fullstack.rocks skills · demos

Reviewed examples/maison-lumen/sites/car/index.html and styles.css against clients/car/design.md, clients/car/profile.md section 6, and research/accessibility.md. No main.js is referenced or present. No files were changed. Every local file the page points at is on disk (styles.css and the ten assets under clients/car/assets/). Fragment targets #main, #arrival, #touring, #fit, #ownership, and #house exist, and all four appointment links use the specified mailto draft. WebP pixels could not be decoded here; alt text matches design.md verbatim, and the asset inventory reports the stated dimensions.

Text pairs in the five-token palette pass WCAG 2.2 AA: chalk/graphite 14.97:1, mineral silver/graphite 8.57:1, fired clay/chalk 5.56:1, pale brass/graphite 9.27:1. Headings are one h1 and seven h2s plus the appointment h3. There is no text on photographs, no autoplay, and reduced motion removes the only transition. The service drawing uses graphite non-scaling strokes and has no <text>; its explanation is the HTML caption.

ID tag severity where finding fix
A1 a11y high styles.css:170-196 The skip link is the first tab stop. Its ring is clay #80543F (--ring on .skip-link) drawn, with a 4px offset, on the graphite header #171B1D. That pair is 2.69:1, under the 3:1 non-text minimum (SC 1.4.11). Brass-on-graphite and clay-on-chalk rings elsewhere clear 3:1. On .skip-link:focus-visible, add box-shadow: 0 0 0 8px var(--chalk);. Keep the global outline: 2px solid var(--ring); outline-offset: 4px, --ring: var(--clay), chalk background, and graphite text. The shadow fills the 4px gap and adds 2px of chalk outside the ring, so clay sits on chalk (5.56:1). Leave the forced-colors Highlight rule in place.
X1 ux high styles.css:161-168; index.html:18 The header wordmark is an a and inherits text-decoration-line: underline. Nav links and .button clear that; .wordmark does not. Hover (a:hover) turns the line pale brass under the chalk mark. A luxury buyer reads that as an unstyled link. After the a:hover rule, add .wordmark, .wordmark:hover, .wordmark:focus-visible { text-decoration: none; color: var(--ink); }. Leave the global focus ring so the logo link still shows the brass outline.
A2 a11y medium styles.css:181-198 .skip-link is position: absolute; left: -9999px until :focus. That box sits far outside the viewport and can widen the scrollport, which breaks reflow at 320px (SC 1.4.10). Set left: var(--space-1) in the base rule and hide it with transform: translateY(calc(-100% - var(--space-4))). In .skip-link:focus, set transform: none and delete the left override. Keep the A1 box-shadow on :focus-visible.
A3 a11y medium styles.css:202-227; index.html:58,183 Header .header-cta stretches, so its label wraps. Arrival and closing .cta-group use align-items: flex-start, so those buttons stay one line: 16px side padding, a 1px border, and “Request a private appointment”. At a 320px viewport the content column is 272px. The design acceptance check (320px reflow plus 0.12em letter-spacing) makes that label wider than the column and forces a horizontal scrollbar (SC 1.4.10, 1.4.12). Add max-width: 100% to .button and max-width: 100% to .cta-group. Under @media (max-width: 767px), set .cta-group { align-items: stretch; }. Leave the 1024px .header-cta { align-items: flex-end; } rule as it is.
X2 ux medium styles.css:157-158; index.html:184,190 design.md sets light-theme helpers to fired clay on chalk. .light .helper forces graphite, so “Opens a demonstration email draft. No appointment is booked.” and “Demonstration address — not monitored.” read as body copy. Dark helpers correctly stay mineral silver. Replace the combined rule with .light .helper { color: var(--clay); } and .light figcaption { color: var(--graphite); }. Figcaptions on chalk stay graphite.
X3 ux medium styles.css:161-168; index.html:189 Light-theme links are specified as clay, turning graphite on hover. a { color: var(--ink); } makes appointments@veyravel.example graphite on chalk, the same ink as the paragraph, with only the underline separating it. After the a rules, add .light a:not(.button) { color: var(--clay); } and .light a:not(.button):hover { color: var(--graphite); }. Outline buttons on chalk stay graphite.
X4 ux medium styles.css:484; index.html:187 The type table sets “Appointment office” at Manrope 500, 20/28. .contact h3 resets it to 16/24 (--ui-size / --ui-line), so the only street address sits at caption size beside the closing button. Delete the font-size and line-height declarations on .contact h3. The base h3 rule (1.25rem / 1.4, weight 500) then applies. Keep the contact divider and spacing.
X5 ux medium styles.css:355-358 From 768px to 1023px, .arrival-aside is forced to grid-column: 1 / span 6 while the headline still spans the full eight columns. The description and appointment button sit in a short left column under a full-width headline until the 1024px side-by-side layout. Delete .arrival-aside { grid-column: 1 / span 6; } from the 768px query. Keep .hero-plate { aspect-ratio: 3 / 1; } there. The 1024px rules already place .arrival-head on columns 1–7 and .arrival-aside on 9–12.
A4 a11y low index.html:34-44 Below 1024px the desktop nav is display: none and the other <nav aria-label="Main"> is inside a closed <details>, so the header navigation landmark is absent until Menu is opened. Footer nav stays available. Wrap the disclosure: <nav class="nav-mobile" aria-label="Main"><details><summary>Menu</summary><ul>…same four links…</ul></details></nav>. Drop the inner <nav>. Existing .nav-mobile CSS still applies.
A5 a11y low styles.css:230-240; index.html:35 summary has min-height: 2.75rem (44px) and no min-width or horizontal padding. “Menu” in Manrope 500 at 16px is about 40px wide. WCAG 2.5.8 (24×24) passes; design.md and profile §8 require 44×44. Add .nav-mobile summary { min-width: 2.75rem; padding-inline: var(--space-2); }.
U1 usability low index.html:34-44 The mobile menu is in normal flow. Activating The 01, Making, Ownership, or The House scrolls to the section and leaves <details open>, so the list is still expanded when the reader returns to the header. Before </body>, add a script that, for each .nav-mobile a, sets link.closest("details").open = false on click. With JavaScript off, the menu stays as it is now. This still works after A4.
X6 ux low styles.css:455,467,473 At 1024px and up, touring and ownership text (columns 8–12) and the appointment text (columns 7–12) add padding-left: 32px on top of the 24px gutter, so those columns start inset from the grid. Remove padding-left: var(--space-4) from .touring > .story-text, .ownership > .story-text, and .appointment > .story-text in that query. Keep the grid-column values.
X7 ux low styles.css:389-396; index.html:125-129 On graphite, ledger labels are mineral silver. .stats dd (“Target combined WLTP range”, “Target DC charge, 10–80%”, “Target combined output”) inherits chalk, the same ink as the Newsreader figures. After the .stats dd rule, add .dark .stats dd { color: var(--silver); }. Leave the chalk touring ledger as graphite.
X8 ux low index.html:105,123; styles.css:130-131 Body measure is 55 characters; introductory copy is 62. The materials and engineering paragraphs use class="lead" (62ch) even though design.md calls them body. Remove class="lead" from those two paragraphs. Keep class="lead" on the arrival description (index.html:56).
X9 ux low styles.css:15-17,62-68,126-127,392 --h1-line: 1.05 and --h2-line: 1.1 are used at every width. Mobile spec is H1 40/44 (1.1) and H2 32/38.4 (1.2). Desktop 80/84 and 48/52.8 already match 1.05 and 1.1. In the base :root, set --h1-line: 1.1 and --h2-line: 1.2. Inside the existing @media (min-width: 1024px) :root block, set --h1-line: 1.05 and --h2-line: 1.1. .stats dt already uses --h2-line.

The palette, heading order, alt text, mailto path, and reduced-motion rule already meet the brief; the skip-link ring is the one contrast failure, and the off-screen skip link plus the unconstrained appointment buttons are the reflow risks. Visible copy matches the locked design.md strings and profile §6: materials and makers are named, range and charging stay labelled as targets, and there is no countdown language, typo, or cliché outside those approved lines. A buyer’s path to “Request a private appointment” is the header, the arrival, and the closing email; what reads as cheap is the underlined wordmark, graphite helpers and link on chalk, the 16px office heading, and the broken tablet arrival column.