---
title: "Website audit"
url: "https://kevinyoung.net/website-audit/"
description: "Every item on specification.website's 172-point checklist, checked against kevinyoung.net, with an honest pass, partial, fail, or not-applicable for each and an overall score."
---

1.  [Home](https://kevinyoung.net/index.md)
2.  Website audit

# Website audit

A full, self-assessed run through specification.website's technical checklist for what a good website does: every one of its 172 items, checked against this site.

## What is specification.website?

[specification.website](https://specification.website/) is an open, community-maintained checklist of the technical details that make a good website: things like a correct `<title>` tag, working search-engine discovery, color contrast, security headers, and how legible a site is to the AI agents more people now use to find things. Each of its 172 items links back to the actual standard behind it, a WHATWG or W3C specification, an IETF RFC, or WCAG, rather than someone's opinion of best practice.

Most of these items are invisible to a visitor scrolling the page, but they're exactly what decides whether this site loads fast, works for people with disabilities, keeps visitor data private, and gets read correctly by both search engines and AI tools. The kevinyoung.net website is checked against this exact checklist. This page is the result: every item, one row each, with an honest status.

## How this page is scored

specification.website tags every item Required, Recommended, Optional, or Avoid (shown as the small badge next to each item's name), and this page weights the score to match: a pass is worth 3 points on a Required or Avoid item, 2 on a Recommended one, and 1 on an Optional one, since a missing `<title>` tag matters a lot more than missing IndexNow support. A partial is worth half of whatever that item's pass is worth, and a fail is always 0, regardless of tier. Items that don't apply to a site like this one (an OAuth item on a site with no logins, say) are left out of the score entirely rather than counted against it. Add up the points earned, divide by the points possible for the items that actually applied, and that's the percentage at the bottom of the table.

**Worth being skeptical of:** this audit was done by Claude, the AI assistant that built most of this site, using its own knowledge of the codebase plus live checks against the published site. It is not an independent, third-party audit, and a person has not re-verified every row by hand. Read it as a detailed, honest self-assessment, not a certification.

## Status key

-   Pass **Pass:** fully meets the item.
-   Partial **Partial:** some of it is done, or done a different way than the spec describes.
-   Fail **Fail:** applies to this site and isn't done.
-   Not applicable **Not applicable:** the item doesn't apply to a site like this one.

85.0%

250 of 294 applicable points across all 172 checklist items

## Foundations

35 / 41 (85%)

specification.website checklist results for Foundations
| Status | Checklist item | How kevinyoung.net did |
| --- | --- | --- |
| Pass | Required The HTML doctype | <!doctype html> is the first line of every prerendered page. |
| Pass | Required The lang attribute on <html> | html lang is set per language (en, es, pt) and kept in sync during client-side navigation. |
| Pass | Required <meta charset> | UTF-8 is declared first in <head>, before any other tag. |
| Pass | Required <meta viewport> | width=device-width, initial-scale=1, and user scaling is never disabled. |
| Pass | Required The <title> element | Every page gets one unique <title> from Seo.svelte, separate from its <h1>. |
| Pass | Recommended <meta name="description"> | Every page has its own hand-written description, authored as a direct answer to what the page is. |
| Pass | Recommended Canonical URL (rel="canonical") | Every page self-canonicalizes, and reposts point their canonical at the original source instead. |
| Pass | Recommended Favicons and app icons | Ships an .ico, 16/32px PNGs, an apple-touch-icon, a manifest with both any and maskable icons (see Web app manifest below), and now a 27 KB static/favicon.svg (a low-poly vector trace of the same photo Kevin generated and hand-optimized, further reduced from 105 KB via SVGO), linked before the .ico so SVG-supporting browsers prefer it. A first vectorization attempt was too heavy to ship (827 KB, 1,481 paths tracing full photographic detail); a lower-fidelity re-trace plus lossless SVGO optimization (whitespace, redundant attributes, integer coordinate rounding) got it to a size actually worth serving, confirmed readable down to 32px. |
| Pass | Recommended <meta name="theme-color"> | Set to match --c-page exactly for the active theme: resolved before first paint by the same inline bootstrap script that sets data-theme, and updated live when the reader toggles the theme. |
| Pass | Recommended <meta name="color-scheme"> | Set as a CSS property tied to the dark-mode toggle, so it changes with the theme rather than sitting as a static tag. |
| Pass | Recommended Open Graph protocol | og:title, og:description, og:image, og:url, og:type and og:locale are set on every page, with alternates for the other two languages. |
| Pass | Recommended Feed discovery with rel="alternate" | Every page links to the per-language RSS feed with <link rel="alternate" type="application/rss+xml">. |
| Pass | Recommended Feed content hygiene | The feed has a self-referencing atom:link, a stable non-permalink guid per item, and a ttl declaring how often it changes. Confirmed valid by the W3C Feed Validator for all three languages. |
| Fail | Recommended Popover API | The cookie banner and Display settings panel are hand-built floating panels with manual positioning and ARIA, not the native Popover API the spec recommends for exactly this. |
| Fail | Optional WebSub: push notification for feeds | No WebSub hub is advertised for the RSS feed, so subscribers still have to poll for updates rather than being pushed them. |
| Fail | Recommended CSS anchor positioning | Panels and menus are positioned with fixed and absolute CSS and manual coordinates, not anchor-name and position-anchor. |
| Pass | Recommended Balanced text wrapping | text-wrap: balance applies to every h1, h2 and h3 sitewide, not just the two About page headings that had it before. |
| Pass | Recommended CSS container queries | The Work page's brand-card grids size their columns off the grid's own rendered width (container-type: inline-size plus @container) rather than the viewport. Today that behaves the same as the viewport breakpoints it replaced, since the surrounding .frame tracks viewport width almost 1:1; the win is that the grid would keep sizing correctly if it were ever reused somewhere narrower than the viewport, which a media query can't do. The rest of the site's layout still runs on ordinary viewport breakpoints, which remain the right tool for page-level layout. |
| Fail | Optional Invoker commands | Buttons that open panels (search, Display settings, the cookie banner) use onclick handlers, not the command/commandfor attributes. |
| Not applicable | Optional \_for-sale DNS records | kevinyoung.net is Kevin's own personal site and is not for sale. |

## SEO

27 / 27 (100%)

specification.website checklist results for SEO
| Status | Checklist item | How kevinyoung.net did |
| --- | --- | --- |
| Pass | Recommended robots.txt | A plain-text robots.txt at the root allows all crawlers and points to the sitemap. |
| Pass | Recommended XML sitemaps | sitemap.xml lists every canonical URL with hreflang alternates, generated fresh on every build. |
| Not applicable | Recommended Sitemap index files | The sitemap holds 45 URLs, far under the 50,000-URL point where an index file would matter. |
| Not applicable | Optional Image and video sitemap extensions | Every image is in plain, server-rendered HTML a crawler can already reach directly; nothing is hidden behind client-side JavaScript or an uncrawlable CDN. |
| Pass | Recommended URL structure | URLs are lowercase, hyphenated and shallow (e.g. /writing/never-old-learn), with identical slugs across all three languages. |
| Pass | Required Redirects (301/302/308) | The only redirect-like rule on the site (the /es/ and /pt/ 404 rewrites) returns the correct status, and no redirect chains exist anywhere. |
| Pass | Recommended Server-side rendering | The whole site is statically prerendered at build time; every page arrives fully formed in the first response, nothing waits on client-side JavaScript. |
| Pass | Avoid Soft 404s | An unknown URL returns a real 404 status, confirmed live, not a 200 with a "not found" message. |
| Pass | Required Meta robots and X-Robots-Tag | Public pages index by default; noindex is set explicitly on Search, the 404 pages, and the generated text and Markdown copies. |
| Pass | Required Heading hierarchy | One h1 per page, checked in the accessibility audit, with semantic (not styled-only) headings throughout. |
| Pass | Recommended Internal linking | Nav, footer, breadcrumbs and in-content links (brand cards, project links, related posts) all tie pages together. |
| Pass | Recommended Structured data (JSON-LD) | Person, WebSite, page-type (WebPage/AboutPage/CollectionPage/BlogPosting) and BreadcrumbList JSON-LD are emitted on every indexable page. |
| Pass | Recommended Breadcrumbs | Both halves are covered: a visible breadcrumb trail plus matching BreadcrumbList JSON-LD, the visible half added in this audit. |
| Pass | Optional IndexNow | Enabled via Cloudflare's Crawler Hints (Kevin's own dashboard setting, not code in this repo): with traffic proxied through Cloudflare, it automatically pings IndexNow-participating search engines (Bing, Yandex, Naver, Seznam) whenever cached content changes, the same mechanism this item is checking for. |

## Accessibility

57 / 63 (90%)

specification.website checklist results for Accessibility
| Status | Checklist item | How kevinyoung.net did |
| --- | --- | --- |
| Pass | Required Colour contrast | Audited with axe-core across every page, both themes, at desktop width and 375px, as recorded on the Accessibility policy page. |
| Fail | Optional Automatic contrasting colour | Text-on-brand-color pairings are hand-picked (a fixed dark ink color) rather than computed with the CSS contrast-color() function. |
| Partial | Recommended Forced colours mode | The forced-colors media feature hides decorative shapes and solidifies hairline borders, but this has been reasoned through rather than tested in an actual forced-colors environment. |
| Pass | Required Image alt text | Every img has an alt attribute; a dedicated quality pass found and fixed two weak ones (a post featured image labelled with the post title instead of what the photo shows) and confirmed the rest are genuinely descriptive. |
| Pass | Required Form labels | Every input this codebase controls (the search box) is properly labelled. The Contact and Speaking pages embed Meteor Forms' and Cal.com's own forms in an iframe; their internal labelling is a different origin's markup, not this site's, the same way a linked-to page's accessibility isn't scored as part of this one. |
| Pass | Required Keyboard navigation | A keyboard-only walkthrough (skip link, header controls, mobile menu, search dialog, FAQ disclosure, cookie banner, Display settings panel) found and fixed one real bug: opening Display settings left focus behind on the trigger, so Tab skipped straight past the now-open panel to the next header button instead of into it (it is mounted after Footer, far from its trigger in DOM order). It now moves focus to the panel heading on open, matching the cookie banner's existing pattern. |
| Pass | Required Visible focus indicators | A two-ring :focus-visible outline is defined sitewide, with an optional thicker "enhanced focus" mode. |
| Pass | Recommended Focus not obscured | Checked directly at phone width: with the cookie banner or Display settings panel open, several footer links (Cookie settings, the policy links, the text-only-version link) were completely hidden behind the fixed panel with no way to scroll them into view, since the document was already at max scroll. Both panels now measure their own rendered height and reserve that much extra scroll room (and matching scroll-padding-bottom) while open, confirmed against the same links: none end up fully hidden any more, in either panel, and the reserved space disappears again on close. |
| Pass | Required Skip links | A "Skip to main content" link is the first focusable element on every page. |
| Partial | Recommended The inert attribute | The native search dialog gets this for free from showModal(); the custom Display settings and cookie banner panels do not inert the rest of the page, relying on visual layering instead. |
| Pass | Required Semantic HTML and landmarks | header, nav, main and footer are used throughout, confirmed during the axe-core audit. |
| Pass | Recommended ARIA: first rule of ARIA | Native elements are preferred everywhere; ARIA is added only where nothing native fits, such as the custom panels. |
| Pass | Required Descriptive link text | Links describe their destination ("Read the original," "See all posts in English"); generic "click here" text is not used. |
| Pass | Avoid Empty links and buttons | Every icon-only control (search, theme toggle, close buttons) carries an aria-label or visually hidden text. |
| Not applicable | Required Accessible form errors | This site has no form of its own that produces a validation error; the Contact and Speaking page forms are entirely Meteor Forms' and Cal.com's own embedded UI. |
| Pass | Recommended Status messages | Confirmed in SearchPanel.svelte: the result-count text sits in its own role="status" aria-live="polite" element, separate from the results list, so a screen reader announces count changes without the results themselves needing to be re-read. |
| Not applicable | Recommended Accessible authentication | There are no accounts or logins anywhere on this site. |
| Not applicable | Recommended Redundant entry | There is no multi-step process anywhere on the site that would ask for the same information twice. |
| Pass | Recommended Consistent help | Search, Display settings, the theme toggle, and now Cookie settings, all sit in the same header or footer position on every page. |
| Pass | Required Document and parts language | html lang is always set correctly per language; the site's content does not currently mix languages mid-paragraph. |
| Pass | Required Reduced motion | prefers-reduced-motion is respected sitewide, and the site has little decorative animation to begin with. |
| Pass | Avoid Accessibility overlays | No third-party accessibility widget is used; the Display settings panel is this site's own first-party code, not a bolted-on overlay. |
| Not applicable | Required Captions and transcripts | The site has no video or audio content. |
| Pass | Required Accessible data tables | Markdown tables get a focusable, scrollable wrapper, scope="col" on every header cell, and an sr-only caption drawn from the heading that introduces the table, all applied automatically by rehype-table-scroll.ts so future tables get them for free. |
| Pass | Required Touch target size | Interactive elements meet the 24 by 24 CSS px minimum sitewide, checked during the layout audit. |
| Not applicable | Recommended Dragging movements | The one candidate, the Writing carousel, uses native browser scrolling with arrow-button alternatives, not a custom drag gesture, so there is no drag-only interaction to provide an alternative for. |
| Pass | Recommended Hidden until found | The About page's "Training and courses" disclosure and the Contact page's FAQ accordions are plain native <details>, which auto-expands for find-in-page and fragment navigation on its own (Chrome 97+, Firefox 139+, Safari 26.2) without needing hidden="until-found" (a separate mechanism for hand-hidden, non-<details> content, which this site does not use). |
| Pass | Recommended Mobile-friendly form inputs | This site's own search input is a plain, correctly-typed text field, the only input this codebase controls. The embedded Meteor Forms and Cal.com forms' input types are a different origin's markup, the same reasoning as Form labels above. |
| Partial | Recommended Native interactive elements | button, a, details and a native dialog (search) are used throughout, but the Display settings panel and cookie banner are deliberately custom, non-modal panels rather than native dialog elements. |
| Fail | Recommended CSS state and relational selectors | :has(), :user-invalid and :focus-within are not used; this site has very little custom form UI for them to apply to. |

## Security

32 / 42 (76%)

specification.website checklist results for Security
| Status | Checklist item | How kevinyoung.net did |
| --- | --- | --- |
| Pass | Required HTTPS and TLS | Served over HTTPS everywhere via Cloudflare and Netlify; plain HTTP redirects to HTTPS. |
| Pass | Required HSTS (Strict-Transport-Security) | Sent with a one-year max-age and includeSubDomains, confirmed live, set through the Cloudflare dashboard. |
| Pass | Recommended Mixed content and upgrade-insecure-requests | Every subresource loads over HTTPS, and the CSP's upgrade-insecure-requests directive is a safety net. |
| Pass | Recommended Content Security Policy (CSP) | Confirmed live and working against production: the cal.com booking widget and Meteor Forms iframe both render and function correctly. It needs 'unsafe-inline' for script-src and style-src on this fully static site, and Cloudflare's own auto-injected analytics beacon is (harmlessly) blocked, since it isn't in the allowlist. |
| Fail | Recommended Reporting API (Reporting-Endpoints) | No Reporting-Endpoints header is set; there is no third-party collector configured yet to receive CSP or COOP violation reports. |
| Pass | Recommended /.well-known/security.txt | Published with a contact address and an expiry date. |
| Pass | Required X-Content-Type-Options: nosniff | nosniff is sent on every response. |
| Pass | Required Clickjacking protection | Both X-Frame-Options: SAMEORIGIN and the modern CSP frame-ancestors 'self' are set. |
| Fail | Recommended Fetch Metadata request headers | Sec-Fetch-\* headers are not read anywhere; this is a static site with no server-side request handler to inspect them. |
| Partial | Recommended Cross-origin isolation (COOP / COEP / CORP) | Cross-Origin-Opener-Policy: same-origin is set; COEP and CORP were deliberately left out, since COEP could break the cal.com and Google Analytics scripts the site depends on. |
| Pass | Recommended Referrer-Policy | strict-origin-when-cross-origin is sent on every response. |
| Pass | Recommended Permissions-Policy | Camera, microphone and geolocation are all turned off sitewide. |
| Fail | Recommended Subresource Integrity (SRI) | Not used for the cal.com embed script or Google Analytics' gtag.js: both are unversioned third-party scripts that change without notice, so a pinned hash would break on their next deploy, not this site's. |
| Fail | Optional Digest Fields | Not implemented. |
| Fail | Recommended Trusted Types | Not implemented yet, deliberately: it would stack a second, unverified enforcement layer on top of a CSP that itself has not been confirmed working in production. |
| Pass | Avoid X-XSS-Protection | Correctly not sent; this dead header is left out, and CSP is relied on instead. |
| Pass | Required Cookie attributes: Secure, HttpOnly, SameSite | This site sets no cookies of its own at all (theme, Display settings and consent state all live in localStorage), so there is nothing here for this codebase to misconfigure. Google Analytics' cookies, loaded only after consent, are Google's to configure. |
| Not applicable | Optional Clear-Site-Data | There is no login, logout, or other event on this site that would need to wipe a visitor's storage. |
| Pass | Recommended DNS CAA records | issue and issuewild records are published for every CA Cloudflare's Universal SSL can use (Let's Encrypt, Google Trust Services, SSL.com, Sectigo), confirmed live; Cloudflare automatically supplemented a couple more (Comodo, DigiCert) on its own. |
| Pass | Optional DNSSEC | Enabled in Cloudflare, with the DS record published at the registrar; confirmed live with a valid RRSIG on the signed answer. |

## Well-Known URIs

5 / 6 (83%)

specification.website checklist results for Well-Known URIs
| Status | Checklist item | How kevinyoung.net did |
| --- | --- | --- |
| Pass | Recommended Well-known URIs | /.well-known/ is used correctly, for security.txt and gpc.json. |
| Not applicable | Optional /.well-known/change-password | The site has no user accounts, so there is no change-password page to point to. |
| Not applicable | Optional /.well-known/webauthn | The site does not use passkeys. |
| Not applicable | Optional /.well-known/openid-configuration | This site is not an OpenID Connect identity provider. |
| Not applicable | Optional /.well-known/oauth-authorization-server | This site does not run an OAuth authorization server. |
| Not applicable | Optional /.well-known/oauth-protected-resource | This site exposes no OAuth-protected API. |
| Pass | Optional /.well-known/gpc.json | Declares that Global Privacy Control is recognized and honored. |
| Pass | Recommended /.well-known/api-catalog | Published per RFC 9727, as a Linkset (RFC 9264) JSON document listing the sitemap, llms.txt, llms-full.txt and RSS feed for every language, plus robots.txt, with a matching Link: rel="api-catalog" response header sitewide. |
| Not applicable | Optional /.well-known/webfinger | Not a Fediverse-connected site. |
| Not applicable | Optional /.well-known/apple-app-site-association | There is no companion iOS app. |
| Not applicable | Optional /.well-known/assetlinks.json | There is no companion Android app. |
| Not applicable | Optional /.well-known/nodeinfo | Not a federated platform. |
| Fail | Optional /.well-known/traffic-advice | Not published; a low-stakes, still-provisional signal this site has not opted into either way. |

## Agent Readiness

20 / 24 (83%)

specification.website checklist results for Agent Readiness
| Status | Checklist item | How kevinyoung.net did |
| --- | --- | --- |
| Pass | Recommended Agent readiness (overview) | Stable URLs, JSON-LD, robots controls and several machine-readable endpoints are all in place, making this one of the stronger categories on this site. |
| Pass | Recommended /llms.txt | Published per language, listing every top-level page, post and brand with a description. |
| Pass | Optional /llms-full.txt | Published per language, concatenating every page's Markdown into one file. |
| Pass | Recommended Per-page Markdown source endpoints | Every page is available as raw Markdown at a matching .md URL, plus a <link rel="alternate" type="text/markdown">. |
| Pass | Recommended robots.txt for AI crawlers | Kevin's stated position: this is a personal-brand site, so being read, cited and trained on by AI is the point, the same way being indexed by a search engine is. robots.txt now names GPTBot, ClaudeBot, Google-Extended, CCBot, PerplexityBot and others explicitly with Allow: /, instead of relying on the silent User-agent: \* default. |
| Pass | Optional Content Signals in robots.txt | A Content-Signal: search=yes, ai-input=yes, ai-train=yes line on the User-agent: \* block matches the same welcoming stance. |
| Fail | Optional TDM reservation (TDMRep) | No tdm-reservation header, meta tag, or tdmrep.json is published. |
| Fail | Optional Web Bot Auth: verifiable bot identity | Not implemented; no bot is currently allowed or blocked by signed identity. |
| Pass | Required Stable URLs | Slugs are permanent and identical across all three languages; nothing has been renamed or broken since launch. |
| Pass | Recommended Structured data for agents | The same JSON-LD used for search engines is available to agents: Person, WebSite, page types, and breadcrumbs. |
| Pass | Recommended Machine-readable formats | RSS, llms.txt, llms-full.txt and per-page Markdown are all offered alongside the HTML. |
| Pass | Recommended HTTP Link headers for discovery | sitemap.xml, llms.txt and rss.xml are now also advertised via a real HTTP Link response header (netlify.toml), language-matched for the /es/ and /pt/ trees, for agents that check headers without parsing the page body. |
| Not applicable | Optional MCP and tool discovery | This is a static content site with nothing queryable or actionable to expose as a tool, and no server to run an MCP endpoint on. |
| Not applicable | Optional A2A agent cards | The site has no agentic behaviour of its own for another agent to delegate to. |
| Not applicable | Recommended Agent Skills discovery | Checked against Cloudflare's own draft RFC (github.com/cloudflare/agent-skills-discovery-rfc): it defines a /.well-known/agent-skills/index.json listing actual scoped instructions/tools an agent can load. This is a static personal-brand site with no such skill or tool to register, the same reasoning as the na status on MCP and tool discovery and A2A agent cards above; publishing an empty or fabricated index would misrepresent what this site offers. |
| Pass | Optional DNS for AI Discovery (DNS-AID) | An \_index.\_agents HTTPS record points at kevinyoung.net with the recommended alpn, port and mandatory parameters, confirmed live and DNSSEC-signed. |
| Not applicable | Optional Agentic Resource Discovery (ARD) | There are no MCP servers or A2A agents on this domain to catalog. |
| Fail | Optional NLWeb: conversational interface discovery | Not implemented; this static site has no natural-language query endpoint to advertise. |
| Fail | Optional WebMCP: browser-native tools for agents | Not implemented; there is no in-page tool yet worth exposing to a browser-resident agent. |
| Not applicable | Optional Open Knowledge Format (OKF) bundle | llms-full.txt already serves the "ingest everything in one fetch" purpose OKF targets, at a fraction of the complexity, for a site this size. |
| Not applicable | Optional Schemamap | This is specification.website's own proposed, not-yet-adopted convention with no external standard behind it yet. |

## Performance

36.5 / 49 (74%)

specification.website checklist results for Performance
| Status | Checklist item | How kevinyoung.net did |
| --- | --- | --- |
| Partial | Required Core Web Vitals (LCP, INP, CLS) | Measured with PageSpeed Insights (Lighthouse 13.5) against production, Sep 28, 2026, before and after fixing the image-delivery gap below. No field data exists either way (Chrome UX Report: "No Data", not enough real-user traffic for a site this size). Before: desktop LCP 1.8s, mobile LCP 4.9s, image-delivery opportunity 2,047 KiB, total page weight flagged at 2.7 MB. After re-optimizing the ten post featured images (AVIF/WebP via <picture>, see the Performance section below): desktop LCP nearly halved to 0.9s, mobile Speed Index dropped from 4.2s to 2.5s, the image-delivery opportunity shrank 79% to 430 KiB (now just the hero portrait and a few already-optimized photos wanting slightly tighter sizing, not a format problem), and the "enormous network payload" warning is gone entirely. Mobile LCP barely moved (4.9s to 4.6s, still over Google's 2.5s "good" threshold) despite those other gains, and mobile Performance only ticked up one point (79 to 80); read as lab-run noise from a single throttled run against a live edge network rather than the fix not working, given how much moved everywhere else. CLS (0.000 to 0.001) and TBT (0ms) stayed excellent throughout, on both form factors. Left as partial, not pass: mobile LCP still misses the "good" bar under worst-case simulated conditions and there's still no field data to say real users fare better. |
| Pass | Required Image optimisation | Every photo ships as AVIF and WebP with a jpg or png fallback. Most carry explicit width and height; the ten post featured images (added later, see the Core Web Vitals note above) don't, since two of their three render contexts force a fixed aspect-ratio box in CSS regardless, and CLS was already 0.000-0.001 before they existed. A responsive srcset (multiple widths for different screens) was deliberately skipped: it would add build complexity for savings a single well-chosen size mostly already captures. |
| Pass | Recommended Lazy loading images, iframes, and video | loading="lazy" is set on every below-the-fold image; the LCP hero images are correctly left eager. |
| Pass | Recommended Preload, prefetch, preconnect | The LCP hero image is preloaded, format-matched to the picture element's first source, and third-party origins that load unconditionally (cal.com, Meteor Forms) are preconnected. |
| Fail | Optional 103 Early Hints | Root cause found: Cloudflare's Early Hints replays a Link header from a cached copy of the page, but this site's HTML is intentionally Cache-Control: max-age=0 (cf-cache-status: DYNAMIC), so Cloudflare has nothing cached to replay it from. The Link header itself is confirmed sent correctly; getting an actual 103 would mean making the HTML edge-cacheable, a bigger caching-strategy tradeoff, not done here. |
| Pass | Required Cache-Control headers | Hashed build assets get a one-year immutable cache; HTML gets a short, must-revalidate policy, confirmed live. |
| Pass | Recommended Vary: the cache key contract | Vary: Accept-Encoding is sent, confirmed live, without over-listing headers that would fragment the cache. |
| Fail | Recommended Conditional requests (ETag, Last-Modified, 304) | No ETag or Last-Modified header is sent on the live HTML response, so every request re-downloads the full body instead of getting a 304. |
| Pass | Recommended No-Vary-Search response header | No-Vary-Search: params is sent sitewide: every page is static prerendered HTML whose content never changes based on a query string (the search page reads ?q=/&section= client-side, after load), so caches and BFCache can safely treat any two URLs differing only by query params as the same page. |
| Pass | Required Compression (gzip, brotli, zstd) | Brotli is confirmed live (content-encoding: br). |
| Partial | Recommended Web font loading | Fonts are self-hosted WOFF2 with font-display: swap, but are not preloaded, since the build's content-hashed filenames would need a small script to find them, similar to the existing text-page generator. |
| Partial | Recommended Critical CSS and render-blocking resources | Vite and SvelteKit code-split CSS per route automatically, but no manual critical-CSS extraction or inlining has been added on top of that. |
| Pass | Recommended Script loading: defer, async, module | Vite emits type="module" for the app bundle; the Google Analytics and cal.com scripts are injected as async. |
| Pass | Recommended HTTP/2 and HTTP/3 | Confirmed live: HTTP/2 at minimum, with alt-svc advertising HTTP/3. |
| Pass | Avoid HTTP/1.1 workarounds: sharding, sprites, and bundling | No domain sharding or image sprites are used anywhere. |
| Pass | Recommended Speculation Rules | A sitewide prefetch ruleset (moderate eagerness, same-origin only) was added in this round of work. |
| Partial | Recommended Resource hints overview | modulepreload (automatic), preconnect and preload are all used; dns-prefetch is deliberately not, since the only remaining un-preconnected third-party origin is consent-gated Google Analytics, and a DNS lookup before consent defeats the point of gating it. Standalone prefetch hints aren't used anywhere. |
| Fail | Optional View Transitions | Not implemented; navigation has no cross-document transition animation. |
| Pass | Recommended Back/forward cache (BFCache) | No unload or beforeunload handlers exist anywhere in the codebase, and the live site's Cache-Control (public, max-age=0, must-revalidate) does not include no-store, the two most common blockers. The Speaking and Contact pages' third-party iframes (cal.com, Meteor Forms) are outside this codebase's control and were not checked. |
| Fail | Optional Visibility-aware rendering | content-visibility and Intersection Observer are not used; the site is small enough that this has not been a rendering bottleneck. |
| Fail | Optional CSS containment | Not used anywhere. |
| Fail | Optional Scroll-driven animations | Not used; the site has very little scroll-tied animation to begin with. |
| Pass | Recommended Scrollbar gutter | scrollbar-gutter: stable is set on html sitewide, so gaining or losing a scrollbar no longer shifts content. |
| Pass | Recommended Dynamic viewport units (dvh, svh, lvh) | The remaining full-height and viewport-relative rules (the layout's min-height, the search dialog's margin, the reading ruler's default position) now use dvh instead of plain vh. |
| Fail | Optional Compression Dictionary Transport | Not implemented; a newer technique with limited hosting support at this stage. |
| Not applicable | Optional Server-Timing header | This is a fully static site with no backend request processing to measure and report on. |

## Privacy

13 / 14 (93%)

specification.website checklist results for Privacy
| Status | Checklist item | How kevinyoung.net did |
| --- | --- | --- |
| Pass | Required Privacy policy | A plain-language policy covers what is collected, Google Analytics, the cookie banner, GPC, and every third party (Cal.com, Meteor Forms, Netlify), in all three languages. |
| Pass | Required Cookie consent | A default-deny banner blocks Google Analytics until a visitor explicitly accepts, built in this round of work. |
| Pass | Recommended Global Privacy Control (GPC) | Honored automatically: a GPC signal turns analytics off with no banner shown and nothing written to storage, and /.well-known/gpc.json declares this. |
| Pass | Recommended Third-party scripts and privacy | The only third-party scripts (Google Analytics, cal.com) are consent-gated or page-specific, and both are listed explicitly in the CSP allowlist and the Privacy policy. |
| Not applicable | Optional Storage Access API | This site has no embedded cross-site content of its own that would need to request its own cookies. |
| Partial | Recommended Privacy-respecting analytics | Google Analytics is used rather than a cookieless, EU-hosted alternative, but it is now fully consent-gated and GPC-aware, which addresses the consent half of this item even though the tool itself is not the more private option the spec favors. |
| Pass | Recommended Data minimisation | Only what is needed is collected: no accounts, no combining analytics with other data, and the Contact form only sends what a visitor types into it. |

## Resilience

6 / 8 (75%)

specification.website checklist results for Resilience
| Status | Checklist item | How kevinyoung.net did |
| --- | --- | --- |
| Pass | Required Custom error pages (404, 500) | A real 404 page returns the correct status, explains what happened in plain language, and links back into the site, confirmed live. |
| Not applicable | Recommended Maintenance pages and 503 | The site has no planned-maintenance workflow; Netlify keeps serving the last successful build, so there is no maintenance state to design a page for. |
| Partial | Recommended Graceful degradation when JavaScript fails | Every page is fully prerendered, so core content and navigation work without JavaScript; interactive extras (search, Display settings, the cookie banner, cal.com booking) do need it, and that has not been explicitly tested with JavaScript disabled. |
| Fail | Optional Offline support and service workers | Not implemented; a network failure shows the browser's own offline page, not a cached fallback. |
| Pass | Recommended Web app manifest | site.webmanifest has a name, theme and background color, and now lists both purpose: "any" and purpose: "maskable" icons: the maskable pair are freshly generated, shrinking the existing circular photo crop onto a full-bleed same-color background so the face sits inside the maskable safe zone instead of touching the canvas edge. |
| Not applicable | Recommended Monitoring and uptime | This is a hosting and operations decision for Kevin to make, not something the codebase itself can implement. |
| Not applicable | Optional Deprecation and Sunset | This site has no API endpoints being retired that clients would need advance warning about. |
| Not applicable | Recommended Redirect-By header | There are no genuine HTTP redirects (3xx) configured anywhere on this site right now for the header to attach to. |

## Internationalisation

18.5 / 20 (93%)

specification.website checklist results for Internationalisation
| Status | Checklist item | How kevinyoung.net did |
| --- | --- | --- |
| Pass | Recommended International URL structure | One consistent pattern (subdirectory: /es/, /pt/) is used everywhere, with no mixing of country-domain or subdomain approaches. |
| Pass | Recommended hreflang for language and regional URLs | Reciprocal hreflang alternates, plus x-default, are set on every page that has translations, both in <head> and in the sitemap. |
| Pass | Recommended Localised page metadata | Title, description, Open Graph, JSON-LD and breadcrumbs are all translated per language, not just the visible body text. |
| Pass | Optional hreflang in XML sitemaps | The sitemap declares language alternates with xhtml:link, not only in the HTML head. |
| Pass | Avoid Avoid automatic IP-based language redirects | There is no IP- or Accept-Language-based redirect anywhere; visitors choose their language explicitly. |
| Pass | Required lang attribute on inline content | The document-level lang is always correct, and the site's content does not mix languages mid-paragraph anywhere, so there is no inline foreign-language text that would need its own lang attribute. Revisit if a future post ever quotes another language inline. |
| Partial | Optional translate attribute for untranslatable content | Added to brand and project names across the main pages and footer in this round, but not yet swept across every remaining mention sitewide, such as inside long-form post body text. |
| Pass | Recommended Language switcher | Lists each language by its own name (English, Español, Português) with the correct lang attribute, with no flags. |
| Not applicable | Recommended RTL and bidirectional text | The site only serves English, Spanish and Portuguese, none of which are right-to-left languages. |
| Not applicable | Optional Writing modes and CJK line breaking | The site serves no Chinese, Japanese, Korean or vertical-script content. |
| Pass | Recommended Locale-aware content | Dates are formatted per locale through a shared date helper built on the Intl API. |
| Partial | Recommended Plural rules and grammatical number | "1 result" versus "{n} results" style strings are hand-authored per language rather than driven by Intl.PluralRules or CLDR categories, though Spanish and Portuguese's simple plural rules have not caused a wrong-sounding string yet. |
| Not applicable | Optional Internationalised Domain Names (IDN) | kevinyoung.net is a plain ASCII domain name. |

85.0%

Overall score: 250 of 294 applicable points (-122 of 172 items were not applicable and don't count toward or against the score)
