/* ==========================================================================
   SOMLIS — site.css
   Single stylesheet, no build step, no preprocessor. Organised:
     1. Fonts        2. Tokens        3. Reset & base       4. Layout/grid
     5. Type utility  6. Buttons/links 7. Navigation          8. Footer
     9. Sections      10. Components  11. Forms               12. Blog
     13. Work cards   14. Hero figure/motion  15. Responsive  16. Print
   Colour tokens are the founder-approved brand kit values, verified against
   WCAG AA — see IMPLEMENTATION-NOTES.md "Contrast verification" for the
   worked numbers behind every derived (non-fixed) token below.
   ========================================================================== */

/* 1. Fonts — self-hosted, subset to latin, swap to avoid invisible text */
@font-face {
  font-family: 'Space Grotesk';
  src: url('/assets/fonts/space-grotesk-latin-500-normal.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Space Grotesk';
  src: url('/assets/fonts/space-grotesk-latin-700-normal.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('/assets/fonts/ibm-plex-sans-latin-400-normal.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('/assets/fonts/ibm-plex-sans-latin-600-normal.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('/assets/fonts/ibm-plex-mono-latin-500-normal.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* 2. Tokens ---------------------------------------------------------- */
:root {
  /* Fixed brand colours — from the approved brand kit, not to be altered */
  --ink: #202536;
  --paper: #F9F5EC;
  --text: #F5F1E6;
  --gold: #E3B548;
  --muted-dark: #8C93AC;   /* secondary text on ink — 4.99:1 */
  --muted-light: #7C7263;  /* secondary text on paper, LARGE TEXT ONLY — 4.34:1 */

  /* Derived — computed to clear WCAG AA where the fixed value doesn't */
  --muted-light-text: #716858; /* body-size secondary text on paper — 5.04:1 */
  --gold-ink: #846314;          /* gold-hued text/links/small accents on paper — 5.11:1 */

  /* Surfaces — dark-ground panels reuse the brand kit's own UI panel tones */
  --surface-raised: #262B40;
  --surface-raised-2: #2E3550;
  --hairline: #3B4260;
  --border-strong: #4C5578;

  /* Paper-ground panels — no brand kit equivalent existed, derived to match */
  --paper-raised: #F2EDE0;
  --hairline-paper: #E3DDC9;
  --border-strong-paper: #CFC6AC;

  --error: #C0392B;        /* on paper — 5.0:1 */
  --error-on-ink: #F0776B; /* on ink — 5.48:1 */
  --focus-ring: var(--text);
  --focus-ring-paper: var(--ink);

  /* Type */
  --font-display: 'Space Grotesk', system-ui, sans-serif;
  --font-body: 'IBM Plex Sans', system-ui, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, monospace;

  --display-xl: clamp(2.5rem, 1.3rem + 4.6vw, 4.75rem);
  --display-l: clamp(1.9rem, 1.3rem + 2.4vw, 3rem);
  --display-m: clamp(1.4rem, 1.1rem + 1.2vw, 2rem);
  --heading-s: clamp(1.05rem, 0.95rem + 0.4vw, 1.2rem);
  --lede: clamp(1.0625rem, 0.98rem + 0.4vw, 1.25rem);
  --body: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  --body-s: 0.9375rem;
  --caption: 0.8125rem;

  /* Space — 4px base scale, matching the approved blueprint's scale */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 20px;
  --sp-6: 24px; --sp-7: 32px; --sp-8: 40px; --sp-9: 48px; --sp-10: 56px;
  --sp-11: 64px; --sp-12: 80px; --sp-13: 96px; --sp-14: 112px; --sp-15: 128px;
  --sp-16: 160px; --sp-17: 200px;

  --section-y: clamp(80px, 8.5vw, 160px);
  --section-y-lg: clamp(112px, 11vw, 200px);
  --gutter-x: clamp(20px, 4.5vw, 64px);
  --shell-w: 1240px;

  --dur-fast: 120ms;
  --dur-med: 200ms;
  --dur-slow: 400ms;
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}

/* 3. Reset & base ------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--body);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}
body.surface-paper { background: var(--paper); color: var(--ink); }
/* A single section can also carry the paper surface (homepage §H8) without
   the whole page being paper-ground — see 04 §H8 of the approved blueprint.
   Scoped to .section specifically so it never accidentally paints the nav
   or footer, which stay ink-ground on every page regardless of surface. */
section.surface-paper { background: var(--paper); color: var(--ink); }
img, svg { max-width: 100%; display: block; }
a { color: inherit; }
h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 700; margin: 0; letter-spacing: -0.01em; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
input, textarea, select { font: inherit; color: inherit; }

.skip-link {
  position: absolute; left: 0; top: -100px; z-index: 200;
  background: var(--gold); color: var(--ink); padding: 12px 20px;
  font-weight: 600; transition: top var(--dur-fast) var(--ease-standard);
}
.skip-link:focus { top: 0; }

:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.surface-paper :focus-visible { outline-color: var(--focus-ring-paper); }

/* 4. Layout -------------------------------------------------------------- */
.shell { max-width: var(--shell-w); margin: 0 auto; padding: 0 var(--gutter-x); }
.section { padding: var(--section-y) 0; }
.section-lg { padding: var(--section-y-lg) 0; }
.section-rule { border-top: 1px solid var(--hairline); }
.surface-paper .section-rule { border-top-color: var(--hairline-paper); }

.section-header { max-width: 46ch; margin-bottom: var(--sp-11); }
.section-header h2 { font-size: var(--display-l); line-height: 1.1; }
.section-header .lede { margin-top: var(--sp-5); }

.lede { font-size: var(--lede); line-height: 1.55; color: var(--muted-dark); max-width: 52ch; }
.surface-paper .lede { color: var(--muted-light-text); }

main { display: block; }

/* 5. Type utility --------------------------------------------------------- */
.eyebrow-none { /* intentionally absent — see 02 §7 of the approved blueprint: no tracked all-caps
                   eyebrow labels above headings anywhere on this site. */ }

/* 6. Buttons & links ------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 52px; padding: 0 28px; border-radius: 2px;
  font-family: var(--font-body); font-weight: 600; font-size: 0.9375rem;
  text-decoration: none; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.btn-primary { background: var(--gold); color: var(--ink); }
.btn-primary:hover { background: #ECC373; }
.btn-primary:active { background: #C79A34; }
.surface-paper .btn-primary { background: var(--gold-ink); color: var(--paper); }
.surface-paper .btn-primary:hover { background: #9A7419; }

.btn-secondary { background: transparent; color: var(--text); border: 1px solid var(--border-strong); }
.btn-secondary:hover { border-color: var(--muted-dark); }
.surface-paper .btn-secondary { color: var(--ink); border-color: var(--border-strong-paper); }

.text-link { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.text-link:hover { color: var(--gold); }
.surface-paper .text-link:hover { color: var(--gold-ink); }

.underline-link {
  display: inline-block; font-weight: 600; text-decoration: none;
  border-bottom: 1px solid var(--gold); padding-bottom: 2px;
  transition: border-bottom-width var(--dur-fast) var(--ease-standard);
}
.underline-link:hover { border-bottom-width: 2px; }
.surface-paper .underline-link { border-bottom-color: var(--gold-ink); }

/* 7. Navigation -------------------------------------------------------------- */
.site-nav {
  position: sticky; top: 0; z-index: 100;
  height: 76px; display: flex; align-items: center;
  background: transparent; transition: background var(--dur-med) var(--ease-standard), height var(--dur-med) var(--ease-standard), border-color var(--dur-med) var(--ease-standard);
  border-bottom: 1px solid transparent;
}
.site-nav.scrolled { height: 64px; background: rgba(32,37,54,0.97); border-bottom-color: var(--hairline); }
/* No backdrop-filter here — filter/backdrop-filter creates a new containing
   block for position:fixed descendants (CSS spec), and .nav-panel below is
   a fixed-position child of .site-nav. With backdrop-filter active, the
   panel's "fixed" positioning was being computed relative to the 64-76px
   nav bar instead of the viewport, so opening the menu after scrolling
   rendered it collapsed/hidden instead of full-screen. Raised the solid
   background from 92%→97% opacity to keep the same legibility without it. */
.surface-paper .site-nav.scrolled { background: rgba(249,245,236,0.97); border-bottom-color: var(--hairline-paper); }

.nav-inner { display: flex; align-items: center; gap: var(--sp-8); width: 100%; }
.logo-lockup { display: inline-flex; align-items: center; gap: calc(var(--mark-size, 26px) * 0.26); text-decoration: none; color: inherit; flex: 0 0 auto; }
.logo-mark { width: calc(var(--mark-size, 26px) * 595 / 613); height: var(--mark-size, 26px); flex: 0 0 auto; }
.logo-word { font-family: var(--font-display); font-weight: 700; font-size: 1.0625rem; letter-spacing: 0.03em; }

.nav-links { display: flex; gap: var(--sp-7); margin-left: auto; }
.nav-links a { text-decoration: none; font-size: 0.9375rem; font-weight: 500; color: var(--muted-dark); padding-bottom: 4px; border-bottom: 1px solid transparent; transition: color var(--dur-fast) var(--ease-standard); }
.nav-links a:hover { color: var(--text); }
.nav-links a[aria-current="page"] { color: var(--text); border-bottom-color: var(--gold); }
.surface-paper .nav-links a { color: var(--muted-light-text); }
.surface-paper .nav-links a:hover { color: var(--ink); }
.surface-paper .nav-links a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--gold-ink); }

.nav-cta { height: 44px; padding: 0 22px; margin-left: var(--sp-6); }
.nav-whatsapp { display: inline-flex; align-items: center; gap: 8px; height: 44px; padding: 0 18px; margin-left: var(--sp-6); }
.nav-whatsapp svg { width: 16px; height: 16px; flex: 0 0 auto; }

.nav-panel { display: none; }

/* Mobile quick-access bar — fixed to the bottom of the viewport, WhatsApp +
   menu, mirroring the persistent bottom action bar pattern from the
   founder-supplied Deimix reference. Desktop-hidden by default; shown only
   under the <1024px media query below, alongside hiding the top nav's own
   hamburger (the trigger lives down here on mobile instead — see 15.). */
.mobile-quickbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
  display: none; align-items: stretch;
  background: var(--ink); border-top: 1px solid var(--hairline);
  padding-bottom: env(safe-area-inset-bottom, 0);
}
.quickbar-whatsapp, .quickbar-menu {
  flex: 1 1 50%; display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 60px; font-family: var(--font-body); font-weight: 600; font-size: 0.875rem;
  color: var(--text); text-decoration: none; border: 0; background: none; font: inherit;
}
.quickbar-whatsapp { color: var(--ink); background: var(--gold); }
.quickbar-whatsapp svg { width: 18px; height: 18px; }
.quickbar-menu { border-left: 1px solid var(--hairline); }
.quickbar-menu-icon { display: inline-flex; flex-direction: column; gap: 4px; }
.quickbar-menu-icon span { display: block; width: 18px; height: 1.5px; background: currentColor; }
.surface-paper .mobile-quickbar { background: var(--paper); border-top-color: var(--hairline-paper); }
.surface-paper .quickbar-menu { color: var(--ink); border-left-color: var(--hairline-paper); }

/* 8. Footer -------------------------------------------------------------------- */
.site-footer { background: var(--ink); color: var(--text); border-top: 1px solid var(--hairline); padding-top: var(--sp-13); }
.footer-grid { display: grid; grid-template-columns: 4fr 3fr 3fr; gap: var(--sp-8); padding-bottom: var(--sp-9); }
.footer-wordmark { font-family: var(--font-display); font-weight: 700; font-size: 1.0625rem; letter-spacing: 0.03em; }
.footer-tagline { font-family: var(--font-display); font-size: 1.0625rem; font-weight: 500; color: var(--muted-dark); margin-top: var(--sp-4); max-width: 22ch; }
.footer-heading { display: block; font-family: var(--font-mono); font-size: var(--caption); letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted-dark); margin-bottom: var(--sp-4); }
.footer-links, .footer-start { display: flex; flex-direction: column; gap: var(--sp-3); }
.footer-links a:not(.btn), .footer-start a:not(.btn) { text-decoration: none; color: var(--muted-dark); font-size: var(--body-s); }
.footer-links a:not(.btn):hover, .footer-start a:not(.btn):hover { color: var(--text); }
.footer-location { color: var(--muted-dark); font-size: var(--body-s); }
.footer-start .btn { margin-top: var(--sp-2); align-self: flex-start; }
.footer-legal { display: flex; justify-content: space-between; padding: var(--sp-6) 0; border-top: 1px solid var(--hairline); font-family: var(--font-mono); font-size: var(--caption); color: var(--muted-dark); }
.footer-legal a { color: var(--muted-dark); text-decoration: none; }
.footer-legal a:hover { color: var(--text); }

/* 9. Hero ------------------------------------------------------------------------ */
/* Padding longhands, not the shorthand. The shorthand's `0` horizontal
   component overrode .shell's `padding: 0 var(--gutter-x)` — equal
   specificity, later source order — leaving the hero with no gutter at any
   viewport narrower than --shell-w. The max-width override puts that gutter
   OUTSIDE the 1240px content box, so the hero's content geometry on a wide
   screen is unchanged. Do not collapse these back into a shorthand. */
.hero { display: grid; grid-template-columns: 7fr 5fr; align-items: center; min-height: 82vh; max-width: calc(var(--shell-w) + var(--gutter-x) * 2); padding-top: var(--sp-17); padding-bottom: var(--sp-11); gap: var(--sp-8); }
.hero-text h1 { font-size: var(--display-xl); line-height: 1.03; max-width: 12ch; }
.hero-text .lede { margin-top: var(--sp-7); }
.hero-actions { display: flex; gap: var(--sp-4); margin-top: var(--sp-8); flex-wrap: wrap; }
.hero-figure-wrap { position: relative; overflow: visible; }
.hero-figure { width: 100%; height: auto; overflow: visible; }
/* Stroke contrast ladder, measured against --ink #202536. The ladder is now a
   DRAFTING HIERARCHY rather than a left-to-right gradient: the object is the
   brightest thing, its annotation is a step quieter, and the edges you could
   not actually see are quietest. That last step is the hidden-line convention,
   and it is what stops a transparent wireframe reading as an ambiguous
   Necker cube.

     visible edges (9)          --hairline-paper      #E3DDC9   11.20:1
     nodes (7) + dimension (5)  --border-strong-paper #CFC6AC    8.94:1
     the one gold edge (1)      --gold                #E3B548    7.95:1
     hidden edges (3) + node    --muted-dark          #8C93AC    4.99:1

   Every value clears the 3:1 non-text floor. The previous --hairline (1.55:1)
   and --border-strong (2.09:1) sat below the threshold of visibility, so only
   the dots and the gold line read and the figure looked like dust. Existing
   tokens only; the light-enough line values live in the *-paper family because
   the kit has no light hairline token meant for ink ground.

   Gold is the LEAST luminous of the three bright tones and still reads as the
   accent, because it is the only chromatic mark in an otherwise neutral
   drawing. Hue carries it, not luminance. Do not "fix" that by brightening it.

   It is also the only 2px line. That is a drafting line-weight hierarchy, not
   a decoration: object lines are drawn heavier than hidden and annotation
   lines. It matters most on a phone — `vector-effect: non-scaling-stroke`
   pins every stroke in device pixels, so when the 720-unit canvas renders at
   ~320px the artwork halves and the strokes do not. The gold therefore holds
   2px against 1px everywhere else, and gets RELATIVELY more prominent as the
   figure gets smaller, which is exactly where an accent is easiest to lose.

   `:not(.fig-gold)` is load-bearing: `.hero-figure .fig-frame line` weighs
   (0,2,1) and would outrank `.hero-figure .fig-gold` at (0,2,0), silently
   repainting the one gold mark the whole image is built around. The gold rule
   is ALSO placed last in this block. Two guards, deliberately — the `:not`
   survives a reorder, the source order survives a selector rewrite. Keep both.

   EVERYTHING ADDED SINCE sits inside that same ladder rather than beside it.
   No new token, no new hue — only the values above, reused:

     the rooms, on faces you can see     --border-strong-paper  8.94:1
     the rooms, on faces you cannot      --muted-dark           4.99:1
     door + window OUTLINES              --hairline-paper      11.20:1
     sign frame + its two supports       --border-strong-paper  8.94:1
     the SOMLIS wordmark                 --hairline-paper      11.20:1
     window mullion + door push rail     --ink                  see below

   The interior deliberately drops a step below the shell's visible edges. The
   outer volume must stay the dominant object, and eight more lines at 11.20:1
   would turn it into a lattice. Dimming the four interior lines that lie on
   hidden faces (the slab's two back edges, the partition's footprint and its
   far vertical) is the same Necker guard as the shell's — without it the
   ambiguity simply returns through the middle of the box instead of around it.

   THE OPENINGS BREAK THAT PATTERN ON PURPOSE, and the reason the rooms do not
   is the reason these do. The rooms are eight lines and dimming them is what
   stops a lattice. The shopfront is TWO outlines on one face, and they are
   object lines — a hole cut in a wall you can see — not annotation, so they
   take the shell's own tone. Two strokes at 11.20:1 do not make a lattice;
   they make a shop.

   THE SIGN IS QUIETER THAN THE NAME IT CARRIES. Frame and supports 8.94:1,
   wordmark 11.20:1. A sign whose frame is as bright as its lettering reads as
   a box with text in it; a frame that recedes half a step reads as a sign.
   The wordmark takes the BRIGHTEST tone despite being the smallest thing here
   because it is the only element whose job is to be READ rather than seen,
   and it is the first thing a phone loses. Sign frame and wordmark are both
   `path`, not `line`, so both need their own `fill: none` — the O and the
   rounded frame are closed subpaths and would otherwise paint solid black.

   THE LIGHT. One accent colour, gold, in FIVE polygons at five opacities —
   three big and faint for the planes of the building, two small and bright
   for the glass. Composited over --ink they resolve to:

     .fig-pane-window  92%  ->  #D3A947   6.91:1 vs ink     lit glass
     .fig-pane-door    84%  ->  #C49E45   6.03:1 vs ink     lit glass
     .fig-solid-top    22%  ->  #4B453A   1.60:1 vs ink     roof
     .fig-solid-front  16%  ->  #3F3C39   1.39:1 vs ink     right wall
     .fig-solid-side    9%  ->  #323238   1.20:1 vs ink     shopfront wall

   THIS IS A RE-ROLE, NOT A NEW COLOUR. The gold used to be a solid block
   filling the whole ground-floor cell at 40/22. It is now the light INSIDE
   that cell, seen through the two openings: the glass carries it, and the
   three planes keep only the warmth of a lit dusk. Lights on at dusk, not a
   yellow rectangle.

   22 / 16 / 9 IS THE STANDARD AXONOMETRIC SHADING LADDER — top lightest, then
   one side, then the other — and it is also physically right at dusk, when
   the sky is the brightest thing in the scene and a horizontal surface sees
   more of it than a vertical one. The darkest plane is the shopfront wall,
   which is what lets its two openings out-glow it by 5.78:1 and 5.05:1. A
   shopfront on the lightest plane would have been the mistake.

   22% IS A CEILING, NOT A PREFERENCE. The three hidden edges cross the roof
   in --muted-dark, and they hold 3.11:1 over a 22% wash. Raise the roof and
   they drop under the 3:1 floor. That is the number to check first if anyone
   ever wants a heavier roof.

   The two BRIGHT ones clear the 3:1 floor comfortably, which the old block
   never did — so the figure's most saturated area moved from below the floor
   to well above it. The three FAINT ones sit below it, which is the point: a
   wall nearly as bright as its own windows is not a lit building. The FORM is
   still carried entirely by strokes, every one of which clears 3:1 over every
   wash (the worst case is that 3.11:1), and the figure is aria-hidden
   decoration beside a real <h1>.

   THE SPINE SURVIVES ALL OF IT, which is what set these numbers. The gold
   edge F-F' is the crease between the two wall polygons and touches neither
   pane, so the accent is never asked to sit on top of itself: 5.72:1 over the
   right wall, 6.65:1 over the shopfront wall — both far better than the
   3.26:1 it held against the old 40% block. That is why the walls DROP rather
   than rise. If they are ever raised back toward 50%, the spine dissolves
   into them and must move off #E3B548 in the same edit.

   Window brighter than door by 8 points, deliberately. A glazed door has more
   frame and less glass than a display window and reads slightly darker in
   every real shopfront; matching them makes the pair look like two stickers
   cut from one sheet. It is the same trick the three planes use — one colour,
   several values, because that is how a solid reads in axonometric.

   THE GLAZING BARS ARE THE ONLY DARK STROKES IN THE FIGURE, and they have to
   be. The window's two mullions and the door's push rail lie entirely INSIDE
   the bright panes; --border-strong-paper on the window pane is 1.29:1 and
   would disappear, where --ink holds 6.91:1 on the window and 6.03:1 on the
   door. It is also what a glazing bar looks like against a lit interior: a
   silhouette. The OUTLINES stay light because they sit on the boundary and
   must read against the dark wall outside it. Light frame, dark bars — that
   is a lit window, drawn. `.fig-shop-detail` must therefore never be given a
   light tone "for consistency", and nothing else may borrow --ink as a stroke.

   Do not add a second accent to separate any of these. */
.hero-figure line { fill: none; stroke: var(--muted-dark); stroke-width: 1; vector-effect: non-scaling-stroke; }
.hero-figure circle { fill: var(--muted-dark); }
.hero-figure .fig-hidden line { stroke: var(--muted-dark); }
.hero-figure .fig-dimension line { stroke: var(--border-strong-paper); }
.hero-figure .fig-nodes circle:not(.fig-node-hidden) { fill: var(--border-strong-paper); }
/* No `:not(.fig-gold)` on this pair, and that is checked rather than assumed.
   The guard exists because `.hero-figure .fig-frame line` (0,2,1) outranks
   `.hero-figure .fig-gold` (0,2,0) and would repaint it. `.fig-rooms line`
   cannot match the gold mark at all — that mark lives in .fig-frame, a
   different group — so there is nothing here to exclude. If a gold-classed
   element is ever added INSIDE .fig-rooms, this rule needs the `:not()`
   immediately, because source order will not save it: (0,2,1) beats (0,2,0)
   wherever it is written. The dimmed variant is (0,3,1) and wins outright. */
.hero-figure .fig-rooms line { stroke: var(--border-strong-paper); }
.hero-figure .fig-rooms line.fig-room-hidden { stroke: var(--muted-dark); }
.hero-figure .fig-word path { fill: none; stroke: var(--hairline-paper); stroke-width: 1; vector-effect: non-scaling-stroke; }
/* The shopfront. `path` for the two openings, `line` for the two bars. The
   `.fig-shop-detail` rule weighs (0,3,1) and beats the group rule's (0,2,1),
   so the bars win regardless of source order — the same two-guard habit the
   gold spine uses below. */
.hero-figure .fig-shop path { fill: none; stroke: var(--hairline-paper); stroke-width: 1; vector-effect: non-scaling-stroke; }
.hero-figure .fig-shop line.fig-shop-detail { stroke: var(--ink); }
/* The signboard: frame and supports, one tone, a step below the wordmark. */
.hero-figure .fig-sign path { fill: none; stroke: var(--border-strong-paper); stroke-width: 1; vector-effect: non-scaling-stroke; }
.hero-figure .fig-sign line { stroke: var(--border-strong-paper); }
.hero-figure .fig-solid { fill: var(--gold); stroke: none; }
.hero-figure .fig-solid-top { fill-opacity: 0.22; }
.hero-figure .fig-solid-front { fill-opacity: 0.16; }
.hero-figure .fig-solid-side { fill-opacity: 0.09; }
.hero-figure .fig-pane { fill: var(--gold); stroke: none; }
.hero-figure .fig-pane-window { fill-opacity: 0.92; }
.hero-figure .fig-pane-door { fill-opacity: 0.84; }
.hero-figure .fig-frame line:not(.fig-gold) { stroke: var(--hairline-paper); }
.hero-figure .fig-gold { stroke: var(--gold); stroke-width: 2; }

/* 10. Component sections --------------------------------------------------------- */

/* Divided panel block — capability cards, 04 §H2 / 11 §5 */
.panel-block { display: grid; grid-template-columns: repeat(2, 1fr); border: 1px solid var(--hairline); background: var(--surface-raised); }
.panel { padding: var(--sp-8); border: 1px solid var(--hairline); border-width: 0; }
.panel-block .panel:nth-child(1) { border-right-width: 1px; border-bottom-width: 1px; }
.panel-block .panel:nth-child(2) { border-bottom-width: 1px; }
.panel-block .panel:nth-child(3) { border-right-width: 1px; }
.panel h3 { font-size: 1.25rem; }
.panel p { margin-top: var(--sp-4); font-size: var(--body-s); color: var(--muted-dark); line-height: 1.65; }
.panel-list { margin-top: var(--sp-5); padding-top: var(--sp-5); border-top: 1px solid var(--hairline); display: flex; flex-direction: column; gap: var(--sp-2); }
.panel-list li { font-size: var(--body-s); padding-left: var(--sp-6); position: relative; }
.panel-list li::before { content: ''; position: absolute; left: 0; top: 0.65em; width: 12px; height: 1px; background: var(--gold); }
.panel:hover { background: var(--surface-raised-2); }

/* Sequence rows — process steps, 04 §H3 / 11 §8 */
.sequence-row { display: grid; grid-template-columns: 40px 200px 1fr; gap: var(--sp-6); padding: var(--sp-8) 0; border-top: 1px solid var(--hairline); }
.sequence-row.compact { grid-template-columns: 40px 1fr; }
.surface-paper .sequence-row { border-top-color: var(--hairline-paper); }
.sequence-row:last-child { }
.sequence-num { font-family: var(--font-mono); font-size: var(--caption); color: var(--muted-dark); font-variant-numeric: tabular-nums; }
.sequence-name { font-weight: 600; font-size: 1.0625rem; }
.sequence-desc { color: var(--muted-dark); max-width: 58ch; line-height: 1.7; }
.surface-paper .sequence-desc { color: var(--muted-light-text); }
.sequence-extra { display: flex; flex-direction: column; gap: var(--sp-2); font-size: var(--body-s); }
.sequence-extra .k { font-family: var(--font-mono); font-size: var(--caption); text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted-dark); }

/* Paired rows — "who we build for" etc, 11 §9 */
.paired-row { display: grid; grid-template-columns: 5fr 7fr; gap: var(--sp-6); padding: var(--sp-8) 0; border-top: 1px solid var(--hairline); }
.paired-row.ratio-1-1 { grid-template-columns: 1fr 1fr; }
.surface-paper .paired-row { border-top-color: var(--hairline-paper); }
.paired-row h3 { font-family: var(--font-body); font-size: var(--heading-s); font-weight: 600; line-height: 1.4; }
.paired-row p { color: var(--muted-dark); max-width: 52ch; line-height: 1.7; }
.surface-paper .paired-row p { color: var(--muted-light-text); }

/* Aside panel — 11 §6 */
.aside { background: var(--surface-raised); border: 1px solid var(--hairline); border-left: 2px solid var(--gold); padding: var(--sp-7); }
.surface-paper .aside { background: var(--paper-raised); border-color: var(--hairline-paper); border-left-color: var(--gold-ink); }
.aside-title { font-weight: 600; margin-bottom: var(--sp-3); }
.aside p:not(.aside-title) { color: var(--muted-dark); font-size: var(--body-s); }
.surface-paper .aside p:not(.aside-title) { color: var(--muted-light-text); }

/* Ladder diagram — 04 §H7 / 11 §13 */
.ladder { border-left: 1px solid var(--gold); margin-left: 2px; }
.ladder-band { position: relative; padding: var(--sp-7) 0 var(--sp-7) var(--sp-7); border-top: 1px solid var(--hairline); }
.ladder-band:first-child { border-top: none; }
.ladder-band::before { content: ''; position: absolute; left: -1px; top: var(--sp-7); width: 7px; height: 1px; background: var(--gold); }
.ladder-band h3 { font-size: 1.0625rem; font-weight: 600; }
.ladder-band p { margin-top: var(--sp-2); color: var(--muted-dark); max-width: 56ch; }

/* Two-column asymmetric — "brief is not the beginning" style sections */
.two-col { display: grid; grid-template-columns: 6fr 5fr; gap: var(--sp-8); }
.two-col.ratio-1-1 { grid-template-columns: 1fr 1fr; }
.two-col.ratio-1-2 { grid-template-columns: 1fr 2fr; }
.two-col .col-aside { padding-top: var(--sp-9); }
.two-col p + p { margin-top: var(--sp-6); }
.two-col .lead-line { font-size: var(--lede); color: var(--text); }
.surface-paper .two-col .lead-line { color: var(--ink); }

/* Form + sidebar layout — Start a Project page */
.two-col-noaside { display: grid; grid-template-columns: 7fr 5fr; gap: var(--sp-9); }

/* Name block — About page */
.name-block { border: 1px solid var(--hairline); }
.name-row { padding: var(--sp-6); border-top: 1px solid var(--hairline); }
.name-row:first-child { border-top: none; }
.name-row .letters { font-family: var(--font-display); font-weight: 700; font-size: var(--display-m); color: var(--gold); letter-spacing: 0.02em; }
.name-row .gloss { margin-top: var(--sp-2); font-size: var(--body-s); color: var(--muted-dark); }

/* Closing CTA — the only centred section, every page ends here */
.section-close { text-align: center; padding: var(--sp-17) 0; }
.close-inner h2 { font-size: var(--display-l); max-width: 18ch; margin: 0 auto; }
.close-inner .lede { margin: var(--sp-6) auto 0; text-align: center; }
.close-inner .btn { margin-top: var(--sp-8); }
.close-inner .supporting { margin-top: var(--sp-6); font-size: var(--body-s); color: var(--muted-dark); }

/* 11. Forms -------------------------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: var(--sp-7); }
.field label { font-family: var(--font-mono); font-size: var(--caption); font-weight: 500; color: var(--muted-light-text); }
.field .help { font-size: var(--caption); color: var(--muted-light-text); }
.field input[type="text"], .field input[type="email"], .field textarea {
  height: 56px; padding: 0 16px; background: transparent;
  border: 1px solid var(--border-strong-paper); border-radius: 2px; font-size: 1rem; color: var(--ink);
}
.field textarea { height: auto; min-height: 140px; padding: 14px 16px; resize: vertical; font-family: inherit; line-height: 1.6; }
.field input:focus, .field textarea:focus { border-width: 2px; border-color: var(--ink); }
.field.has-error input, .field.has-error textarea { border-color: var(--error); border-width: 2px; }
.field-error { font-size: var(--caption); color: var(--error); }

.chip-group { display: flex; flex-direction: row; flex-wrap: wrap; gap: var(--sp-2); }
.chip-group legend { flex-basis: 100%; font-family: var(--font-mono); font-size: var(--caption); font-weight: 500; color: var(--muted-light-text); margin-bottom: var(--sp-3); padding: 0; }
.chip { position: relative; }
.chip input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.chip label {
  display: inline-block; padding: 10px 16px; border: 1px solid var(--border-strong-paper);
  border-radius: 2px; font-size: var(--body-s); cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}
.chip input:checked + label { background: var(--gold-ink); color: var(--paper); border-color: var(--gold-ink); font-weight: 600; }
.chip input:focus-visible + label { outline: 2px solid var(--ink); outline-offset: 2px; }

.form-privacy { margin-top: var(--sp-4); font-size: var(--caption); color: var(--muted-light-text); }
.form-success, .form-error-banner { padding: var(--sp-7); border: 1px solid var(--hairline-paper); background: var(--paper-raised); }
.form-error-banner { border-color: var(--error); }
[hidden] { display: none !important; }

/* 12. Blog --------------------------------------------------------------------------- */
.post-list { display: flex; flex-direction: column; }
.post-card { display: grid; grid-template-columns: 160px 1fr; gap: var(--sp-6); padding: var(--sp-8) 0; border-top: 1px solid var(--hairline); text-decoration: none; color: inherit; }
.surface-paper .post-card { border-top-color: var(--hairline-paper); }
.post-card:first-child { border-top: none; }
.post-card-meta { font-family: var(--font-mono); font-size: var(--caption); color: var(--muted-dark); padding-top: 4px; }
.post-card h2 { font-size: 1.375rem; }
.post-card p { margin-top: var(--sp-3); color: var(--muted-dark); max-width: 62ch; }
.surface-paper .post-card p { color: var(--muted-light-text); }
.post-card .tags { margin-top: var(--sp-4); display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.tag { font-family: var(--font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted-dark); border: 1px solid var(--hairline); border-radius: 999px; padding: 3px 10px; }
.surface-paper .tag { color: var(--muted-light-text); border-color: var(--hairline-paper); }
.post-card:hover h2 { text-decoration: underline; text-decoration-color: var(--gold); text-underline-offset: 4px; }

.post-header { max-width: 68ch; }
.post-header .post-meta { font-family: var(--font-mono); font-size: var(--caption); color: var(--muted-dark); text-transform: uppercase; letter-spacing: 0.06em; }
.post-header h1 { font-size: var(--display-l); margin-top: var(--sp-4); line-height: 1.1; }
.post-header .post-excerpt { margin-top: var(--sp-6); font-size: var(--lede); color: var(--muted-dark); }
.surface-paper .post-header .post-excerpt { color: var(--muted-light-text); }
.post-body { max-width: 68ch; margin-top: var(--sp-11); }
.post-body h2 { font-size: 1.5rem; margin-top: var(--sp-11); margin-bottom: var(--sp-5); }
.post-body h3 { font-size: 1.1875rem; margin-top: var(--sp-9); margin-bottom: var(--sp-4); }
.post-body p { margin-top: var(--sp-5); line-height: 1.75; }
.post-body p:first-child { margin-top: 0; }
.post-body ul, .post-body ol { margin-top: var(--sp-5); padding-left: var(--sp-7); }
.post-body ul { list-style: disc; }
.post-body ol { list-style: decimal; }
.post-body li + li { margin-top: var(--sp-2); }
.post-body a { text-decoration: underline; text-decoration-color: var(--gold); text-underline-offset: 3px; }
.surface-paper .post-body a { text-decoration-color: var(--gold-ink); }
.post-body blockquote { border-left: 2px solid var(--gold); padding-left: var(--sp-6); margin-top: var(--sp-6); color: var(--muted-dark); }
.post-body code { font-family: var(--font-mono); font-size: 0.875em; background: var(--surface-raised); padding: 2px 6px; border-radius: 2px; }
.surface-paper .post-body code { background: var(--paper-raised); }
.post-body pre { background: var(--surface-raised); padding: var(--sp-6); overflow-x: auto; margin-top: var(--sp-6); border-radius: 2px; }
.post-body pre code { background: none; padding: 0; }
.post-body hr { border: 0; border-top: 1px solid var(--hairline); margin: var(--sp-9) 0; }
.post-body table { width: 100%; margin-top: var(--sp-7); border-collapse: collapse; font-size: var(--body-s); display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.post-body th, .post-body td { text-align: left; padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--hairline); vertical-align: top; }
.post-body th { font-family: var(--font-mono); font-size: var(--caption); text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted-dark); font-weight: 500; border-bottom: 1px solid var(--border-strong); }
.post-body td:first-child, .post-body th:first-child { padding-left: 0; }
.post-body td:last-child, .post-body th:last-child { padding-right: 0; }
.surface-paper .post-body th, .surface-paper .post-body td { border-bottom-color: var(--hairline-paper); }
.surface-paper .post-body th { color: var(--muted-light-text); border-bottom-color: var(--border-strong-paper); }
.post-body img { margin-top: var(--sp-6); border-radius: 2px; }
.post-footer { max-width: 68ch; margin-top: var(--sp-11); padding-top: var(--sp-7); border-top: 1px solid var(--hairline); }

/* 13. Work / build cards --------------------------------------------------------------- */
.build-row { display: grid; grid-template-columns: 5fr 7fr; gap: var(--sp-8); padding: var(--sp-9) 0; border-top: 1px solid var(--hairline-paper); align-items: start; }
.build-row.no-image { grid-template-columns: 7fr 5fr; }
.build-row:first-child { border-top: none; }
.build-image { aspect-ratio: 16/10; background: var(--paper-raised); border: 1px solid var(--hairline-paper); overflow: hidden; }
.build-image img { width: 100%; height: 100%; object-fit: cover; }
.build-content h3 { font-size: 1.25rem; }
.build-descriptor { margin-top: var(--sp-2); font-size: var(--body-s); color: var(--muted-light-text); }
.build-desc { margin-top: var(--sp-5); color: var(--muted-light-text); max-width: 46ch; }
.build-tags { margin-top: var(--sp-6); padding-top: var(--sp-6); border-top: 1px solid var(--hairline-paper); display: flex; gap: var(--sp-2); flex-wrap: wrap; }

.grid-cards { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-8); }
.build-card { border: 1px solid var(--hairline-paper); background: var(--paper-raised); }
.build-card .build-image { border: 0; border-bottom: 1px solid var(--hairline-paper); }
.build-card .build-card-body { padding: var(--sp-7); }

/* 14. Hero figure motion + reduced-motion policy -------------------------------------- */
/* THE SEQUENCE, IN FOUR ACTS. 5150ms end to end, after site.js's own 200ms
   pre-roll: 5350ms total, played once per session.

     ACT 1  STRUCTURE   0 -> 2060ms
            Eight nodes fade in where they were scattered, hold for a beat,
            travel to the eight corners of one axonometric volume. Twelve
            edges grow OUT of those corners, gold spine first. A dimension
            line measures the finished object, closing in from both arrows.
            . . . . . . . . . . . . . . . . . . . . . 180ms hold
     ACT 2  ROOMS       2240 -> 3060ms
            A floor slab grows out of the midpoint of the gold spine, then one
            partition wall draws itself up, back and across. The box stops
            being an empty shell: three cells, an upper storey over a ground
            floor split in two.
            . . . . . . . . . . . . . . . . . . . . . 180ms hold
     ACT 3  IT BECOMES SOLID, AND A SHOPFRONT IS CUT INTO IT   3240 -> 3900ms
            The three visible planes take tone — roof first, then the right
            wall, then the shopfront wall — and the wireframe becomes a
            building. Only then are the openings cut into it: the door, the
            display window, and last the push rail and the two glazing bars.
            . . . . . . . . . . . . . . . . . . . . . 140ms hold
     ACT 4  THE NAME GOES UP, THE LIGHTS COME ON   4040 -> 5150ms
            Two supports rise off the roof, the signboard unrolls across them
            from the left support, SOMLIS writes itself inside it letter by
            letter — and as the last letter lands, the display window and then
            the door warm to gold. The shop is open.

   ORDER INSIDE ACT 3 IS THE ARGUMENT, not a preference. The faces take tone
   BEFORE the openings are drawn, because you cannot cut a hole in a
   wireframe. Reversing those two beats gives a door hanging in empty space
   with a wall arriving behind it afterwards.

   THE HOLDS ARE THE STRUCTURE. Three gaps where nothing at all moves — 180,
   180, 140 — are what make this read as four acts rather than one long smear.
   The earlier sequence was judged too fast, and the fix is NOT a uniform
   slowdown: stretching every delay by the same factor lengthens the dead air
   inside an act and leaves the joins between acts just as blurred. Each act
   instead completes, stops, and is followed by silence. Preserve the three
   gaps before you preserve any individual number in here.

   THE LAST HOLD IS SHORTER (140) on purpose. Four acts inside a budget that
   has to stay under 5.5s cannot afford three equal silences, and the gap
   before the payoff is the one that can be tightened without losing the join
   — a held breath before the lights, rather than another full stop.

   WITHIN an act, things still overlap: the gold spine starts drawing while
   the last two dots are still travelling, the window is cut while the walls
   are still arriving, and the panes warm while the last letter is still being
   written. Overlap inside a beat is momentum; overlap between beats is mush.

   The point of the whole thing: the dots BECOME the structure, the structure
   BECOMES a building, and the building BECOMES a shop that is open. Nothing
   here fades in beside anything else. Every line's first coordinate pair in
   hero-figure.mjs is the point it draws away from — the shell edges radiate
   from the hub corners F and B, the slab from the two spine midpoints, the
   partition from the shell edge it stands on, the door up out of the
   threshold it sits in, the supports up off the roof, the signboard out from
   the left support. The five fill polygons are the single exception, because
   a filled area cannot be drawn by dashoffset; they fade.

   THE FIGURE IS VISIBLE BY DEFAULT. The two staging rules below are the
   hidden state — nodes invisible and displaced, strokes retracted — and they
   apply ONLY once site.js has added `is-staged`, which it does on the single
   path that is about to animate. They used to be base rules, which meant
   every path on which site.js never added `is-animating` (a second page view
   in the same session, JS blocked or failed, the window before the deferred
   script runs) rendered an empty hero. Do not move them back out to the base
   state.

   `:where()` contributes zero specificity, so both selectors still weigh
   exactly (0,1,1) as they did unscoped — the reduced-motion overrides at the
   foot of this section keep winning on source order, unchanged. Writing them
   as a plain `.hero-figure.is-staged ...` would weigh (0,3,1), outrank those
   overrides, and silently leave every reduced-motion visitor with an empty
   hero. Keep the `:where()`. */

/* Scatter origins and the fade-in stagger, one rule per node, addressed by
   document order in .fig-nodes. Reordering that group re-scatters AND re-times
   the figure. These sit in the BASE state, not inside :where(.is-staged),
   because the @keyframes below read them too — they are inert whenever no
   transform is applied. Offsets are 86-105 user units; every resulting start
   position was checked to sit inside the 720x480 canvas. The delays are
   shuffled rather than sequential so the points read as hand-placed. */
.hero-figure .fig-nodes circle:nth-child(1) { --fx: -78px; --fy:  58px; --fd:   0ms; }
.hero-figure .fig-nodes circle:nth-child(2) { --fx: -48px; --fy:  88px; --fd:  70ms; }
.hero-figure .fig-nodes circle:nth-child(3) { --fx:  72px; --fy:  76px; --fd:  40ms; }
.hero-figure .fig-nodes circle:nth-child(4) { --fx:  34px; --fy:  96px; --fd: 140ms; }
.hero-figure .fig-nodes circle:nth-child(5) { --fx: -94px; --fy: -38px; --fd: 100ms; }
.hero-figure .fig-nodes circle:nth-child(6) { --fx: -62px; --fy:  74px; --fd: 170ms; }
.hero-figure .fig-nodes circle:nth-child(7) { --fx:  58px; --fy: -64px; --fd: 115ms; }
.hero-figure .fig-nodes circle:nth-child(8) { --fx:  86px; --fy:  52px; --fd: 195ms; }

/* The hidden state, for every element type the figure now contains. Strokes
   retract, dots go invisible and displaced, and the five fill polygons go to
   opacity 0 — NOT to fill-opacity 0, because fill-opacity is where each one's
   final tone lives (22/16/9 for the walls, 92/84 for the glass) and animating
   it would mean restating those five numbers in a keyframe. `opacity`
   multiplies with `fill-opacity`, so a plain 0 -> 1 fade leaves every tone
   stated exactly once, in §9.

   Every selector here weighs (0,1,1) — or (0,1,0) for .fig-solid and
   .fig-pane — exactly as the original two did, and the reduced-motion
   overrides at the foot of this section match them and win on source order.
   Adding a group is safe. Adding a group and writing its staging rule as
   `.hero-figure.is-staged .fig-x` (0,3,1) is not: it would outrank the
   override and leave reduced-motion visitors looking at a figure with a hole
   in it. .fig-shop and .fig-sign each contain BOTH paths and lines, so both
   element types are listed for each — a `.fig-sign line` alone would leave
   the signboard frame permanently retracted, which is the whole wordmark. */
:where(.hero-figure.is-staged) .fig-nodes circle {
  opacity: 0;
  transform: translate(var(--fx, 0px), var(--fy, 0px)) scale(0.35);
  transform-origin: center; transform-box: fill-box;
}
:where(.hero-figure.is-staged) .fig-hidden line,
:where(.hero-figure.is-staged) .fig-frame line,
:where(.hero-figure.is-staged) .fig-rooms line,
:where(.hero-figure.is-staged) .fig-dimension line,
:where(.hero-figure.is-staged) .fig-shop path,
:where(.hero-figure.is-staged) .fig-shop line,
:where(.hero-figure.is-staged) .fig-sign path,
:where(.hero-figure.is-staged) .fig-sign line,
:where(.hero-figure.is-staged) .fig-word path {
  stroke-dasharray: 1; stroke-dashoffset: 1;
}
:where(.hero-figure.is-staged) .fig-solid { opacity: 0; }
:where(.hero-figure.is-staged) .fig-pane { opacity: 0; }

/* animation-delay is a separate LONGHAND after the shorthand, not folded into
   it. A var() anywhere inside a shorthand makes the whole declaration
   pending-substitution, so one bad custom property would invalidate
   animation-name too and leave the node stuck at opacity 0 — invisible, on the
   one path that is supposed to be showing it. As a longhand, a bad --fd costs
   only the delay. */
/* ---- ACT 1: STRUCTURE, 0 -> 2060ms ------------------------------------- */
.hero-figure.is-animating .fig-nodes circle { animation: figNode 880ms var(--ease-out) forwards; animation-delay: var(--fd, 0ms); }

/* Edges. Base beat is 1020ms (the verticals); later ranges override by
   :nth-child, each weighing (0,4,1) and resolving on source order. */
.hero-figure.is-animating .fig-frame line,
.hero-figure.is-animating .fig-hidden line { animation: figDraw 320ms var(--ease-standard) forwards; animation-delay: 1020ms; }
.hero-figure.is-animating .fig-frame line:nth-child(n+4) { animation-delay: 1080ms; }  /* bottom edges */
.hero-figure.is-animating .fig-frame line:nth-child(n+6) { animation-delay: 1140ms; }  /* top, front pair */
.hero-figure.is-animating .fig-frame line:nth-child(n+8) { animation-delay: 1200ms; }  /* top, back pair */
.hero-figure.is-animating .fig-hidden line:nth-child(n+2) { animation-delay: 1080ms; } /* hidden bottom pair */

/* The gold spine leads — it is "the first ordered thing", so it draws before
   any other edge, and it starts at 950ms while the last two dots (delays 170
   and 195) are still in flight. Same two guards as the colour ladder in §9:
   it is .fig-frame's FIRST child (so no :nth-child(n+N) range above can match
   it) AND this rule is written last (so it wins the (0,4,1) tie regardless). */
.hero-figure.is-animating .fig-frame line.fig-gold { animation-duration: 300ms; animation-delay: 950ms; }

/* The measurement closes the act: witness lines drop, then the two halves of
   the span run inward from their arrows and MEET at x=360. The ticks snap on
   last.

   Six children: witness, witness, half-span, half-span, tick, tick. The two
   half-spans used to stop short of centre, leaving a gap for the wordmark;
   now that the name lives on the signboard they meet, and the measurement is
   one unbroken line in the end state. The GESTURE is unchanged and so is the
   child count, which is the only reason none of the :nth-child delays below
   had to move. Re-time, do not patch, if this group changes again. */
.hero-figure.is-animating .fig-dimension line { animation: figDraw 260ms var(--ease-standard) forwards; animation-delay: 1620ms; }
.hero-figure.is-animating .fig-dimension line:nth-child(n+3) { animation-duration: 290ms; animation-delay: 1730ms; }
.hero-figure.is-animating .fig-dimension line:nth-child(n+5) { animation-duration: 160ms; animation-delay: 1900ms; }

/* ---- ACT 2: ROOMS, 2240 -> 3060ms -------------------------------------- */
/* The slab first, both front edges growing out of the gold spine's midpoint,
   then its two back edges out of the hidden spine. Then the partition draws
   as one continuous gesture — up the front vertical, back along its top, out
   along its footprint, up its far vertical — which is why its four delays
   step rather than fire together. Same (0,4,1) nth-child cascade as above. */
.hero-figure.is-animating .fig-rooms line { animation: figDraw 340ms var(--ease-standard) forwards; animation-delay: 2240ms; }
.hero-figure.is-animating .fig-rooms line:nth-child(n+3) { animation-delay: 2340ms; }                          /* slab, back edges */
.hero-figure.is-animating .fig-rooms line:nth-child(n+5) { animation-duration: 280ms; animation-delay: 2640ms; } /* partition, front vertical */
.hero-figure.is-animating .fig-rooms line:nth-child(n+6) { animation-duration: 300ms; animation-delay: 2700ms; } /* partition, top edge */
.hero-figure.is-animating .fig-rooms line:nth-child(n+7) { animation-duration: 300ms; animation-delay: 2760ms; } /* partition, footprint + far vertical */

/* ---- ACT 3: IT BECOMES SOLID + SHOPFRONT, 3240 -> 3900ms --------------- */
/* The three planes take tone 50ms apart, roof -> right wall -> shopfront
   wall, so the volume turns solid AROUND its near corner rather than
   switching on flat. Addressed by CLASS, not :nth-child, so the order of
   .fig-fill can change without re-timing: only .fig-solid-top takes the base
   delay. var(--ease-out) because this one thing should decelerate into place;
   every stroke in the figure uses --ease-standard and should keep doing so. */
.hero-figure.is-animating .fig-solid { animation: figFill 440ms var(--ease-out) forwards; animation-delay: 3240ms; }
.hero-figure.is-animating .fig-solid.fig-solid-front { animation-delay: 3290ms; }
.hero-figure.is-animating .fig-solid.fig-solid-side { animation-delay: 3340ms; }

/* Then the openings are cut, 200ms after the roof starts arriving — the walls
   are still fading up as the door is cut into them, which is the one overlap
   in this act and is what stops it reading as two separate events.

   Five children: door path, window path, push rail, mullion, mullion. The two
   PATHS are the openings and the three LINES are their subdivisions, so the
   element type does the grouping and :nth-child only has to stagger within
   it. `path:nth-child(2)` is the window; `line` alone catches child 3 (the
   rail) at the base delay, and 4 and 5 override it. Reordering this group
   re-times it — the rail must stay adjacent to the door it belongs to. */
.hero-figure.is-animating .fig-shop path { animation: figDraw 280ms var(--ease-standard) forwards; animation-delay: 3440ms; }
.hero-figure.is-animating .fig-shop path:nth-child(2) { animation-delay: 3510ms; }   /* the display window */
.hero-figure.is-animating .fig-shop line { animation: figDraw 200ms var(--ease-standard) forwards; animation-delay: 3620ms; }
.hero-figure.is-animating .fig-shop line:nth-child(4) { animation-delay: 3660ms; }   /* mullion, b=0.5 */
.hero-figure.is-animating .fig-shop line:nth-child(5) { animation-delay: 3700ms; }   /* mullion, b=0.7 */

/* ---- ACT 4: THE NAME AND THE LIGHT, 4040 -> 5150ms --------------------- */
/* The supports rise first, 50ms apart, drawing UPWARD off the roof — their
   first coordinate pair in hero-figure.mjs is the roof end, so they grow out
   of the building rather than descending onto it. Then the frame unrolls from
   the point where the LEFT support meets it (the path's M is at 236,96), all
   the way round and back. Nothing is holding the sign up until the supports
   are there, and the eye reads it in that order. */
.hero-figure.is-animating .fig-sign line { animation: figDraw 200ms var(--ease-standard) forwards; animation-delay: 4040ms; }
.hero-figure.is-animating .fig-sign line:nth-child(2) { animation-delay: 4090ms; }
.hero-figure.is-animating .fig-sign path { animation: figDraw 380ms var(--ease-standard) forwards; animation-delay: 4170ms; }

/* SOMLIS, one letter per path, 54ms apart against a 380ms draw — so each
   letter is barely a seventh written when the next starts and the word reads
   as one continuous hand rather than six separate strokes. It starts at
   4390ms, 220ms after the frame begins: the sign is still drawing itself when
   the name begins to appear inside it. */
.hero-figure.is-animating .fig-word path { animation: figDraw 380ms var(--ease-standard) forwards; animation-delay: 4390ms; }
.hero-figure.is-animating .fig-word path:nth-child(2) { animation-delay: 4444ms; }
.hero-figure.is-animating .fig-word path:nth-child(3) { animation-delay: 4498ms; }
.hero-figure.is-animating .fig-word path:nth-child(4) { animation-delay: 4552ms; }
.hero-figure.is-animating .fig-word path:nth-child(5) { animation-delay: 4606ms; }
.hero-figure.is-animating .fig-word path:nth-child(6) { animation-delay: 4660ms; }

/* THE LIGHT IS THE LAST THING, and it is ONE beat, not a loop. The window
   warms at 4700ms and the door 50ms behind it, so the light reads as spreading
   along the frontage from the big pane to the small one rather than as two
   switches thrown together. 400ms on var(--ease-out) — slow enough to be a
   warming rather than a flick, short enough not to become an effect.

   Deliberately NOT a perpetual loop. A looping glow would run on every frame
   for as long as the tab is open, costs battery and CPU on a phone for a
   decoration, and competes with the <h1> beside it forever. Once, at the end,
   is the whole idea: the shop opens. It does not open repeatedly. */
.hero-figure.is-animating .fig-pane { animation: figFill 400ms var(--ease-out) forwards; animation-delay: 4700ms; }
.hero-figure.is-animating .fig-pane.fig-pane-door { animation-delay: 4750ms; }

@keyframes figDraw { to { stroke-dashoffset: 0; } }
@keyframes figFill { from { opacity: 0; } to { opacity: 1; } }

/* Appear (0-28%), hold (28-42%), travel (42-100%). The hold is what separates
   "the dots appeared" from "the dots moved" — without it the two phases blur
   into one drift. translate() stays at the scattered offset for the first two
   stops so only opacity and scale change there. */
@keyframes figNode {
  0%   { opacity: 0; transform: translate(var(--fx, 0px), var(--fy, 0px)) scale(0.35); }
  28%  { opacity: 1; transform: translate(var(--fx, 0px), var(--fy, 0px)) scale(1); }
  42%  { opacity: 1; transform: translate(var(--fx, 0px), var(--fy, 0px)) scale(1); }
  100% { opacity: 1; transform: translate(0px, 0px) scale(1); }
}

/* EVERY element the figure contains must be reset here, not just the ones
   that existed when this block was written. A group added above and forgotten
   here stays staged forever for a reduced-motion visitor — invisible, on the
   one path that is supposed to show everything immediately. The list below is
   deliberately exhaustive rather than wildcarded: `.hero-figure *` would also
   hit the <g> wrappers and would hide the fact that .fig-solid needs a
   different property (opacity) from the stroked groups (stroke-dashoffset).

   Specificity: each of these weighs (0,1,1) — (0,1,0) for .fig-solid and
   .fig-pane — exactly matching its :where()-wrapped staging rule above, and
   wins on source order alone. That equality is the whole reason the staging
   rules are wrapped in :where().

   The list must MIRROR the staging block line for line. .fig-shop and
   .fig-sign each hold both paths and lines; miss either and a reduced-motion
   visitor gets a door with no frame, or a signboard with no sign. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .fig-nodes circle { opacity: 1; transform: none; }
  .fig-hidden line, .fig-frame line, .fig-rooms line, .fig-dimension line, .fig-word path { stroke-dashoffset: 0; }
  .fig-shop path, .fig-shop line, .fig-sign path, .fig-sign line { stroke-dashoffset: 0; }
  .fig-solid { opacity: 1; }
  .fig-pane { opacity: 1; }
  .hero-figure.is-animating * { animation: none !important; }
}

/* 15. Responsive ------------------------------------------------------------------------ */
@media (max-width: 1023px) {
  body { padding-bottom: 60px; } /* clearance for the fixed mobile quickbar so it never overlaps the footer or last section */
  .nav-links, .nav-cta, .nav-whatsapp { display: none; }
  .mobile-quickbar { display: flex; }
  .nav-panel {
    position: fixed; inset: 64px 0 0 0; z-index: 90;
    background: var(--ink); padding: var(--sp-8) var(--gutter-x);
    display: flex; flex-direction: column; gap: var(--sp-9); overflow-y: auto;
  }
  .surface-paper .nav-panel { background: var(--paper); }
  .nav-panel[hidden] { display: none; }
  .nav-panel nav { display: flex; flex-direction: column; gap: var(--sp-6); }
  .nav-panel nav a { font-family: var(--font-display); font-size: 1.75rem; font-weight: 700; text-decoration: none; color: inherit; }
  .nav-panel .btn { width: 100%; }
  .nav-panel-meta { display: flex; flex-direction: column; gap: var(--sp-2); font-size: var(--body-s); color: var(--muted-dark); }
  .nav-panel-meta a { color: inherit; text-decoration: none; }

  .hero { grid-template-columns: 1fr; min-height: auto; padding-top: var(--sp-16); }
  .hero-text h1 { max-width: none; }
  .hero-figure-wrap { margin-top: var(--sp-8); }

  .panel-block { grid-template-columns: 1fr; }
  .panel-block .panel { border-right-width: 0 !important; border-bottom-width: 1px !important; }
  .panel-block .panel:last-child { border-bottom-width: 0 !important; }

  /* !important is deliberate here, not a specificity shortcut taken
     casually: this rule's whole job is to be the one guarantee that no
     column layout on this site — including any future modifier class
     added alongside .two-col/.sequence-row/.paired-row/.build-row, which
     by definition has higher normal specificity than this single-class
     selector — can cause horizontal overflow on a narrow viewport. An
     inline style already broke this once (see IMPLEMENTATION-NOTES.md
     "Mobile responsive audit"); this is the fix that makes that class of
     bug structurally impossible to reintroduce by accident. */
  .two-col, .two-col-noaside, .sequence-row, .paired-row, .build-row { grid-template-columns: 1fr !important; }
  .two-col .col-aside { padding-top: 0; }
  .sequence-row { row-gap: var(--sp-3); }
  .grid-cards { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr; gap: var(--sp-9); }
}

@media (max-width: 767px) {
  .hero-text h1 { font-size: 2.5rem; }
  .hero-actions .btn { width: 100%; }
  .hero-actions { flex-direction: column; }
  .post-card { grid-template-columns: 1fr; }
}

@media (max-width: 480px) {
  .close-inner .btn { width: 100%; }
}

/* 16. Print --------------------------------------------------------------------------- */
@media print {
  .site-nav, .nav-panel, .site-footer .btn { display: none; }
  body { background: #fff; color: #000; }
  .hero-figure { display: none; }
  a { color: #000; text-decoration: underline; }
}
