@charset "UTF-8";

/* ==========================================================================
   00 FONTS — self-hosted, so this site makes ZERO third-party requests.
   The previous build loaded Newsreader + Archivo from fonts.googleapis.com,
   which was its only external dependency (ADR 0004). These are the same
   open-licensed files (SIL OFL 1.1), served from this repo instead: one less
   DNS lookup and TLS handshake on the critical path, nothing to break if a
   third party is blocked or slow, and no request describing the visitor to
   anyone else. See fonts/README.md for how to update them.

   Both are VARIABLE fonts, so the whole weight range is one file. Only the
   latin subset (U+0000-00FF and friends) is shipped: the site is in English,
   and any character outside that range falls back to the system stack, which
   is correct behaviour rather than a bug.
   ========================================================================== */

@font-face {
  font-family: 'Fraunces';
  src: url('fonts/fraunces-latin.woff2') format('woff2');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;   /* the fallback renders immediately; nothing is invisible while a font loads */
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo';
  src: url('fonts/archivo-latin.woff2') format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* ==========================================================================
   AURCO Interiors — styles.css
   The single stylesheet for the whole site. No framework, no build step,
   no third-party CSS, no CDN. Edit this file directly.

   ————————————————————————————————————————————————————————————————————————
   THE IDEA — "IT DEVELOPS."
   ————————————————————————————————————————————————————————————————————————
   Look at media/projects/project-01.jpg. A carved arched panel that came out
   of a family in India hangs on an Ontario living-room wall. Beside it, three
   BLACK-AND-WHITE family photographs in black frames. On the table below, a
   copper tray. That one wall is the whole business: the past arrives with no
   colour on it, the room it lands in is in full colour, and something metal
   carries you from one to the other.

   So this site is a DARKROOM, not a showroom. Everything arrives out of
   monochrome and takes colour. The structural unit is an APERTURE — a window
   frame — because every building tradition on earth has one: the jali screen,
   the mashrabiya, the shoji, the celosia, the claustra, and the Suzhou garden's
   louchuang, the "leaking window", which exists precisely to frame a borrowed
   view. That makes an aperture the one shape that can hold "bring me your
   heritage, whatever it is" without borrowing any single culture's motif.

   ONE motif carries the identity and it is the FOUNDER'S OWN. Every other
   tradition appears on this site as CONTENT — a client's object, a photograph,
   a caption — and never as chrome, a border, a divider or a hover state.

   A NOTE ON THE NAME OF THAT OBJECT: earlier documents in this repo call the
   carved panel in project-01.jpg a jharokha. That is probably wrong. A
   jharokha is a projecting upper-storey oriel BALCONY — brackets, pillars,
   balustrade, often a cupola — whose opening is filled with a jali. A flat
   carved arched panel hung on a living-room wall is a jali panel set in an
   arch. So the site says "a carved arched panel", which is what it
   demonstrably is, and does not assert a term nobody here can verify.

   Written up in docs/adr/0006_gold_black_aperture.md. Read it before changing
   anything visual, and read the two LAWS below before adding any colour or any
   motion. They are binding.

   ————————————————————————————————————————————————————————————————————————
   THE PALETTE LAW — read this before adding any colour
   ————————————————————————————————————————————————————————————————————————
   1. BLACK IS THE GROUND, and the reason is measured rather than fashionable.
      Every warm colour in this studio's own photographs is legible as type
      ONLY on black:
          saffron   #e8a33d   9.12:1 on --obs  ·  2.02:1 on --paper  (fails)
          turquoise #37c2ca   9.12:1           ·  2.02:1             (fails)
          ochre     #c9922f   7.15:1           ·  2.57:1             (fails)
          gilt      #c9a961   8.74:1           ·  2.10:1             (fails)
      On black no accent needs an AA-safe twin. That is the whole reason for
      the choice: it deletes a category of workaround and lets the client's
      colours appear as they actually are.
   2. THREE GOLDS, THREE JOBS, NEVER SWAPPED.
          --gilt   #c9a961   light, hairlines, rules, focus rings, small fills
          --champ  #e2d0a9   TYPE on black                        12.96:1
          --bronze #6a5a36   TYPE on --paper — the only gold on light  6.28:1
      All three are hue 41.5 deg, saturation ~49%: --champ and --bronze are
      tints of --gilt, not separately chosen hexes. --gilt may set type on black
      (8.74:1) but --champ is the house value for running text because it also
      holds up over a photograph.
      (These numbers are checked against the code. #d4af37 / #e6cfa1 / #6f5a12
      were the first pass and appear nowhere in this file — if you see them
      quoted anywhere, that text is stale.)
   3. GOLD IS NEVER A SLAB. It is a hairline plus a wash of at most 24%. A
      large flat #d4af37 fill reads mustard — this was rendered, reviewed and
      rejected. There is no gradient "metallic" text anywhere on this site: it
      is the clearest cheap-luxury tell, and its contrast varies across the
      glyph, so it cannot be measured.
   4. ONE LIGHT BAND MEANS ONE THING: PAPER. The site goes light only where the
      subject is literally a document — the mood board, the space plan, the
      shopping list, the price sheet. Not for rhythm, not for relief.
   5. THE PHOTOGRAPHED COLOURS APPEAR TOGETHER EXACTLY ONCE, in #colour, which
      OPENS the home page and carries its h1: "heritage is not beige" is the
      literal subject, and the page argues outward from it. Everywhere else:
      one accent per section, or none.
   6. ABUT, NEVER BLEND. No gradient between two accents anywhere.
   7. --hair is a 1px boundary line. It is never a background for text.

   ————————————————————————————————————————————————————————————————————————
   THE MOTION LAW
   ————————————————————————————————————————————————————————————————————————
   There is ONE motion idea: IT DEVELOPS. Things arrive the way a photograph
   arrives in a darkroom tray — out of monochrome and into colour, out of the
   dark and into light. Nothing on this site fades in for no reason.

   The four named gestures, and nothing else:
     · THE APERTURE OPENS — a clip-path wipe up the inside of a window frame.
       Once per page, on the one image that is the page's subject.
     · IT DEVELOPS — a grayscale filter animated on a CONTAINER, never per
       tile. Measured on a throttled phone, 12 tiles, 120 sampled frames:
           per tile        p99 21.7ms   max 24.7ms   2 frames over 20ms
           one container   p99 17.4ms   max 17.7ms   0 frames over 20ms
       Per-object colour is an INTERACTION (hover / focus / tap), never a
       scroll effect, so it never competes with the scroll for frames.
     · THE LIFT — 8px of travel and nothing else. Three elements per page, max.

   Everything is transform / opacity / filter / clip-path, so it composites.

   MOTION FAILS OPEN, ALWAYS. The base state of every animated thing is its
   FINISHED state. A monochrome start is applied only under
   `html:not(.no-js)` — that is, only where the JavaScript that can undo it is
   present. A browser with no scroll-driven animations, no JavaScript, or
   `prefers-reduced-motion: reduce` gets a complete page, in full colour.
   (The first draft of this stylesheet had that backwards and would have
   shipped a permanently grey photo wall to Firefox. See ADR 0006.)

   ————————————————————————————————————————————————————————————————————————
   A NOTE ON CSS MATH — this has already bitten this file once
   ————————————————————————————————————————————————————————————————————————
   Inside clamp() / calc() / min() / max(), the + and - operators REQUIRE
   whitespace on both sides. `clamp(2.8rem,1.2rem+7vw,6.6rem)` is a parse error
   and the entire declaration is silently dropped — the type just comes out at
   the inherited size and nothing tells you why. Always write
   `clamp(2.8rem, 1.2rem + 7vw, 6.6rem)`.

   ————————————————————————————————————————————————————————————————————————
   CONTENTS
     01 Tokens                        13 The object roll
     02 Reset and base                14 The colour room
     03 Typography                    15 Price plates
     04 Layout                        16 Process and deliverables
     05 The aperture                  17 Before / after
     06 Reveal, develop, gild         18 Film and project slots
     07 Header and navigation         19 Tip of the week
     08 Buttons and links             20 Newsletter and forms
     09 Scroll progress               21 The name
     10 Hero and page mast            22 Footer
     11 The paper band                23 Utilities
     12 The developing wall           24 No-JS, reduced motion, print
   ========================================================================== */


/* ==========================================================================
   01 TOKENS
   Values marked "sampled" were read off the studio's own photographs with a
   canvas colour sampler. Ratios are computed sRGB relative-luminance contrast
   values, not estimates.
   ========================================================================== */

:root {
  /* ---- Grounds. Black, three steps, and none of them is #000. ---- */
  --obs:        #0b0b0c;  /* the page */
  --obs-lift:   #141317;  /* a card on the page */
  --obs-card:   #17161a;  /* a card on a card */
  --paper:      #faf7f1;  /* the ONE light ground: paper, i.e. a document */
  --bone:       #f1ebdf;  /* a panel on paper */

  /* ---- Gold. ONE hue, three tints, three jobs. Palette law rule 2.
       All three are hue 41.5 deg and saturation 49% — one metal, not three
       colours that happen to be goldish. #d4af37 (the most-published "gold"
       hex) was measured at saturation 64.6% and hue 45.9 deg and rejected:
       above ~55% saturation in this lightness band gold stops reading as
       metal and starts reading as yellow paint. ---- */
  --gilt:   #c9a961;  /* THE gold. Light, rules, hairlines, marks, focus.
                         8.74:1 on --obs · hue 41.5 · sat 49.1 */
  --champ:  #e2d0a9;  /* = gilt mixed 50% into --paper. TYPE on black.
                         12.96:1 on --obs · hue 41.1 · sat 49.6 */
  --bronze: #6a5a36;  /* = gilt mixed 50% into --obs. TYPE on --paper — the
                         only gold that can. 6.28:1 on --paper · hue 41.5 */

  /* ---- Ink ---- */
  --on-dark:       #faf7f1;  /* 18.40:1 on --obs */
  --on-dark-soft:  #b9b2a6;  /*  9.35:1 */
  --on-dark-dim:   #8a8478;  /*  5.30:1 — the floor. Nothing dimmer sets type. */
  --on-light:      #0b0b0c;  /* 18.40:1 on --paper */
  --on-light-soft: #4a4550;  /*  8.70:1 */
  --on-light-dim:  #5c5661;  /*  6.64:1 on --paper, 5.98 on --bone, and 4.90 on
                                  the tinted .plate--free ground. The floor on
                                  light. It was #6e6873, which passes on plain
                                  paper (5.05:1) but drops to 3.72:1 on the free
                                  plate's 8% bronze wash — measured, and the
                                  reason this value is darker than it looks like
                                  it needs to be. Note that color-mix() in oklab
                                  darkens a light ground more than an sRGB mix
                                  of the same percentage would. */

  /* ---- The photographed colours. Legible on black; never used on --paper. ---- */
  --saffron:   #e8a33d;  /* sampled: the saffron book cover, hero-02.  9.12:1 */
  --turquoise: #37c2ca;  /* sampled: the turquoise enamel plate.       9.12:1 */
  --crabapple: #e8578c;  /* sampled: the crabapple blossom.            5.78:1 */
  --ochre:     #c9922f;  /* sampled: the papyrus ground, testimonial-01. 7.15:1 */
  --leaf:      #7fa267;  /* sampled: the pothos, project-05.           6.81:1 */
  --lapis:     #22376b;  /* sampled: the navy sectional. FILL ONLY — 1.71:1. */

  /* ---- Lines. THE DEFAULT DIVIDER IS NEUTRAL, NOT GOLD.
       Gold on every divider, every card border and every heading underline is
       the "gold tax": applied everywhere it stops reading as a material and
       starts reading as a finish sprayed over a template. Gold rules are
       SPENT, a few times per page, on: the aperture frame, the price grid, the
       free plate, the scroll progress, focus rings, the wordmark tagline, the
       arrow links, and every dashed placeholder. Everything else gets --hair. ---- */
  --hair:       rgba(250, 247, 241, 0.13);  /* the neutral hairline, on dark */
  --hair-soft:  rgba(250, 247, 241, 0.08);
  --hair-gold:  color-mix(in oklab, var(--gilt) 30%, transparent);
  --hair-light:      rgba(11, 11, 12, 0.14);  /* the neutral hairline, on paper */
  --hair-light-gold: color-mix(in oklab, var(--bronze) 30%, transparent);

  /* ---- Semantic roles. These are the ONLY things a rule references for text
       colour. The LIGHT GROUNDS block below swaps their values, so no rule
       ever needs an ".on-light" twin — which is how a stylesheet ends up with
       a black heading on a black ground. ---- */
  --heading:    var(--on-dark);
  --body:       var(--on-dark-soft);
  --quiet:      var(--on-dark-dim);
  --rule:       var(--hair);       /* the neutral divider */
  --rule-gold:  var(--hair-gold);  /* spend this sparingly — see --hair above */
  --accent:     var(--champ);   /* the gold that may set type on this ground */
  --accent-lit: var(--gilt);    /* the gold that is light on this ground */
  --focus-ring: var(--gilt);
  --field-bg:   #101013;
  --field-ink:  var(--on-dark);
  --panel-bg:   var(--obs-lift);

  /* ---- Type. Two families, and no monospace for prose. Fraunces sets
       everything read as language; Archivo sets everything read as a label or
       a number. Both have metric-comparable fallbacks and no layout depends on
       an exact line count, so a page that never receives the webfonts still
       composes — which is verified, because review screenshots are taken both
       ways. ---- */
  --display: 'Fraunces', Georgia, 'Iowan Old Style', 'Palatino Linotype',
             Palatino, 'Times New Roman', serif;
  --ui:      'Archivo', 'Helvetica Neue', Helvetica, 'Liberation Sans',
             Arial, sans-serif;
  --mono:    ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Fluid scale. Note the whitespace around every + — see the note above. */
  --s-1: clamp(0.8125rem, 0.8rem + 0.06vw, 0.875rem);
  --s0:  clamp(1rem, 0.97rem + 0.14vw, 1.0625rem);
  --s1:  clamp(1.125rem, 1.07rem + 0.26vw, 1.3rem);
  --s2:  clamp(1.3rem, 1.17rem + 0.62vw, 1.7rem);
  --s3:  clamp(1.5rem, 1.26rem + 1.1vw, 2.2rem);
  --s4:  clamp(1.8rem, 1.4rem + 1.9vw, 3rem);
  --s5:  clamp(2rem, 1.3rem + 2.9vw, 3.6rem);
  --s6:  clamp(2.45rem, 1.5rem + 3.4vw, 4.5rem);

  /* Labels never drop below 12px at any viewport. */
  --label:    0.75rem;
  --label-lg: 0.82rem;

  /* ---- Space ---- */
  --gap:    clamp(0.9rem, 0.7rem + 1vw, 1.4rem);
  --gutter: clamp(1.15rem, 0.5rem + 2.6vw, 3rem);
  --band:   clamp(3.6rem, 2rem + 6.4vw, 7.5rem);
  --shell:      74rem;
  --shell-wide: 90rem;
  --shell-text: 42rem;

  /* ---- The aperture. ONE shape, everywhere: a window with a soft point, not
       a semicircle. The real carved panel comes to an ogee. ---- */
  --aperture:       48% 48% 3px 3px / 30% 30% 0 0;
  --aperture-tight: 46% 46% 2px 2px / 22% 22% 0 0;

  /* ---- Shape ---- */
  --r-sm: 2px;

  /* ---- Elevation. On black a shadow is nearly invisible, so depth comes from
       a gold hairline and from light. These are used sparingly. ---- */
  --shadow-2: 0 24px 60px -30px rgba(0, 0, 0, 0.85);
  --shadow-3: 0 40px 90px -40px rgba(0, 0, 0, 0.95);
  --shadow-light: 0 2px 5px rgba(11, 11, 12, 0.06),
                  0 24px 60px -32px rgba(11, 11, 12, 0.28);

  /* ---- Motion ---- */
  --ease:     cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-1: 180ms;
  --dur-2: 320ms;
  --dur-3: 640ms;
  --dur-4: 900ms;   /* the one long gesture. Under 1s: past that it reads as drag. */

  /* The lift. 8px and nothing else. */
  --lift: 8px;
}

/* --------------------------------------------------------------------------
   LIGHT GROUNDS — the one token swap. This site is dark by default, so this
   list is the inverse of the usual arrangement: it names the places that go
   LIGHT. Add a light band? Add its selector here and everything inside it
   adapts, including the gold, which becomes --bronze because --gilt measures
   1.97:1 on paper and cannot set type there at all.
   -------------------------------------------------------------------------- */
.on-light,
.band--paper,
.band--bone {
  --heading:    var(--on-light);
  --body:       var(--on-light-soft);
  --quiet:      var(--on-light-dim);
  --rule:       var(--hair-light);
  --rule-gold:  var(--hair-light-gold);
  --accent:     var(--bronze);
  --accent-lit: var(--bronze);
  --focus-ring: var(--on-light);
  --field-bg:   #ffffff;
  --field-ink:  var(--on-light);
  --panel-bg:   var(--bone);
  color: var(--on-light);
  background: var(--paper);
}
.band--bone { background: var(--bone); }


/* --------------------------------------------------------------------------
   CROSS-DOCUMENT VIEW TRANSITIONS. Two declarations, no JavaScript, and no
   fallback code: a browser without support simply does a normal page load.
   The names are assigned HERE rather than in the markup on purpose — the
   header and footer are duplicated across all 8 files, and a duplicate
   view-transition-name inside one document aborts the whole transition.
   -------------------------------------------------------------------------- */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: no-preference) {
  .header { view-transition-name: site-header; }
  .footer { view-transition-name: site-footer; }
}

/* ==========================================================================
   02 RESET AND BASE
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The header is sticky, so an in-page anchor must not land underneath it.
     WCAG 2.2 SC 2.4.11 "Focus Not Obscured" is failed by exactly this pattern
     (W3C failure technique F110), so both halves are needed: padding on the
     scroll container and a margin on anything that can be targeted. */
  scroll-padding-top: 5.5rem;
}
:target, [id] { scroll-margin-top: 5.5rem; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--obs);
  color: var(--on-dark);
  font: 400 var(--s0)/1.7 var(--ui);
  /* Light type on a dark ground blooms, so it needs a touch more air than the
     same type would on paper: +0.006em tracking and a taller line. */
  letter-spacing: 0.006em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img, svg, video, picture { display: block; max-width: 100%; }
img, video { height: auto; }

h1, h2, h3, h4, p, ul, ol, dl, dd, figure, blockquote, fieldset { margin: 0; }
ul, ol { padding: 0; }
fieldset { border: 0; padding: 0; min-width: 0; }

a { color: inherit; }

button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }

/* One focus treatment for the whole site. --focus-ring swaps on light bands. */
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; border-radius: var(--r-sm); }
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--gilt); color: var(--obs); }

.skip {
  position: absolute; left: 50%; top: 0; z-index: 200;
  transform: translate(-50%, -140%);
  padding: 0.85rem 1.3rem;
  background: var(--gilt); color: var(--obs);
  font: 500 var(--s-1)/1 var(--ui); letter-spacing: 0.1em; text-transform: uppercase;
  text-decoration: none;
  transition: transform var(--dur-2) var(--ease);
}
.skip:focus { transform: translate(-50%, 0); }

/* Screen-reader-only, but focusable if it needs to be. */
.sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}


/* ==========================================================================
   03 TYPOGRAPHY
   Fraunces is variable. opsz is set per size so the display cuts get the
   high-contrast drawing and the small cuts stay readable; SOFT and WONK are
   what make it warm rather than a cold didone, which is the entire reason it
   is here — this studio sells friendly and affordable, not exclusive.
   ========================================================================== */

h1, h2, h3, .display {
  font-family: var(--display);
  font-weight: 400;
  line-height: 1.03;
  letter-spacing: -0.015em;
  color: var(--heading);
  font-variation-settings: 'opsz' 90, 'SOFT' 22, 'WONK' 1;
  text-wrap: balance;
}
h4 {
  font-family: var(--display); font-weight: 500; color: var(--heading);
  font-variation-settings: 'opsz' 40, 'SOFT' 20, 'WONK' 0;
  line-height: 1.2;
}

h1 { font-size: var(--s6); }
h2 { font-size: var(--s5); }
h3 { font-size: var(--s2); font-variation-settings: 'opsz' 48, 'SOFT' 20, 'WONK' 0; }
h4 { font-size: var(--s1); }

/* THE ONE GOLD WORD. A headline may carry a single <em>, and it is not italic:
   it is the gold that the sentence turns on. Palette law rule 3 — this is the
   only place gold sets display type. */
h1 em, h2 em, h3 em { font-style: normal; color: var(--accent); }

p { color: var(--body); text-wrap: pretty; }
.lede { font-size: var(--s1); color: var(--body); max-width: 46ch; line-height: 1.55; }
.prose { max-width: var(--shell-text); }
.prose > * + * { margin-top: 1.05rem; }
.prose h3 { margin-top: 2.2rem; }
.prose strong { color: var(--heading); font-weight: 500; }

/* THE LABEL. Every kicker, eyebrow, tag and micro-heading on the site. */
.lbl {
  display: block;
  font: 500 var(--label)/1.3 var(--ui);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}
.lbl--quiet { color: var(--quiet); }

/* A label with a gold tick drawn in front of it. */
.lbl--tick { display: inline-flex; align-items: center; gap: 0.6rem; }
.lbl--tick::before { content: ""; width: 18px; height: 1px; background: var(--accent-lit); flex: none; }

.meta { font: 400 var(--s-1)/1.55 var(--ui); color: var(--quiet); }
code { font-family: var(--mono); font-size: 0.92em; color: var(--accent); }

.num {
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.16em;
  color: var(--quiet); font-variant-numeric: tabular-nums;
}

blockquote {
  font-family: var(--display); font-size: var(--s2); line-height: 1.35;
  color: var(--heading); font-variation-settings: 'opsz' 60, 'SOFT' 26, 'WONK' 1;
  padding-left: 1.3rem; border-left: 1px solid var(--accent-lit);
}
blockquote p { color: inherit; }
blockquote cite {
  display: block; margin-top: 0.9rem; font: 400 var(--s-1)/1.4 var(--ui);
  font-style: normal; color: var(--quiet); letter-spacing: 0.06em;
}

a:not([class]) {
  color: var(--accent);
  text-decoration-color: var(--hair);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
  transition: text-decoration-color var(--dur-2) var(--ease);
}
a:not([class]):hover { text-decoration-color: var(--accent-lit); }


/* ==========================================================================
   04 LAYOUT
   ========================================================================== */

.shell { max-width: var(--shell); margin-inline: auto; padding-inline: var(--gutter); }
.shell--wide { max-width: var(--shell-wide); }
.shell--text { max-width: calc(var(--shell-text) + var(--gutter) * 2); }

/* A band is a full-width horizontal section. Every band in the markup carries
   an HTML comment naming its ground — keep it accurate when reordering. */
.band { padding-block: var(--band); position: relative; isolation: isolate; }
.band--tight { padding-block: calc(var(--band) * 0.62); }

/* THE GROUND. Flat black looks dead, so: a warm vignette from the top-left
   (light arriving from off-frame) plus SVG grain so the black has tooth. Both
   are inert, decorative, and cost no image request.

   The blend mode matters and is counter-intuitive. MEASURED, by rendering each
   variant and taking the pixel standard deviation of a quiet patch:
       overlay   0.13 on flat near-black   std dev 0.11  — INVISIBLE
       overlay   0.13 on the vignette      std dev 2.31  — vs 2.27 with NO
                                                           grain at all, i.e.
                                                           it does nothing
       normal    0.045                     std dev 2.56  — works
       soft-light 0.45                     std dev 3.15  — best tooth per unit
                                                           of lift  <-- chosen
   `overlay` composites as multiply wherever the backdrop luminance is below
   0.5, and this ground sits at Y≈0.005, so overlay grain on it is a no-op.
   Do not "fix" this back to overlay. */
.band--dark {
  background: radial-gradient(140% 100% at 8% -8%, #241d13 0%, #14110f 34%, var(--obs) 68%);
}
.band--dark::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: -1;
  opacity: 0.45; mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
.band--flat { background: var(--obs); }

/* Every band boundary is a 1px NEUTRAL hairline. It used to be gold, which
   put a gold rule between every section on the site — the gold tax described
   beside --hair. The gold rules are spent elsewhere. */
.band + .band { border-top: 1px solid var(--hair-soft); }

.band__head { max-width: 48rem; margin-bottom: clamp(2rem, 4vw, 3.4rem); }
.band__head > * + * { margin-top: 0.85rem; }
.band__head .lede { margin-top: 1.1rem; }

.grid { display: grid; gap: var(--gap); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); }

.split { display: grid; gap: clamp(1.6rem, 4vw, 3.4rem); }
@media (min-width: 52rem) {
  .split { grid-template-columns: 1fr 1fr; align-items: center; }
  .split--wide-left  { grid-template-columns: 1.25fr 0.75fr; }
  .split--wide-right { grid-template-columns: 0.8fr 1.2fr; }
  .split--top { align-items: start; }
}

.stack > * + * { margin-top: var(--stack, 1rem); }
.row { display: flex; flex-wrap: wrap; gap: 0.8rem; align-items: center; }


/* ==========================================================================
   05 THE APERTURE
   One shape, used for every framed thing on the site: the hero window, a
   project, a film slot, a portrait. A gold hairline around a hole you look
   through. .ap--empty is the placeholder state and is DASHED — see 18.
   ========================================================================== */

.ap { position: relative; margin: 0; }
.ap__win {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: var(--aperture);
  /* The frame is a 2px gold rule with a 14% halo outside it. A 1px rule
     disappears against a photograph; a thicker one starts to look like a
     picture-frame graphic. */
  box-shadow: 0 0 0 2px var(--gilt),
              0 0 0 7px color-mix(in oklab, var(--gilt) 14%, transparent),
              var(--shadow-3);
}
.ap__win > img,
.ap__win > video { width: 100%; height: 100%; object-fit: cover; }

/* The light under the frame. This is the only glow on the site.
   NOTE the inset: it bleeds vertically but NOT horizontally. With -14% on all
   four sides this pushed 27px past the viewport on a 360px phone and opened a
   horizontal scrollbar — and since the gradient is bottom-centred, the sideways
   bleed was doing nothing visible anyway. */
.ap__glow {
  position: absolute; inset: -14% 0; pointer-events: none; z-index: -1;
  border-radius: inherit;
  background: radial-gradient(58% 42% at 50% 100%,
              color-mix(in oklab, var(--gilt) 24%, transparent), transparent 72%);
}
.ap__cap { margin-top: 0.9rem; font: 400 var(--s-1)/1.55 var(--ui); color: var(--body); }
/* In the hero this caption sits over a photograph, so it gets the brighter of
   the two body tones (9.35:1 rather than 5.30:1 on the ground) plus a shadow. */
.hero .ap__cap { color: var(--on-dark-soft); text-shadow: 0 1px 6px rgba(11, 11, 12, 0.9); }
@media (max-width: 56rem) { .hero .ap__cap { font-size: 0.8rem; margin-top: 0.6rem; } }
.ap__cap b { color: var(--accent); font-weight: 500; }
.ap--center .ap__cap { text-align: center; }

.ap--tight .ap__win { border-radius: var(--aperture-tight); }
.ap--tall .ap__win { aspect-ratio: 3 / 4; }
.ap--wide .ap__win { aspect-ratio: 4 / 3; }

/* A portrait chip: his actual face, at label size, beside his actual sentence.
   With no testimonials to show, a real photograph of a real person is the
   trust signal that is honestly available.
   A flex row, not a float: floating it made every line after the first hang
   back out to the full measure and the paragraph read as two ragged blocks. */
.with-face { display: flex; align-items: flex-start; gap: 0.9rem; }
.face {
  flex: none; width: 46px; height: 46px; border-radius: 50%; object-fit: cover;
  margin-top: 0.3rem;
  box-shadow: 0 0 0 1px color-mix(in oklab, var(--gilt) 55%, transparent);
}


/* ==========================================================================
   06 REVEAL, DEVELOP, GILD
   The whole motion layer. Read the MOTION LAW at the top of this file first.
   Every base state here is the FINISHED state; the unfinished state is only
   ever applied under html:not(.no-js), so the page cannot fail closed.
   ========================================================================== */

/* ---- THE LIFT — 8px and an opacity. The generic entrance. ---- */
html:not(.no-js) [data-reveal] { opacity: 0; transform: translateY(var(--lift)); }
html:not(.no-js) [data-reveal].is-in {
  opacity: 1; transform: none;
  transition: opacity var(--dur-3) var(--ease-out) var(--d, 0ms),
              transform var(--dur-3) var(--ease-out) var(--d, 0ms);
}
/* Stagger. --i is set in the markup; nothing else reads it. */
html:not(.no-js) [data-reveal][style*="--i"] { --d: calc(var(--i, 0) * 70ms); }

/* ---- THE APERTURE OPENS — a clip-path wipe up the inside of the frame.
     Once per page, on the image that is the page's subject. ---- */
html:not(.no-js) .ap--opens .ap__win { clip-path: inset(100% 0 0 0 round var(--aperture)); }
html:not(.no-js) .ap--opens.is-open .ap__win {
  clip-path: inset(0 round var(--aperture));
  transition: clip-path var(--dur-4) var(--ease-out) 120ms;
}

/* ---- IT DEVELOPS — ONE grayscale filter on the CONTAINER. Measured: 0 frames
     over 20ms across 120 samples on a 4x-throttled phone, against 2 for the
     same effect applied per tile. Never animate this per tile. ---- */
html:not(.no-js) .develops { filter: grayscale(1) contrast(1.06); }
html:not(.no-js) .develops.is-developed {
  filter: grayscale(0) contrast(1);
  transition: filter var(--dur-4) var(--ease);
}
/* Where scroll-driven animations exist, the same effect becomes a continuous
   scrub tied to the section's own position, which reads far better than a
   one-shot. The JS class above is harmless if this branch is taken: the
   animation wins over the transition. */
@supports (animation-timeline: view()) and (animation-range: cover) {
  @media (prefers-reduced-motion: no-preference) {
    html:not(.no-js) .develops {
      animation: develop linear both;
      animation-timeline: view();
      animation-range: entry 18% cover 48%;
    }
    @keyframes develop {
      from { filter: grayscale(1) contrast(1.06); }
      to   { filter: grayscale(0) contrast(1); }
    }
  }
}



/* ==========================================================================
   07 HEADER AND NAVIGATION
   Duplicated in all 8 HTML files between SHARED CHROME markers. A change here
   is a change in 8 files.
   ========================================================================== */

.header {
  position: sticky; top: 0; z-index: 100;
  border-bottom: 1px solid transparent;
  transition: background var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease),
              transform var(--dur-3) var(--ease);
}
.header.is-scrolled {
  /* A 92%-opaque bar rather than backdrop-filter: a sticky element's backdrop
     changes on every scroll frame, so a backdrop-filter re-filters on every
     scroll frame. On a black ground the two are visually identical. */
  background: color-mix(in oklab, var(--obs) 93%, transparent);
  border-bottom-color: var(--hair-soft);
}
.header.is-hidden { transform: translateY(-100%); }

.header__in {
  max-width: var(--shell-wide); margin-inline: auto;
  padding: 0.9rem var(--gutter);
  display: flex; align-items: center; gap: 1rem;
}

/* The wordmark. Letterspaced Roman caps, matching the real printed lockup in
   media/studio/logo-lockup.jpg, with the tagline under it at label size. */
.mark { display: inline-flex; flex-direction: column; gap: 0.18rem; text-decoration: none; }
.mark__name {
  font-family: var(--display); font-weight: 500; font-size: var(--s1);
  letter-spacing: 0.3em; text-transform: uppercase; color: var(--on-dark);
  font-variation-settings: 'opsz' 40, 'SOFT' 8, 'WONK' 0;
}
.mark__line {
  font: 400 var(--label)/1 var(--ui); letter-spacing: 0.14em;
  color: var(--champ); text-transform: none; white-space: nowrap;
}
@media (max-width: 26rem) {
  .mark__name { font-size: var(--s0); letter-spacing: 0.24em; }
  .mark__line { font-size: 0.75rem; letter-spacing: 0.08em; }   /* 12px: the floor. Never below it. */
}
.mark:hover .mark__line { color: var(--gilt); }

.nav { margin-left: auto; display: none; }
@media (min-width: 62rem) { .nav { display: block; } }

/* With JavaScript off there is no script to open a panel, so the full nav is
   shown inline at every width instead and the toggle is removed. The header
   gets taller on a phone; that is the correct trade for being navigable. */
html.no-js .nav { display: block; margin-left: 0; width: 100%; }
html.no-js .nav ul { flex-wrap: wrap; gap: 0.5rem 1.1rem; }
html.no-js .header__in { flex-wrap: wrap; }
html.no-js .nav-toggle { display: none; }
.nav ul { list-style: none; display: flex; gap: clamp(1rem, 2vw, 1.9rem); align-items: center; }
.nav a {
  position: relative; display: inline-block; padding: 0.4rem 0;
  font: 500 var(--label-lg)/1 var(--ui); letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--on-dark-soft); text-decoration: none;
  transition: color var(--dur-2) var(--ease);
}
.nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--gilt); transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-2) var(--ease);
}
.nav a:hover, .nav a[aria-current="page"] { color: var(--on-dark); }
.nav a:hover::after, .nav a[aria-current="page"]::after { transform: scaleX(1); }

/* NOTE the descendant selector: .btn is defined later in this file and would
   otherwise win the source-order tie and show this button on a phone, where it
   collides with the Menu toggle. */
.header .header__cta { display: none; }
@media (min-width: 74rem) { .header .header__cta { display: inline-flex; } }

/* The toggle. 48px hit area at every viewport. */
.nav-toggle {
  margin-left: auto; display: inline-flex; align-items: center; gap: 0.6rem;
  min-height: 48px; min-width: 48px; justify-content: flex-end;
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--on-dark);
}
@media (min-width: 62rem) { .nav-toggle { display: none; } }
.nav-toggle__bars { position: relative; width: 22px; height: 10px; flex: none; }
.nav-toggle__bars span {
  position: absolute; left: 0; right: 0; height: 1px; background: var(--gilt);
  transition: transform var(--dur-2) var(--ease), opacity var(--dur-1) var(--ease);
}
.nav-toggle__bars span:first-child { top: 0; }
.nav-toggle__bars span:last-child { bottom: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:first-child { transform: translateY(4.5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:last-child { transform: translateY(-4.5px) rotate(-45deg); }

/* THE PANEL — three ways to open it, so it cannot end up unreachable.
   1. The native `popover` attribute. Fully declarative: top layer, light
      dismiss, Escape and focus return all work WITH JAVASCRIPT DISABLED.
   2. site.js adds .is-open, for the browsers that do not support popover.
   3. With JavaScript off AND no popover support, the header falls back to a
      plain wrapped list of links (see html.no-js below) so there is always a
      way to navigate.

   The base state is display:none, which matters: a browser that does not know
   the `popover` attribute simply ignores it, and without this rule the panel
   would render as a full-screen block permanently covering the page. */
.nav-panel {
  display: none;
  position: fixed; inset: 0; z-index: 150;
  width: 100%; max-width: none; height: 100%; max-height: none;
  margin: 0; border: 0;
  background: var(--obs); color: var(--on-dark);
  padding: calc(var(--gutter) + 3.6rem) var(--gutter) var(--gutter);
  flex-direction: column; gap: 1.2rem;
  overflow-y: auto; overscroll-behavior: contain;
}
.nav-panel:popover-open { display: flex; }
.nav-panel.is-open { display: flex; }
.nav-panel::backdrop { background: rgba(11, 11, 12, 0.6); }
@media (prefers-reduced-motion: no-preference) {
  @supports (transition-behavior: allow-discrete) {
    .nav-panel {
      opacity: 0;
      transition: opacity var(--dur-2) var(--ease), display var(--dur-2) allow-discrete;
    }
    .nav-panel:popover-open, .nav-panel.is-open { opacity: 1; }
    @starting-style { .nav-panel:popover-open, .nav-panel.is-open { opacity: 0; } }
  }
}
.nav-panel__list { list-style: none; display: flex; flex-direction: column; }
.nav-panel__list li { border-top: 1px solid var(--hair-soft); }
.nav-panel__list li:last-child { border-bottom: 1px solid var(--hair-soft); }
.nav-panel__list a {
  display: flex; align-items: baseline; gap: 1rem; padding: 1rem 0;
  font-family: var(--display); font-size: var(--s3); color: var(--on-dark);
  text-decoration: none; font-variation-settings: 'opsz' 60, 'SOFT' 20, 'WONK' 1;
}
.nav-panel__list a:hover { color: var(--champ); }
.nav-panel__list a[aria-current="page"] { color: var(--champ); }
.nav-panel__list .num { flex: none; }
.nav-panel__foot { margin-top: auto; padding-top: 1.6rem; }
.nav-panel__close {
  position: absolute; top: 1rem; right: var(--gutter);
  min-height: 48px; min-width: 48px; display: inline-flex; align-items: center; gap: 0.5rem;
  justify-content: flex-end;
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--on-dark);
}


/* ==========================================================================
   08 BUTTONS AND LINKS
   Palette law rule 3: gold is a hairline plus a wash, never a slab. A flat
   #d4af37 button was rendered and rejected — it reads mustard.
   ========================================================================== */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.6rem;
  min-height: 48px; padding: 0.8rem 1.5rem;
  font: 500 var(--s-1)/1 var(--ui); letter-spacing: 0.1em; text-transform: uppercase;
  text-decoration: none; text-align: center;
  color: var(--accent);
  border: 1px solid var(--accent-lit);
  background: color-mix(in oklab, var(--accent-lit) 12%, transparent);
  position: relative;
  transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease);
}
.btn::after {
  content: ""; position: absolute; left: -1px; right: -1px; bottom: -1px; height: 1px;
  background: var(--accent-lit); transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-3) var(--ease-out);
}
.btn:hover { background: color-mix(in oklab, var(--accent-lit) 24%, transparent); color: var(--heading); }
.btn:hover::after { transform: scaleX(1); }

.btn--ghost {
  background: transparent; color: var(--body);
  border-color: color-mix(in oklab, var(--accent-lit) 45%, transparent);
}
.btn--ghost:hover { background: transparent; color: var(--heading); border-color: var(--accent-lit); }

.btn--sm { min-height: 40px; padding: 0.55rem 1rem; }

/* The one arrow link, used for every "and then what" on the site. */
.go {
  display: inline-flex; align-items: center; gap: 0.55rem;
  font: 500 var(--label-lg)/1 var(--ui); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--accent); text-decoration: none; padding: 0.4rem 0;
}
.go__line { width: 22px; height: 1px; background: var(--accent-lit); transition: width var(--dur-2) var(--ease); }
.go:hover { color: var(--heading); }
.go:hover .go__line { width: 34px; }


/* ==========================================================================
   09 SCROLL PROGRESS
   A 1px gold rule across the top of the page. Native scroll-timeline where it
   exists, a JS-set custom property otherwise, and absent entirely if neither.
   ========================================================================== */

.progress {
  position: fixed; top: 0; left: 0; right: 0; height: 1px; z-index: 190;
  background: transparent; pointer-events: none;
}
.progress span {
  display: block; height: 100%; background: var(--gilt);
  transform-origin: left; transform: scaleX(var(--p, 0));
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .progress span {
      transform: scaleX(0);
      animation: progress linear both;
      animation-timeline: scroll(root block);
    }
    @keyframes progress { to { transform: scaleX(1); } }
  }
}


/* ==========================================================================
   10 HERO AND PAGE MAST
   The hero is the 30-second answer. Everything a stranger needs — who, what,
   where, how much, what next — is inside the first screen at 1440x900, and
   inside the first screen plus one short scroll on a 390px phone. That is a
   layout requirement, not a copy requirement: see ADR 0006.

   The device: the whole screen is a real room in MONOCHROME, and inside a
   gold-framed aperture the SAME photograph is in full colour. It is the only
   colour on the screen. This is where the brief's black-and-white section and
   colourful section meet — as one piece of content transitioning, so the
   monochrome carries meaning instead of being a filter.
   ========================================================================== */

.hero {
  position: relative; isolation: isolate; overflow: hidden;
  /* 92svh, not 100vh, and deliberately not a self-contained composition: a
     hero sized exactly to the viewport with everything resolved inside it is
     the textbook recipe for the "illusion of completeness", where the visitor
     believes the page has ended. svh rather than vh because vh is unreliable
     while a mobile browser's toolbars move. The next band's top edge is meant
     to be visible. */
  min-height: 88svh;
  display: grid; align-content: center;
  padding-block: calc(var(--band) * 0.8) calc(var(--band) * 0.7);
}

/* The scroll cue. A 1px gold rule that breathes, and a word. It exists because
   the hero is otherwise a closed composition. */
.hero__cue {
  position: absolute; left: 50%; bottom: 1.1rem; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 0.5rem;
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.2em; text-transform: uppercase;
  /* Same reason as .crumbs: this sits over the hero photograph, where sampling
     the real pixels put --quiet at 4.24:1. */
  color: var(--on-dark-soft); z-index: 3;
  text-shadow: 0 1px 6px rgba(11, 11, 12, 0.9);
}
.hero__cue span { display: block; width: 1px; height: 30px; background: linear-gradient(var(--gilt), transparent); }
@media (prefers-reduced-motion: no-preference) {
  .hero__cue span { animation: cue 2.6s var(--ease) infinite; transform-origin: top; }
  @keyframes cue { 0%, 100% { transform: scaleY(0.4); opacity: 0.5; } 50% { transform: scaleY(1); opacity: 1; } }
}
@media (max-height: 40rem) { .hero__cue { display: none; } }

/* The monochrome room behind everything. Decorative: alt="" and aria-hidden. */
.hero__bg { position: absolute; inset: 0; z-index: -2; }
.hero__bg img {
  width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(1) contrast(1.1) brightness(0.34);
}
/* The scrim. Type never sits on bare photograph: at 0.95 over an already
   darkened image the effective ground is --obs, which measures 18.4:1 with
   --on-dark. Measured, not assumed. */
.hero__bg::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg,
              rgba(11, 11, 12, 0.95) 0%, rgba(11, 11, 12, 0.9) 38%,
              rgba(11, 11, 12, 0.5) 72%, rgba(11, 11, 12, 0.72) 100%);
}
@media (max-width: 56rem) {
  /* On a phone the copy runs the full width, so the scrim has to be flat and
     heavy rather than directional. */
  .hero__bg::after { background: rgba(11, 11, 12, 0.86); }
}

.hero__grid { display: grid; gap: clamp(1.5rem, 4vw, 3.4rem); align-items: center; }
@media (min-width: 56rem) { .hero__grid { grid-template-columns: 1.05fr 0.95fr; } }

/* Phone order: identity, headline, THE COLOUR WINDOW, who he is, the prices,
   the call. The window comes third because it is the hook, and on a phone it
   must not be pushed below two paragraphs of text. */
@media (max-width: 56rem) {
  .hero__grid { display: flex; flex-direction: column; gap: 1.4rem; }
  /* display:contents dissolves this wrapper so its children become flex items
     of .hero__grid — without it the order values below would apply to the
     wrapper only and the colour window would sit under two paragraphs of copy,
     which is exactly where the one thing that has to be seen must not be. */
  .hero__copy { display: contents; }
  .hero__kicker { order: 1; margin-bottom: 0; }
  .hero h1      { order: 2; margin-bottom: 0; }
  .hero .ap     { order: 3; max-width: 14.5rem; align-self: center; width: 100%; }
  .hero__sub    { order: 4; }
  .facts        { order: 5; margin-top: 0.4rem; }
  .hero__cta    { order: 6; margin-top: 0.4rem; }
}

.hero__kicker { display: flex; flex-wrap: wrap; gap: 0.45rem 1.1rem; margin-bottom: 1.3rem; }
.hero__kicker span { display: inline-flex; align-items: center; gap: 0.55rem; }
.hero__kicker span::before { content: ""; width: 16px; height: 1px; background: var(--gilt); flex: none; }

.hero h1 { margin-bottom: 1.1rem; }
.hero__sub { font-size: var(--s1); color: var(--body); max-width: 42ch; line-height: 1.55; }
.hero__cta { display: flex; flex-wrap: wrap; gap: 0.8rem; margin-top: 2rem; }

/* THE FACTS STRIP. Four numbers, in a gold grid, that answer "how much" and
   "what do I get" without a click. A price behind a form is the single most
   common way a small studio loses a visitor, and every number here is real. */
.facts {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px; margin-top: 2.1rem;
  background: var(--rule-gold);   /* a deliberate gold spend: the price grid */
}
@media (min-width: 40rem) { .facts { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.facts > div { background: var(--obs); padding: 0.95rem 1rem 1.05rem; }
.band--paper .facts > div, .on-light .facts > div { background: var(--paper); }
.facts dt {
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--quiet); margin-bottom: 0.45rem;
}
.facts dd {
  font-family: var(--display); font-size: var(--s1); color: var(--heading);
  font-variation-settings: 'opsz' 40, 'SOFT' 20, 'WONK' 0; line-height: 1.15;
}
.facts dd small {
  display: block; margin-top: 0.25rem;
  font: 400 var(--label)/1.35 var(--ui); letter-spacing: 0.03em; color: var(--quiet);
}

/* The page mast: every page that is not the home page. Same language, no
   photograph, so an inner page loads instantly. */
.mast { padding-block: calc(var(--band) * 0.9) calc(var(--band) * 0.55); }
.mast h1 { max-width: 34ch; }
.mast .lede { margin-top: 1.2rem; }
.crumbs { list-style: none; display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 1.2rem; }
/* --on-dark-soft, not --quiet: measured by sampling the rendered pixels, the
   crumbs sit on the lightest part of the warm vignette and --quiet came out at
   4.46:1 there — under AA for 12px text. Nothing at the floor tone goes on top
   of a gradient. */
.crumbs li { font: 500 var(--label)/1 var(--ui); letter-spacing: 0.14em; text-transform: uppercase; color: var(--on-dark-soft); }
.crumbs li + li::before { content: "·"; margin-right: 0.5rem; color: var(--accent-lit); }
.crumbs a { text-decoration: none; color: var(--on-dark-soft); }
.crumbs a:hover { color: var(--accent); }


/* ==========================================================================
   11 THE PAPER BAND
   The one light ground, and it means one thing: a document. The mood board,
   the space plan, the shopping list, the price sheet. Palette law rule 4.
   Gold cannot set type here — the LIGHT GROUNDS block swaps --accent to
   --bronze automatically, so nothing in this section needs a special rule.
   ========================================================================== */

/* A mood board is a DOCUMENT, so it is never cropped and never framed as an
   aperture — the aperture is for windows. A plain hairline and a soft shadow,
   and object-fit: contain so nothing is cut off. */
.paper__shot {
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-light);
  border: 1px solid var(--rule);
  width: 100%; height: auto;
}
figure.doc { margin: 0; }
figure.doc figcaption { margin-top: 0.8rem; font: 400 var(--s-1)/1.55 var(--ui); color: var(--body); }
figure.doc figcaption b { color: var(--accent); font-weight: 500; }
.paper__list { list-style: none; counter-reset: d; }
.paper__list li {
  counter-increment: d;
  display: grid; grid-template-columns: auto 1fr; gap: 0.9rem;
  padding: 0.95rem 0; border-top: 1px solid var(--rule);
}
.paper__list li:last-child { border-bottom: 1px solid var(--rule); }
.paper__list li::before {
  content: counter(d, decimal-leading-zero);
  grid-column: 1; grid-row: 1 / span 2;
  font: 500 var(--label)/1.6 var(--ui); letter-spacing: 0.12em; color: var(--accent);
}
/* Both of these belong in column 2, stacked. Without an explicit column the
   <span> wrapped onto a second row in the narrow auto column and the title was
   crushed to one word per line. */
.paper__list b { grid-column: 2; grid-row: 1; }
.paper__list span { grid-column: 2; grid-row: 2; }
.paper__list b { font-weight: 500; color: var(--heading); }
.paper__list span { display: block; margin-top: 0.2rem; font-size: var(--s-1); color: var(--body); }


/* ==========================================================================
   12 THE DEVELOPING WALL
   The black-and-white section that becomes the colourful section. ONE filter
   on the container (see the MOTION LAW). Touching a single tile lifts that one
   object into colour early — that is an interaction, not a scroll effect.
   ========================================================================== */

.wall__grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(0.4rem, 1.2vw, 0.9rem); }
@media (min-width: 48rem) { .wall__grid { grid-template-columns: repeat(4, 1fr); } }

.tile { position: relative; overflow: hidden; background: var(--obs-card); margin: 0; }
.tile img {
  width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
  transition: transform var(--dur-4) var(--ease), filter var(--dur-3) var(--ease);
}
.tile figcaption {
  position: absolute; inset: auto 0 0 0; padding: 0.7rem 0.8rem;
  font: 500 var(--label)/1.35 var(--ui); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--on-dark);
  background: linear-gradient(to top, rgba(0, 0, 0, 0.86), rgba(0, 0, 0, 0.5) 55%, transparent);
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease);
}
.tile:hover figcaption, .tile:focus-within figcaption { opacity: 1; transform: none; }

/* The single-object lift. One element, one transition, only on interaction. */
.tile:hover img, .tile:focus-within img { filter: grayscale(0) saturate(1.06); transform: scale(1.03); }
.tile::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  box-shadow: inset 0 0 0 1px transparent;
  transition: box-shadow var(--dur-2) var(--ease);
}
.tile:hover::after, .tile:focus-within::after { box-shadow: inset 0 0 0 1px var(--gilt); }
/* A tile can be a link or hold one; either way the whole tile is the target. */
.tile a { position: absolute; inset: 0; z-index: 2; }


/* ==========================================================================
   13 THE OBJECT ROLL
   The all-cultures answer, and it is structural rather than stated: one row
   per object, OBJECT / WHAT IT DOES / WHICH WALL IT GOT, and the last row is
   empty and addressed to the reader. No flags, no country lists, no globes,
   and no claim about an object's provenance that docs/ does not record.
   ========================================================================== */

.roll { list-style: none; border-top: 1px solid var(--rule); }
.roll li {
  border-bottom: 1px solid var(--rule);
  display: grid; gap: 0.35rem 1.6rem; padding: 1.15rem 0; align-items: baseline;
}
@media (min-width: 46rem) {
  .roll li { grid-template-columns: 1.4fr 1.7fr auto; padding: 1.35rem 0; }
}
.roll__obj {
  font-family: var(--display); font-size: var(--s2); color: var(--heading);
  font-variation-settings: 'opsz' 48, 'SOFT' 20, 'WONK' 0; line-height: 1.15;
}
.roll__does { color: var(--body); font-size: var(--s-1); line-height: 1.6; text-align: left; }
.roll__wall {
  font: 500 var(--label)/1.4 var(--ui); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent);
}
@media (min-width: 46rem) { .roll__wall { text-align: right; white-space: nowrap; } }

/* The open slot. Dashed, like every other placeholder on the site (see 18). */
.roll li.is-open { border-bottom-style: dashed; }
.roll li.is-open .roll__obj { color: var(--accent); }
.roll li.is-open .roll__wall { color: var(--quiet); }


/* ==========================================================================
   14 THE COLOUR ROOM
   The payoff, and the one place on the site where the photographed colours
   appear together (palette law rule 5). It is on a black ground because that
   is the only ground these colours are legible on — saffron measures 9.12:1
   on --obs and 2.02:1 on --paper.
   ========================================================================== */

/* The three named colours, used in the hero h1 and nowhere else that needs a
   different value. Not scoped to a section: the h1 that carries them moved out
   of .colour into .hero (ADR 0010), and scoping them broke the moment it did. */
.c1 { color: var(--saffron); }
.c2 { color: var(--turquoise); }
.c3 { color: var(--crabapple); }

.colour__grid { display: grid; gap: clamp(0.5rem, 1.4vw, 1rem); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 52rem) {
  .colour__grid { grid-template-columns: 1.6fr 1fr 1fr; }
  .colour__grid > :first-child { grid-row: span 2; }
}
.colour__grid figure { margin: 0; position: relative; overflow: hidden; }
.colour__grid img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 1; transition: transform var(--dur-4) var(--ease); }
.colour__grid > :first-child img { aspect-ratio: 4 / 5; }
.colour__grid figure:hover img { transform: scale(1.04); }

/* The swatch chips: the palette, named, in its own colours. */
.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.chip {
  display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.42rem 0.8rem;
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.1em; text-transform: uppercase;
  border: 1px solid color-mix(in oklab, currentColor 40%, transparent);
}
.chip::before { content: ""; width: 8px; height: 8px; background: currentColor; flex: none; }
.chip--saffron   { color: var(--saffron); }
.chip--turquoise { color: var(--turquoise); }
.chip--crabapple { color: var(--crabapple); }
.chip--leaf      { color: var(--leaf); }
.chip--ochre     { color: var(--ochre); }
.chip--gilt      { color: var(--champ); }


/* ==========================================================================
   15 PRICE PLATES
   Every number on this site is real and every one is visible without a form.
   Four plates: the free call first, because it is the honest lowest-friction
   way in and the only one that costs nothing to try.
   ========================================================================== */

.plates { display: grid; gap: 1px; background: var(--rule); }
@media (min-width: 44rem) { .plates { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 68rem) { .plates { grid-template-columns: repeat(4, 1fr); } }

.plate {
  background: var(--obs); padding: clamp(1.2rem, 2.4vw, 1.8rem);
  display: flex; flex-direction: column; gap: 0.8rem;
}
.band--paper .plate, .on-light .plate { background: var(--paper); }
.plate__num { font: 500 var(--label)/1 var(--ui); letter-spacing: 0.18em; color: var(--quiet); }
.plate h3 { font-size: var(--s2); }
.plate__price {
  font-family: var(--display); font-size: var(--s3); color: var(--accent);
  font-variation-settings: 'opsz' 72, 'SOFT' 18, 'WONK' 0; line-height: 1;
}
.plate__price small { font-family: var(--ui); font-size: var(--label); letter-spacing: 0.1em;
                      color: var(--quiet); display: block; margin-top: 0.4rem; text-transform: uppercase; }
.plate__what { font-size: var(--s-1); color: var(--body); }
.plate ul { list-style: none; font-size: var(--s-1); color: var(--body); }
.plate ul li { padding: 0.35rem 0 0.35rem 1.1rem; position: relative; }
.plate ul li::before {
  content: ""; position: absolute; left: 0; top: 0.85em; width: 6px; height: 1px;
  background: var(--accent-lit);
}
.plate__limit { margin-top: auto; padding-top: 0.6rem; font: 400 var(--label)/1.5 var(--ui); color: var(--quiet); }
.plate .go, .plate .btn { margin-top: auto; }

/* The free plate is the one thing on the site that gets a gold ground, and it
   is a 10% wash rather than a fill — palette law rule 3. */
.plate--free { background: color-mix(in oklab, var(--gilt) 10%, var(--obs)); }
.band--paper .plate--free, .on-light .plate--free { background: color-mix(in oklab, var(--bronze) 8%, var(--paper)); }


/* ==========================================================================
   16 PROCESS AND DELIVERABLES
   Five steps, four stages, four things that arrive. Numbered, because the one
   thing a stranger buying a design service wants is to know what happens next.
   ========================================================================== */

.steps { list-style: none; counter-reset: s; }
.steps li {
  counter-increment: s; position: relative;
  padding: 1.05rem 0 1.05rem 3.2rem;
  border-top: 1px solid var(--rule);
}
.steps li:last-child { border-bottom: 1px solid var(--rule); }
.steps li::before {
  content: counter(s, decimal-leading-zero);
  position: absolute; left: 0; top: 1.15rem;
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.14em; color: var(--accent);
}
.steps b { display: block; font-weight: 500; color: var(--heading); font-family: var(--display);
           font-size: var(--s1); font-variation-settings: 'opsz' 40, 'SOFT' 20, 'WONK' 0; }
.steps span { display: block; margin-top: 0.3rem; font-size: var(--s-1); color: var(--body); }

.stages { display: grid; gap: 1px; background: var(--rule); }
@media (min-width: 44rem) { .stages { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 68rem) { .stages { grid-template-columns: repeat(4, 1fr); } }
.stage { background: var(--obs); padding: clamp(1.1rem, 2.2vw, 1.6rem); }
.band--paper .stage, .on-light .stage { background: var(--paper); }
.stage__num { font: 500 var(--label)/1 var(--ui); letter-spacing: 0.18em; color: var(--accent); }
.stage h3 { margin: 0.7rem 0 0.5rem; font-size: var(--s1); }
.stage p:not([class]) { font-size: var(--s-1); }
/* Whose job it is. Two states, and they are the whole point of naming stages. */
.stage__who {
  display: inline-block; margin-top: 0.9rem;
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.12em; text-transform: uppercase;
  padding: 0.3rem 0.6rem; border: 1px solid color-mix(in oklab, currentColor 40%, transparent);
}
.stage__who--mine  { color: var(--champ); }
.stage__who--yours { color: var(--turquoise); }
.band--paper .stage__who--mine, .on-light .stage__who--mine { color: var(--bronze); }
.band--paper .stage__who--yours, .on-light .stage__who--yours { color: var(--on-light-soft); }

.delivs { list-style: none; display: grid; gap: var(--gap); }
@media (min-width: 50rem) { .delivs { grid-template-columns: repeat(2, 1fr); } }
.deliv { border: 1px solid var(--rule); padding: clamp(1.1rem, 2.2vw, 1.7rem); background: var(--panel-bg); }
.deliv__num { font: 500 var(--label)/1 var(--ui); letter-spacing: 0.18em; color: var(--accent); }
.deliv h3 { margin: 0.7rem 0 0.6rem; }
.deliv p:not([class]) { font-size: var(--s-1); }
.deliv__when { display: block; margin-top: 0.9rem; font: 500 var(--label)/1.4 var(--ui);
               letter-spacing: 0.1em; text-transform: uppercase; color: var(--quiet); }


/* ==========================================================================
   17 BEFORE / AFTER
   Driven by a real <input type="range">, so it is keyboard-operable and it
   works with a screen reader. With JavaScript off both photographs are shown
   stacked and labelled, which is a complete answer rather than a broken one.
   ========================================================================== */

.ba { position: relative; margin: 0; }
.ba__frame {
  position: relative; overflow: hidden; border-radius: var(--r-sm);
  box-shadow: 0 0 0 1px var(--rule), var(--shadow-2);
  aspect-ratio: 4 / 3;
}
.ba__frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.ba__after { clip-path: inset(0 0 0 var(--x, 50%)); }
.ba__handle {
  position: absolute; top: 0; bottom: 0; left: var(--x, 50%); width: 1px;
  background: var(--gilt); pointer-events: none;
  box-shadow: 0 0 12px 1px color-mix(in oklab, var(--gilt) 45%, transparent);
}
.ba__handle::after {
  content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 42px; height: 42px; border-radius: 50%;
  border: 1px solid var(--gilt);
  background: color-mix(in oklab, var(--obs) 78%, transparent);
}
.ba__tag {
  position: absolute; top: 0.7rem; z-index: 3;
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.16em; text-transform: uppercase;
  padding: 0.35rem 0.6rem; background: color-mix(in oklab, var(--obs) 78%, transparent);
  color: var(--on-dark); border: 1px solid var(--hair-soft);
}
.ba__tag--before { left: 0.7rem; }
.ba__tag--after  { right: 0.7rem; color: var(--champ); }

/* The range sits over the frame, invisible but fully operable. */
.ba__range {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
  -webkit-appearance: none; appearance: none; background: transparent; cursor: ew-resize; z-index: 4;
}
.ba__range::-webkit-slider-thumb { -webkit-appearance: none; width: 44px; height: 100%; background: transparent; }
.ba__range::-moz-range-thumb { width: 44px; height: 100%; background: transparent; border: 0; }
.ba__range:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 4px; }

/* No JS: no clipping, no handle, both photographs shown in sequence. */
html.no-js .ba__after { clip-path: none; position: relative; }
html.no-js .ba__frame { aspect-ratio: auto; }
html.no-js .ba__frame img { position: relative; }
html.no-js .ba__handle, html.no-js .ba__range { display: none; }
html.no-js .ba__tag { position: static; display: inline-block; margin: 0.5rem 0.5rem 0 0; }


/* ==========================================================================
   18 FILM AND PROJECT SLOTS
   The brief asked for placeholder videos and placeholder projects. A
   placeholder has to look deliberate rather than broken, and it must never
   imply content that does not exist.

   ONE CONVENTION, USED EVERYWHERE ON THE SITE:
       a DASHED gold frame  = nothing has been uploaded here yet
       a SOLID  gold frame  = this is real
   One glance tells them apart, and every empty slot says which file fills it.
   ========================================================================== */

.slots { display: grid; gap: clamp(0.7rem, 1.6vw, 1.2rem);
         grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); }

.slot {
  position: relative; margin: 0; aspect-ratio: 3 / 4;
  display: grid; place-items: center; overflow: hidden;
  border-radius: var(--aperture); background: var(--obs-lift);
  box-shadow: 0 0 0 1.5px color-mix(in oklab, var(--gilt) 55%, transparent);
}
/* EMPTY: a hatch, a dashed inner rule, and a poster that is deliberately
   dimmed so nobody mistakes it for the content. */
.slot--empty {
  background-image: repeating-linear-gradient(135deg,
    color-mix(in oklab, var(--gilt) 7%, transparent) 0 10px, transparent 10px 20px);
}
.slot--empty::before {
  content: ""; position: absolute; inset: 9px; border-radius: inherit; pointer-events: none;
  border: 1.5px dashed color-mix(in oklab, var(--gilt) 48%, transparent);
}
.slot__poster { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
                filter: grayscale(1) brightness(0.4); }
.slot__body { position: relative; z-index: 2; text-align: center; padding: 1.3rem 1.1rem;
              background: radial-gradient(70% 60% at 50% 50%, rgba(11, 11, 12, 0.82), transparent 78%); }
.slot__mark { display: block; margin-bottom: 0.55rem;
              font: 500 var(--label)/1 var(--ui); letter-spacing: 0.18em; text-transform: uppercase;
              color: var(--champ); }
.slot__what { display: block; font-family: var(--display); font-size: var(--s2); color: var(--on-dark);
              font-variation-settings: 'opsz' 40, 'SOFT' 20, 'WONK' 0; line-height: 1.15; }
.slot__note { display: block; margin-top: 0.6rem; font: 400 var(--label)/1.55 var(--ui); color: var(--on-dark-dim); }
.slot__note code { color: var(--champ); }

.slot__play {
  width: 54px; height: 54px; border-radius: 50%; display: grid; place-items: center;
  margin: 0 auto 0.85rem; border: 1.5px solid var(--gilt); color: var(--champ);
  transition: background var(--dur-2) var(--ease);
}
.slot__play svg { width: 15px; height: 15px; fill: currentColor; margin-left: 2px; }
button.slot__play:hover { background: color-mix(in oklab, var(--gilt) 20%, transparent); }

/* REAL: a filled video card. site.js flips a card to .slot--empty if the
   <source> errors, so a missing file degrades to the honest state rather than
   to a broken player. */
.slot--filled { background: var(--obs); }
.slot--filled video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.slot--filled .slot__body { background: linear-gradient(to top, rgba(0,0,0,.86), transparent 70%);
                            align-self: end; width: 100%; }


/* ==========================================================================
   19 TIP OF THE WEEK
   Real, useful, and rotates by ISO week from an array in site.js. It is here
   because a stranger who is not ready to book anything can still leave with
   something, which is the cheapest honest reason to come back.
   ========================================================================== */

.tip {
  border: 1px solid var(--rule); background: var(--panel-bg);
  padding: clamp(1.3rem, 3vw, 2.2rem);
  display: grid; gap: 1rem;
}
.tip__top { display: flex; flex-wrap: wrap; gap: 0.6rem 1rem; align-items: baseline; }
.tip__week { font: 500 var(--label)/1 var(--ui); letter-spacing: 0.18em; text-transform: uppercase; color: var(--accent); }
.tip h2, .tip h3 { font-size: var(--s3); }
.tip__body { font-size: var(--s1); color: var(--body); max-width: 52ch; }
.tip__foot { display: flex; flex-wrap: wrap; gap: 0.8rem 1.2rem; align-items: center; }


/* ==========================================================================
   20 NEWSLETTER AND FORMS
   No backend exists (ADR 0003 / ADR 0004 / ADR 0006). Every form carries
   data-endpoint="". Empty means submitting composes a complete pre-filled
   email in the visitor's own mail client, which genuinely reaches a person.
   Set data-endpoint to a form service and the same form POSTs there instead.
   NOTHING EVER REPORTS SUCCESS FOR SOMETHING THAT DID NOT HAPPEN.
   ========================================================================== */

.form { display: grid; gap: 1.1rem; }
.field { display: grid; gap: 0.4rem; }
.field > label {
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--quiet);
}
.field .req { color: var(--accent); }
.field input, .field textarea, .field select {
  width: 100%; padding: 0.8rem 0.9rem; min-height: 48px;
  background: var(--field-bg); color: var(--field-ink);
  border: 1px solid var(--rule); border-radius: var(--r-sm);
  transition: border-color var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}
.field textarea { min-height: 8rem; resize: vertical; }
.field input:hover, .field textarea:hover, .field select:hover { border-color: var(--accent-lit); }
.field input::placeholder, .field textarea::placeholder { color: var(--quiet); opacity: 1; }
.field__hint { font: 400 var(--label)/1.5 var(--ui); color: var(--quiet); }

.fields2 { display: grid; gap: 1.1rem; }
@media (min-width: 40rem) { .fields2 { grid-template-columns: 1fr 1fr; } }

.choices { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.choice { position: relative; }
.choice input { position: absolute; opacity: 0; inset: 0; }
.choice span {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0.5rem 0.9rem;
  font: 500 var(--label-lg)/1 var(--ui); letter-spacing: 0.06em;
  border: 1px solid var(--rule); color: var(--body); cursor: pointer;
  transition: border-color var(--dur-2) var(--ease), color var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}
.choice input:checked + span {
  border-color: var(--accent-lit); color: var(--heading);
  background: color-mix(in oklab, var(--accent-lit) 14%, transparent);
}
.choice input:focus-visible + span { outline: 2px solid var(--focus-ring); outline-offset: 3px; }

/* The status line. It says what actually happened, in words. */
.form__status { font: 400 var(--s-1)/1.55 var(--ui); color: var(--body); min-height: 1.55em; }
.form__status[data-state="ok"]      { color: var(--accent); }
.form__status[data-state="pending"] { color: var(--quiet); }   /* the JS sets "pending" — keep these in step */
.form__status[data-state="error"]   { color: var(--crabapple); }
.band--paper .form__status[data-state="error"], .on-light .form__status[data-state="error"] { color: #a00840; }

.news { border: 1px solid var(--rule); padding: clamp(1.3rem, 3vw, 2.2rem); background: var(--panel-bg); }
.news__form { display: grid; gap: 0.7rem; margin-top: 1.2rem; }
@media (min-width: 34rem) { .news__form { grid-template-columns: 1fr auto; align-items: start; } }
@media (min-width: 34rem) { .news__form .form__status { grid-column: 1 / -1; } }


/* ==========================================================================
   21 THE NAME
   AUR from Aurora Borealis; CO from Orko, Sanskrit for Sun. A northern light
   and a sun — which is, conveniently and not coincidentally, gold on black.
   This is the one band where the gold is the literal subject rather than the
   trim, so it is the one place the gold is allowed to be large.
   ========================================================================== */

.name { position: relative; overflow: hidden; }
.name__halves { display: grid; gap: 1px; background: var(--rule); margin-top: clamp(2rem, 4vw, 3rem); }
/* NOTE: this must never be put directly on .shell. Its background paints the
   shell's inline padding, which no grid item reaches, and you get two grey
   strips down the outside edges. Always nest it inside the shell. */
@media (min-width: 46rem) { .name__halves { grid-template-columns: 1fr 1fr; } }
.name__half { background: var(--obs); padding: clamp(1.4rem, 3vw, 2.4rem); position: relative; overflow: hidden; }
.name__syl {
  font-family: var(--display); font-size: var(--s5); line-height: 1;
  letter-spacing: 0.06em; color: var(--champ);
  font-variation-settings: 'opsz' 100, 'SOFT' 14, 'WONK' 0;
}
.name__from { display: block; margin: 0.7rem 0 0.9rem;
              font: 500 var(--label)/1.4 var(--ui); letter-spacing: 0.16em; text-transform: uppercase;
              color: var(--quiet); }
/* :not([class]) matters here. `.name__half p` is class+type and therefore
   beats `.name__syl`, which is class only — without this the AUR and CO
   syllables rendered at 13px instead of at display size. Any rule in this file
   that sizes a bare type inside a block must be scoped the same way. */
.name__half p:not([class]) { font-size: var(--s-1); }
/* The aurora half gets a cold light; the sun half a warm one. One gradient
   each, and they never meet — palette law rule 6. */
.name__half--aur::before {
  content: ""; position: absolute; inset: -40% -10% auto -10%; height: 70%; z-index: -1;
  background: radial-gradient(60% 100% at 30% 100%, color-mix(in oklab, var(--turquoise) 16%, transparent), transparent 70%);
}
.name__half--co::before {
  content: ""; position: absolute; inset: auto -10% -40% -10%; height: 70%; z-index: -1;
  background: radial-gradient(60% 100% at 70% 0%, color-mix(in oklab, var(--gilt) 20%, transparent), transparent 70%);
}


/* ==========================================================================
   22 FOOTER
   Duplicated in all 8 HTML files between SHARED CHROME markers.
   ========================================================================== */

.footer { border-top: 1px solid var(--rule-gold);  /* one gold rule closes the page */ padding-block: var(--band) 2.4rem; background: var(--obs); }
.footer__grid { display: grid; gap: clamp(1.6rem, 4vw, 3rem); }
@media (min-width: 52rem) { .footer__grid { grid-template-columns: 1.3fr 1fr 1fr; } }
.footer h2 { font-size: var(--s2); }
/* These are <h2> rather than <h3> so the heading order stays sequential on
   404.html, which has an h1 and no h2 of its own. They are styled as labels
   because that is what they read as. */
.footer__col h2 { font-size: var(--s-1); font-family: var(--ui); font-weight: 500;
                  letter-spacing: 0.16em; text-transform: uppercase; color: var(--quiet);
                  line-height: 1.3; }
.footer__col ul { list-style: none; margin-top: 0.9rem; display: grid; gap: 0.5rem; }
.footer__col a { text-decoration: none; color: var(--body); font-size: var(--s-1); }
.footer__col a:hover { color: var(--accent); }

.socials { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1rem; }
.social {
  display: inline-flex; align-items: center; gap: 0.45rem; min-height: 44px; padding: 0.5rem 0.85rem;
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.12em; text-transform: uppercase;
  border: 1px solid var(--rule); color: var(--body); text-decoration: none;
}
.social:hover { color: var(--accent); border-color: var(--accent-lit); }
/* A deliberate placeholder, not a link: no TikTok account exists on record and
   a guessed handle would send visitors to a stranger. Dashed, like every other
   placeholder on this site. */
.social--soon { border-style: dashed; color: var(--quiet); cursor: default; }

.footer__base {
  margin-top: var(--band); padding-top: 1.4rem; border-top: 1px solid var(--hair-soft);
  display: flex; flex-wrap: wrap; gap: 0.6rem 1.4rem; align-items: baseline;
}
.footer__base p { font: 400 var(--label)/1.6 var(--ui); color: var(--quiet); }


/* ==========================================================================
   23 UTILITIES
   ========================================================================== */

.center { text-align: center; }
.center .lede, .center .band__head { margin-inline: auto; }
.hairline { height: 1px; background: var(--rule); border: 0; margin: 0; }
.pill {
  display: inline-flex; align-items: center; gap: 0.45rem; padding: 0.35rem 0.7rem;
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.12em; text-transform: uppercase;
  border: 1px solid var(--rule); color: var(--body);
}
.pill--gold { color: var(--accent); border-color: var(--accent-lit); }
.note {
  border-left: 1px solid var(--accent-lit); padding: 0.2rem 0 0.2rem 1rem;
  font-size: var(--s-1); color: var(--body);
}


/* --------------------------------------------------------------------------
   CONTROLS THAT CANNOT WORK WITHOUT JAVASCRIPT ARE NOT RENDERED.
   A button that does nothing when pressed is worse than an absent one. Each of
   these enhances content that is already fully visible with JS off: every
   project is shown unfiltered, the tip in the markup is a real tip, and the
   email address beside the copy button is a live mailto link.
   -------------------------------------------------------------------------- */
html.no-js #projectFilters,
html.no-js #tipNext,
html.no-js [data-copy] { display: none; }

/* ==========================================================================
   24 NO-JS, REDUCED MOTION, PRINT
   The site is complete in all three states. Every unfinished state in this
   stylesheet is scoped to html:not(.no-js), so this block mostly exists to
   switch OFF motion rather than to switch content on.
   ========================================================================== */

/* <html class="no-js"> is an ATTRIBUTE in the markup that site.js removes.
   It can never be set by an inline script: with JavaScript disabled that
   script would not run, so every animated element would keep its unfinished
   state and the page would come up blank. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    animation-timeline: auto !important;   /* a scroll timeline ignores duration */
    scroll-behavior: auto !important;
  }
  /* Put everything into its finished state rather than merely freezing it. */
  html:not(.no-js) [data-reveal] { opacity: 1 !important; transform: none !important; }
  html:not(.no-js) .ap--opens .ap__win { clip-path: inset(0 round var(--aperture)) !important; }
  html:not(.no-js) .develops { filter: none !important; }
  .hero__bg img { filter: grayscale(1) contrast(1.1) brightness(0.34) !important; }
}

@media print {
  .header, .nav-panel, .progress, .skip, .hero__bg, .slot, .news, .socials { display: none !important; }
  :root { --obs: #fff; --on-dark: #000; --heading: #000; --body: #222; --quiet: #444;
          --accent: #6f5a12; --accent-lit: #6f5a12; --rule: #bbb; }
  body { background: #fff; color: #000; }
  .band { padding-block: 1.2rem; break-inside: avoid; }
  .band--dark { background: #fff !important; }
  .band--dark::after { display: none !important; }
  .develops { filter: none !important; }
  a { text-decoration: underline; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.8em; color: #555; }
  .ap__win { box-shadow: 0 0 0 1px #999 !important; }
}


/* ==========================================================================
   25 HOOKS THE SCRIPT SETS
   Everything site.js touches, in one place, so it is obvious what would stop
   working if a class name changed. Nothing here is required for the page to
   be readable — see section 24.
   ========================================================================== */

/* The mobile panel is full-screen, so the page behind it must not scroll. */
body.nav-open { overflow: hidden; }

/* Tip of the week: a small turn when the visitor asks for another one. */
@media (prefers-reduced-motion: no-preference) {
  .tip.is-turning { animation: tipTurn var(--dur-3) var(--ease); }
  @keyframes tipTurn {
    from { opacity: 0.35; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }
}

/* A film slot that is actually playing loses its caption overlay. */
.slot.is-playing .slot__body { opacity: 0; pointer-events: none; }
.slot.is-playing video { z-index: 3; }

/* Project filters (projects.html). With JS off no button exists and every
   card is visible, which is the correct default. */
.filters { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.filters button {
  min-height: 44px; padding: 0.5rem 0.9rem;
  font: 500 var(--label)/1 var(--ui); letter-spacing: 0.12em; text-transform: uppercase;
  border: 1px solid var(--rule); color: var(--body);
  transition: border-color var(--dur-2) var(--ease), color var(--dur-2) var(--ease),
              background var(--dur-2) var(--ease);
}
.filters button:hover { color: var(--heading); border-color: var(--accent-lit); }
.filters button.is-active {
  color: var(--heading); border-color: var(--accent-lit);
  background: color-mix(in oklab, var(--accent-lit) 14%, transparent);
}
