/* ===== css/tokens.css ===== */
/* ============================================================================
   tokens.css — Imperial Arowana design system.
   OWNER: imperial-scaffold. Do not add page-specific values here.

   TWO LAYERS, and the distinction is load-bearing:
     1. PALETTE primitives (--c-*)  — raw brand colour. Components must NOT read these.
     2. SEMANTIC roles (--surface/--text/--accent/...) — what every component reads.
   Semantic roles are re-pointed under [data-surface="deep"], so one attribute flips a
   section to a deep-water "cinema island" and every component inside stays AA-legible
   without the page author touching a colour. See CONTRACT.md §2.

   CONTRAST (measured, not assumed — WCAG 2.1 vs the surface each role sits on):
     ink   #121417 on paper #F7F4ED ... 16.80:1
     gold  #B08A3E on paper #F7F4ED ....  2.92:1  <-- FAILS body AND the 3:1 UI floor.
     gold  #836427 on paper #F7F4ED ....  5.01:1  <-- so gold TEXT on light = --accent-text
     seal  #A31621 on paper #F7F4ED ....  7.10:1
     seal  #A31621 on deep  #0E2C3F ....  1.86:1  <-- so the chop flips gold on deep
     gold  #B08A3E on deep  #0E2C3F ....  4.52:1
   --accent is ORNAMENT ONLY (rules, rings, seals, non-meaningful graphics).
   Any gold that carries meaning or text reads --accent-text. This is why the two exist.
   ========================================================================== */

:root{
  /* ---------- 1. PALETTE primitives — never referenced by components ---------- */
  --c-paper:      #F7F4ED;   /* warm paper — the site reads LIGHT (BRIEF §3) */
  --c-paper-2:    #F1EDE3;   /* alternating section */
  --c-paper-3:    #E8E3D6;   /* wells / insets */
  --c-ink:        #121417;
  --c-ink-2:      #484D54;
  --c-ink-3:      #5A5F66;
  --c-gold:       #B08A3E;   /* imperial gold — ornament */
  --c-gold-deep:  #836427;   /* gold that may carry text on light */
  --c-gold-lift:  #C79E4C;   /* gold that may carry text on deep */
  --c-seal:       #A31621;   /* seal red — stamps / CTA / accents, sparingly */
  --c-water:      #16405B;   /* deep water blue */
  --c-water-deep: #0E2C3F;   /* cinema-island ground (AA-clears gold text) */
  --c-bone:       #F3EFE4;   /* text on deep */
  --c-bone-2:     #C6D2DB;
  --c-bone-3:     #A9B9C4;

  /* ---------- bloodline motif (BRIEF §3: the 4 colours map to bloodlines) ------
     Semantic, not decorative: use these when the thing IS a bloodline. */
  --bl-golden: var(--c-gold);
  --bl-red:    var(--c-seal);
  --bl-blue:   var(--c-water);
  --bl-black:  var(--c-ink);

  /* ---------- 2. SEMANTIC roles — components read ONLY these ---------- */
  --surface:      var(--c-paper);
  --surface-2:    var(--c-paper-2);
  --surface-3:    var(--c-paper-3);
  --text:         var(--c-ink);      /* 16.80:1 */
  --text-2:       var(--c-ink-2);    /*  7.76:1 — secondary */
  --text-3:       var(--c-ink-3);    /*  5.86:1 — muted / captions. Floor: nothing below AA exists. */
  --accent:       var(--c-gold);     /* ORNAMENT ONLY */
  --accent-text:  var(--c-gold-deep);/*  5.01:1 — gold that carries text/meaning */
  --seal-ink:     var(--c-seal);     /*  7.10:1 — the chop + CTA */
  --seal-on:      var(--c-paper);    /* text on a seal-red fill (7.10:1) */
  --water:        var(--c-water);

  --rule:         rgba(18, 20, 23, .12);   /* hairline — design.md §3 premium recipe */
  --rule-2:       rgba(18, 20, 23, .22);
  --rule-accent:  rgba(176, 138, 62, .38); /* the 1px inset gold ring on media frames */
  --wash-accent:  rgba(176, 138, 62, .10);
  --wash-seal:    rgba(163, 22, 33, .08);

  --sh-1: 0 1px 2px rgba(18, 20, 23, .04), 0 6px 16px -8px rgba(18, 20, 23, .10);
  --sh-2: 0 2px 6px rgba(18, 20, 23, .05), 0 22px 50px -18px rgba(18, 20, 23, .18);

  /* ---------- 3. TYPE ----------
     Two families, max (BRIEF §3). Cormorant Garamond = display; Inter = body.
     HIERARCHY IS DESIGNED, NOT SET (taste.md 2026-07-12: "lane = costume,
     hierarchy = craft"). The rhythm: as size DESCENDS, weight ASCENDS and
     tracking OPENS — big+thin+tight (display) → small+bold+wide (micro-label).
     That inversion is the editorial signal; keep it. */
  --f-display: "Cormorant Garamond", "Cormorant", Georgia, serif;
  --f-body:    "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* clamp(): ALWAYS spaces around + and - or the WHOLE declaration is invalid
     and the font silently falls back (websites.md §12b — shipped live once). */
  --t-display: clamp(2.6rem, 1.4rem + 4.4vw, 5rem);        /* hero ONLY. Capped 5rem/80px:
                                                              taste.md — 118px read LESS clean. */
  --t-h1:      clamp(2.1rem, 1.3rem + 2.8vw, 3.6rem);      /* section openers */
  --t-h2:      clamp(1.5rem, 1.15rem + 1.3vw, 2.25rem);
  --t-h3:      clamp(1.125rem, 1.05rem + .35vw, 1.375rem);
  --t-lede:    clamp(1.0625rem, 1rem + .45vw, 1.3125rem);
  --t-body:    clamp(1rem, .96rem + .18vw, 1.0625rem);
  --t-small:   .875rem;
  --t-micro:   .6875rem;                                    /* small-caps tracked label */

  --w-display: 300;   /* thinnest-luxe Cormorant (design.md §4 cheat-sheet) */
  --w-h1:      400;
  --w-h2:      500;
  --w-h3:      600;
  --w-body:    400;
  --w-micro:   500;

  --lh-display: .96;  /* tight leading on the big serif = half the luxe (meridian §15) */
  --lh-h:       1.06;
  --lh-lede:    1.5;
  --lh-body:    1.65;

  --ls-display: -.022em;
  --ls-h:       -.014em;
  --ls-body:     0;
  --ls-micro:    .18em;

  --prose: 68ch;      /* line length 45–75ch (design.md §4) */

  /* ---------- 4. SPACE ----------
     Rhythm is INTENTIONAL, not uniform (design.md §6): tight within a group,
     generous between groups. Use the semantic tiers, not raw steps, for layout. */
  --s-1: .25rem; --s-2: .5rem;  --s-3: .75rem; --s-4: 1rem;
  --s-5: 1.5rem; --s-6: 2rem;   --s-7: 3rem;   --s-8: 4.5rem;

  --space-section: clamp(5rem, 3rem + 6.5vw, 9.5rem);   /* between sections — generous */
  --space-block:   clamp(2rem, 1.4rem + 2.2vw, 3.5rem); /* between blocks in a section */
  --space-group:   clamp(.75rem, .6rem + .5vw, 1.1rem); /* within a group — tight */
  --gutter:        clamp(1.25rem, 4vw, 3rem);
  --maxw:          1280px;
  --nav-h:         clamp(4.25rem, 3.4rem + 2.4vw, 5.5rem);

  /* ---------- 5. RADII ----------
     Deliberately near-square: editorial luxury is sharp. Uniform radius everywhere
     is a named slop tell (design.md §2). The pill is reserved for the CTA + seal. */
  --r-0:    0;
  --r-1:    2px;
  --r-pill: 999px;

  /* ---------- 6. MOTION ----------
     Easing set lifted from the Volta Skai study (design.md §14) — proven. */
  --ease-out:     cubic-bezier(.16, 1, .3, 1);
  --ease-hover:   cubic-bezier(0, .3, .3, 1);
  --ease-reveal:  cubic-bezier(0, 0, .1, 1);
  --ease-inout:   cubic-bezier(.77, 0, .175, 1);
  --ease-overlay: cubic-bezier(.25, .005, 0, 1);
  --ease-card:    cubic-bezier(.3, 0, 0, 1);

  --d-1: 180ms; --d-2: 320ms; --d-3: 600ms; --d-4: 900ms;

  /* ---------- 7. Z-INDEX LADDER — one source of truth. Never invent a z-index. -- */
  --z-base:    0;
  --z-media:   1;
  --z-content: 2;
  --z-seal:    3;
  --z-nav:     100;
  --z-drawer:  110;
  --z-overlay: 120;
  --z-grain:   9998;
  --z-cursor:  9999;
}

/* ============================================================================
   DEEP SURFACE — the "cinema island" (BRIEF §3).
   Put data-surface="deep" on any <section> that goes full-bleed immersive.
   Every semantic role re-points; components inside need ZERO colour changes.
   Verified AA on #0E2C3F: text 12.60 · text-2 9.40 · text-3 7.19 · accent-text 5.81.
   The chop flips gold because seal red on deep is 1.86:1 (unusable).
   ========================================================================== */
[data-surface="deep"]{
  --surface:     var(--c-water-deep);
  --surface-2:   #123449;
  --surface-3:   #16405B;
  --text:        var(--c-bone);
  --text-2:      var(--c-bone-2);
  --text-3:      var(--c-bone-3);
  --accent:      var(--c-gold);
  --accent-text: var(--c-gold-lift);
  --seal-ink:    var(--c-gold-lift);   /* seal red is illegible on deep — flip to gold */
  --seal-on:     var(--c-water-deep);
  --rule:        rgba(243, 239, 228, .16);
  --rule-2:      rgba(243, 239, 228, .28);
  --rule-accent: rgba(199, 158, 76, .45);
  --wash-accent: rgba(199, 158, 76, .12);
  --sh-1: 0 1px 2px rgba(0, 0, 0, .20), 0 6px 16px -8px rgba(0, 0, 0, .45);
  --sh-2: 0 2px 6px rgba(0, 0, 0, .24), 0 22px 50px -18px rgba(0, 0, 0, .55);

  background: var(--surface);
  color: var(--text);
}

/* Ink surface — for the footer / a hard black moment. Same contract. */
[data-surface="ink"]{
  --surface:     var(--c-ink);
  --surface-2:   #1B1E23;
  --surface-3:   #24282E;
  --text:        var(--c-bone);
  --text-2:      var(--c-bone-2);
  --text-3:      #939AA3;
  --accent:      var(--c-gold);
  --accent-text: var(--c-gold);        /* 5.76:1 on ink */
  --seal-ink:    var(--c-gold);
  --seal-on:     var(--c-ink);
  --rule:        rgba(243, 239, 228, .14);
  --rule-2:      rgba(243, 239, 228, .26);
  --rule-accent: rgba(176, 138, 62, .42);
  --wash-accent: rgba(176, 138, 62, .10);

  background: var(--surface);
  color: var(--text);
}


/* ===== css/base.css ===== */
/* ============================================================================
   base.css — reset, typography, chrome, shared components.
   OWNER: imperial-scaffold. Page authors: do NOT edit. Add to css/pages/<key>.css.

   Every component here reads SEMANTIC tokens only (--surface/--text/--accent/...),
   so all of it works unchanged inside [data-surface="deep"] and ="ink".
   ========================================================================== */

/* ---------------------------------------------------------------- 1. reset */
*, *::before, *::after{ box-sizing: border-box; }

html{ -webkit-text-size-adjust: 100%; }

/* NEVER put transform / filter / backdrop-filter on html or body: it makes them a
   containing block and traps every position:fixed descendant (the grain overlay,
   the nav, the drawer). websites.md §8 — cost a whole debugging session once. */
body{
  margin: 0;
  background: var(--surface);
  color: var(--text);
  font-family: var(--f-body);
  font-size: var(--t-body);
  font-weight: var(--w-body);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd{ margin: 0; }
ul, ol{ margin: 0; padding: 0; list-style: none; }
a{ color: inherit; text-decoration: none; }
img, video, svg, canvas{ display: block; max-width: 100%; }
img, video{ height: auto; }
button, input, select, textarea{ font: inherit; color: inherit; }
button{ background: none; border: 0; padding: 0; cursor: pointer; }

/* The ONE sanctioned !important in this codebase.
   [hidden] is (0,1,0) — the same specificity as any single class. A page author's
   `.card{display:block}` concatenated AFTER base.css wins on source order and the
   "hidden" element stays visible. Cost a real bug on meridian (design.md §14).
   Enforcing UA semantics against author rules is exactly what !important is for. */
[hidden]{ display: none !important; }

/* ------------------------------------------------- 2. motion / a11y floors */
/* Reduced motion is a FLOOR, not a downgrade: content must be fully visible and
   still. JS honours this too (motion-kit + site.js both check it). */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}
@media (prefers-reduced-motion: no-preference){
  html{ scroll-behavior: smooth; }
}

:focus-visible{
  outline: 2px solid var(--text);
  outline-offset: 3px;
  border-radius: var(--r-1);
}
/* A focus ring must contrast with the surface it lands on — an ink ring on the
   seal-red CTA is invisible (design.md §7, shipped this bug once). */
.nav-cta:focus-visible,
.btn--seal:focus-visible{ outline-color: var(--c-paper); outline-offset: 2px; }

.skip{
  position: absolute; left: var(--s-4); top: -100px; z-index: var(--z-cursor);
  background: var(--c-ink); color: var(--c-paper);
  padding: var(--s-3) var(--s-5); font-size: var(--t-small);
  transition: top var(--d-1) var(--ease-out);
}
.skip:focus{ top: var(--s-4); }

.visually-hidden{
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* The shared SVG <defs> sprite (partials/shared/defs.html) — build.mjs injects one
   copy per page right after <body>, so the chop's filter + arc are defined ONCE and
   every .seal on the page references them. Duplicating <defs> per seal would mean
   duplicate ids. It must occupy no space. */
.ia-defs{
  position: absolute;
  width: 0; height: 0;
  overflow: hidden;
  pointer-events: none;
}

/* ---------------------------------------------- 3. atmospheric paper grain */
/* design.md §3: "the single biggest crafted-not-templated tell". Fixed, non-
   interactive, soft-light so it adds tooth to the paper without shifting colour. */
body::after{
  content: "";
  position: fixed; inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: .05;
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: reduce){ body::after{ opacity: .03; } }

/* ------------------------------------------------------------ 4. typography
   The designed rhythm (tokens.css §3): size DESCENDS as weight ASCENDS and
   tracking OPENS. Do not flatten this — it is the hierarchy, not decoration. */
.display, h1, h2, h3{
  font-family: var(--f-display);
  /* Cormorant merges fi/ff into one fused glyph — the "f looks fucked" complaint.
     No stylistic set fixes it; killing ligatures does. BRIEF §3 mandates this. */
  font-variant-ligatures: none;
  font-kerning: normal;
}

.display{
  font-size: var(--t-display);
  font-weight: var(--w-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
}
h1{
  font-size: var(--t-h1);
  font-weight: var(--w-h1);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
}
h2{
  font-size: var(--t-h2);
  font-weight: var(--w-h2);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
}
h3{
  font-size: var(--t-h3);
  font-weight: var(--w-h3);
  line-height: 1.3;
  letter-spacing: 0;
}

/* Charlie reads slanted display type as "off" — accent with gold COLOUR, never a
   slant (design.md §9c, websites.md §14). .gold is the sanctioned emphasis device,
   and it is for PHRASES, not stray single words (websites.md §8). */
.gold{ color: var(--accent-text); }
/* NO ITALICS anywhere on this site (client rule, Charlie 2026-07-18) — emphasis is
   colour, never slant. Page CSS must not re-italicise; the verify gate greps for it. */
em, i, cite, q, dfn, address, blockquote{ font-style: normal; }
em, i{ color: var(--accent-text); }

.lede{
  font-size: var(--t-lede);
  line-height: var(--lh-lede);
  color: var(--text-2);
  max-width: var(--prose);
}
.prose{ max-width: var(--prose); }
.prose p + p{ margin-top: var(--s-4); }
.muted{ color: var(--text-3); }

/* micro-label — the small-caps tracked kicker. The TDA signal. */
.micro{
  display: inline-block;
  font-family: var(--f-body);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  line-height: 1;
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--text-3);
}
.micro--accent{ color: var(--accent-text); }
.micro--seal{ color: var(--seal-ink); }
/* micro-label with a leading rule — use where a kicker opens a section */
.micro--rule{ display: inline-flex; align-items: center; gap: var(--s-3); }
.micro--rule::before{
  content: ""; width: clamp(1.5rem, 4vw, 3rem); height: 1px;
  background: var(--accent); opacity: .8;
}

/* tabular numerals for anything that counts (design.md §4) */
.num{ font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------- 5. layout primitives */
.wrap{ width: min(100% - var(--gutter) * 2, var(--maxw)); margin-inline: auto; }
.wrap--narrow{ width: min(100% - var(--gutter) * 2, 880px); margin-inline: auto; }

.section{ position: relative; padding-block: var(--space-section); }
.section--tight{ padding-block: var(--space-block); }
.section--flush{ padding-block: 0; }
.section--alt{ background: var(--surface-2); }

/* full-viewport stacked section — the meridian narrative unit (BRIEF §3 move 2) */
.section--full{
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Fixed nav + a bottom/centre-aligned full-height hero = the nav eats the copy on
   mobile, where content is taller. Any section that sits UNDER the nav needs this.
   websites.md §8 (rts-link). Screenshot 390 to prove it. */
.hero-safe{ padding-top: calc(var(--nav-h) + var(--s-4)); }
@media (max-width: 640px){
  .hero-safe{ padding-top: calc(var(--nav-h) + var(--s-6)); }
}

.stack{ display: flex; flex-direction: column; gap: var(--space-group); }
.stack--block{ gap: var(--space-block); }

/* ------------------------------------------------------- 6. framed media ⭐
   THE fill-a-box pattern. Absolute-fill, never in-flow width/height:100%.
   In-flow media in an auto-height cell fails in BOTH directions: it collapses to
   ~46px in a grid span, AND it inflates the row past its min-height when the
   intrinsic ratio resolves (websites.md §8, logged twice, Charlie caught both).
   Absolute-fill sidesteps flow/grid sizing entirely. Use .frame for ALL media. */
.frame{
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--frame-ratio, 4 / 3);
  background: var(--surface-3);
  border-radius: var(--r-0);
}
.frame > img,
.frame > video{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: var(--frame-pos, 50% 50%);
}
.frame > iframe{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0;
}
/* Click-to-play YouTube facade (home 65-film + media page). No third-party request
   beyond the poster until the visitor clicks — the iframe is injected by page JS
   ([data-yt]), so the pages stay free of YouTube script at load. */
.yt-play{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: grid; place-items: center;
  background: linear-gradient(to top, rgb(10 15 20 / .38), rgb(10 15 20 / 0) 45%);
  border: 0; padding: 0;
  cursor: pointer;
  z-index: var(--z-content);
}
.yt-play i{
  display: grid; place-items: center;
  width: 64px; height: 64px;
  border-radius: 50%;
  background: var(--c-paper);
  box-shadow: inset 0 0 0 1px var(--rule-accent), 0 10px 30px rgb(10 15 20 / .35);
  transition: transform .3s var(--ease-hover);
}
.yt-play svg{ width: 18px; height: 20px; fill: var(--c-ink); translate: 2px 0; }
.yt-play:hover i,
.yt-play:focus-visible i{ transform: scale(1.08); }
.yt-play:focus-visible{ outline: 2px solid var(--rule-accent); outline-offset: -2px; }

/* the 1px inset gold ring — the micro-detail that reads expensive (design.md §14) */
.frame::after{
  content: "";
  position: absolute; inset: 0;
  box-shadow: inset 0 0 0 1px var(--rule-accent);
  pointer-events: none;
  z-index: var(--z-content);
}
.frame--plain::after{ display: none; }
.frame--fill{ aspect-ratio: auto; height: 100%; }

/* caption sits OUTSIDE the frame — editorial, not an overlay band */
.frame-cap{
  margin-top: var(--s-3);
  font-size: var(--t-small);
  color: var(--text-3);
}

/* ------------------------------------------------------ 7. the red-seal chop ⭐
   BRIEF §3: "EST. 2003" chop as a brand device. A real chop is ink pressed into
   paper — multiply blends it INTO the grain instead of sitting on top, and the
   slight rotation says hand-stamped, not placed. Markup snippet: CONTRACT.md §5.
   Colour reads --seal-ink, which flips to gold on deep surfaces (seal red on
   #0E2C3F is 1.86:1 — unusable). */
.seal{
  --seal-size: clamp(72px, 8vw, 104px);
  position: relative;
  z-index: var(--z-seal);
  width: var(--seal-size);
  height: var(--seal-size);
  flex: 0 0 auto;
  color: var(--seal-ink);
  transform: rotate(-4deg);
  mix-blend-mode: multiply;
  opacity: .92;
}
[data-surface="deep"] .seal,
[data-surface="ink"] .seal{ mix-blend-mode: normal; opacity: .95; }

.seal svg{ width: 100%; height: 100%; overflow: visible; }
.seal .seal-ring{ fill: none; stroke: currentColor; }
.seal .seal-type{
  fill: currentColor;
  font-family: var(--f-body);
  font-weight: 600;
  font-size: 6.4px;
  letter-spacing: 1.15px;
}
.seal .seal-year{
  fill: currentColor;
  font-family: var(--f-display);
  font-variant-ligatures: none;
  font-weight: 500;
  font-size: 21px;
  letter-spacing: .5px;
}
.seal .seal-est{
  fill: currentColor;
  font-family: var(--f-body);
  font-weight: 600;
  font-size: 5.6px;
  letter-spacing: 1.6px;
}
.seal--lg{ --seal-size: clamp(104px, 12vw, 152px); }
.seal--sm{ --seal-size: 64px; }

/* The client's REAL mark (twin arowana + 皇宫 medallion, 564×364 PNG), replacing the
   invented SVG chop site-wide (Charlie 2026-07-18 "change to their logo"). Landscape,
   so width-driven; ink variant on light surfaces, white variant on deep. */
.brand-chop{ flex: 0 0 auto; height: auto; }
.brand-chop--foot{ width: clamp(120px, 13vw, 160px); }
.brand-chop--sm{ width: 104px; }

/* ------------------------------------------------------------- 8. buttons */
.btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  min-height: 44px;                 /* touch target floor (design.md §7) */
  padding: var(--s-3) var(--s-6);
  border-radius: var(--r-pill);
  font-family: var(--f-body);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--d-2) var(--ease-hover),
              color var(--d-2) var(--ease-hover),
              border-color var(--d-2) var(--ease-hover),
              transform var(--d-2) var(--ease-hover);
}
.btn:active{ transform: translateY(1px); }

.btn--seal{
  background: var(--seal-ink);
  color: var(--seal-on);            /* 7.10:1 */
  border: 1px solid var(--seal-ink);
}
.btn--seal:hover{ background: #8C121C; border-color: #8C121C; }
[data-surface="deep"] .btn--seal:hover,
[data-surface="ink"] .btn--seal:hover{ background: var(--c-gold); border-color: var(--c-gold); }

.btn--ghost{
  background: transparent;
  color: var(--text);
  border: 1px solid var(--rule-2);
}
.btn--ghost:hover{ border-color: var(--text); background: var(--wash-accent); }

/* Button cluster — use this for ANY row of buttons. Never hand-roll `display:flex`.
   .btn is white-space:nowrap by design (a CTA label must not break mid-word), so a
   row without flex-wrap overflows the viewport the moment two CTAs don't fit: an
   Enquire + The farm pair measured 415px against a 390px viewport, i.e. a horizontal
   scrollbar on mobile. Wrapping at the container is the only place that can't be
   forgotten five times by five page authors. */
.btns{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
}
.btns--loose{ gap: var(--s-5); }

/* text link with an underline that draws on hover — compositor-friendly */
.link{
  position: relative;
  color: var(--text);
  padding-bottom: 2px;
}
.link::after{
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 100% 50%;
  transition: transform var(--d-2) var(--ease-hover);
}
.link:hover::after{ transform: scaleX(1); transform-origin: 0 50%; }

/* hairline rule */
.rule{ height: 1px; background: var(--rule); border: 0; margin: 0; }
.rule--accent{ background: var(--rule-accent); }

/* ---------------------------------------------------------------- 9. nav
   The CTA is a SIBLING of .nav-links, never a child. Logged bug #1 (design.md §8):
   `.nav-cta{padding}` (0,1,0) lost to `.nav-links a{padding}` (0,1,1) and the
   button's horizontal padding collapsed to 0. Structure sidesteps it; the scoped
   rule below is the belt to that braces. */
.nav{
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-nav);
  height: var(--nav-h);
  display: flex;
  align-items: center;
  gap: var(--s-5);
  padding-inline: var(--gutter);
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background var(--d-3) var(--ease-out),
              border-color var(--d-3) var(--ease-out);
}
/* frosted paper once scrolled. NOTE: backdrop-filter makes .nav a containing
   block for fixed descendants — which is exactly why the drawer + scrim live at
   BODY level, not inside <header>. Never move them in here. websites.md §8. */
.nav.is-scrolled{
  background: color-mix(in srgb, var(--c-paper) 82%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  border-bottom-color: var(--rule);
}
@supports not (backdrop-filter: blur(1px)){
  .nav.is-scrolled{ background: var(--c-paper); }
}

.nav-brand{
  display: flex;
  align-items: center;
  gap: clamp(.55rem, .4rem + .5vw, .8rem);
  margin-right: auto;
  color: var(--c-ink);
}
/* The client emblem (twin arowana medallion). Height-capped to sit inside --nav-h
   with breathing room; width follows the 564×364 intrinsic ratio. */
.nav-brand-emblem{
  height: clamp(2.1rem, 1.7rem + 1vw, 2.6rem);
  width: auto;
  flex: none;
}
.nav-brand-text{
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.nav-brand-mark{
  font-family: var(--f-display);
  font-variant-ligatures: none;
  font-size: clamp(1.05rem, .9rem + .5vw, 1.35rem);
  font-weight: 500;
  line-height: 1;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.nav-brand-sub{
  font-size: .5625rem;
  font-weight: 500;
  line-height: 1;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-ink-3);
}

.nav-links{ display: flex; align-items: center; gap: clamp(1rem, 2vw, 2rem); }
.nav-links a{
  position: relative;
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-ink-2);
  padding-block: var(--s-2);
  transition: color var(--d-2) var(--ease-hover);
}
.nav-links a::after{
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--c-gold);
  transform: scaleX(0);
  transform-origin: 100% 50%;
  transition: transform var(--d-2) var(--ease-hover);
}
.nav-links a:hover{ color: var(--c-ink); }
.nav-links a:hover::after{ transform: scaleX(1); transform-origin: 0 50%; }
.nav-links a[aria-current="page"]{ color: var(--c-ink); }
.nav-links a[aria-current="page"]::after{ transform: scaleX(1); }

/* scoped to out-specify `.nav-links a` even if the CTA is ever nested (0,2,1) */
.nav-cta,
.nav .nav-links a.nav-cta{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 40px;
  padding: var(--s-2) var(--s-5);
  border-radius: var(--r-pill);
  background: var(--c-seal);
  color: var(--c-paper);
  border: 1px solid var(--c-seal);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  transition: background var(--d-2) var(--ease-hover), border-color var(--d-2) var(--ease-hover);
}
.nav-cta::after{ display: none; }            /* kill the inherited link underline */
.nav-cta:hover{ background: #8C121C; border-color: #8C121C; color: var(--c-paper); }

.nav-burger{
  display: none;
  width: 44px; height: 44px;
  align-items: center;
  justify-content: center;
  color: var(--c-ink);
}
.nav-burger i{ display: block; width: 20px; height: 12px; position: relative; }
.nav-burger i b{
  position: absolute; left: 0; right: 0; height: 1.5px;
  background: currentColor;
  transition: transform var(--d-2) var(--ease-out), opacity var(--d-1) linear;
}
.nav-burger i b:nth-child(1){ top: 0; }
.nav-burger i b:nth-child(2){ top: 50%; margin-top: -.75px; }
.nav-burger i b:nth-child(3){ bottom: 0; }
.nav-burger[aria-expanded="true"] i b:nth-child(1){ transform: translateY(5.25px) rotate(45deg); }
.nav-burger[aria-expanded="true"] i b:nth-child(2){ opacity: 0; }
.nav-burger[aria-expanded="true"] i b:nth-child(3){ transform: translateY(-5.25px) rotate(-45deg); }

@media (max-width: 900px){
  .nav-links{ display: none; }
  .nav-cta{ display: none; }
  .nav-burger{ display: inline-flex; }
}

/* --------------------------------------------- 10. mobile drawer (body-level)
   Pinned EXPLICITLY with top/height — never inset:0 or bottom:0. If an ancestor
   ever gains a filter/transform, inset-based pinning silently collapses to that
   ancestor's box. Explicit top+height survives it. websites.md §8 (the nasty one). */
.nav-scrim{
  position: fixed;
  top: 0; left: 0;
  width: 100vw; height: 100dvh;
  z-index: var(--z-drawer);
  background: rgba(18, 20, 23, .38);
  opacity: 0;
  visibility: hidden;                /* NOT transform-offscreen: fixed elements paint
                                        into fullPage screenshots even when translated
                                        away (websites.md §8). visibility actually hides. */
  transition: opacity var(--d-3) var(--ease-overlay), visibility 0s linear var(--d-3);
}
.nav-drawer{
  position: fixed;
  top: 0; right: 0;
  width: min(88vw, 400px);
  height: 100dvh;
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
  padding: calc(var(--nav-h) + var(--s-6)) var(--gutter) var(--s-8);
  background: var(--c-paper);
  border-left: 1px solid var(--rule);
  box-shadow: var(--sh-2);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform var(--d-3) var(--ease-overlay), visibility 0s linear var(--d-3);
  overflow-y: auto;
  overscroll-behavior: contain;
}
html.menu-open .nav-scrim{
  opacity: 1; visibility: visible;
  transition: opacity var(--d-3) var(--ease-overlay), visibility 0s;
}
html.menu-open .nav-drawer{
  transform: none; visibility: visible;
  transition: transform var(--d-3) var(--ease-overlay), visibility 0s;
}

/* iOS scroll-lock: overflow:hidden does NOT hold position on iOS — the page flings.
   Position-fixed body lock + restore is the real fix (design.md §16, 2 failed
   attempts before this). site.js owns the JS half. */
html.menu-open{ overflow: hidden; }
html.menu-open body{
  position: fixed;
  left: 0; right: 0;
  width: 100%;
  overflow: hidden;
}

.nav-drawer-links{ display: flex; flex-direction: column; gap: var(--s-2); }
.nav-drawer-links a{
  font-family: var(--f-display);
  font-variant-ligatures: none;
  font-size: 1.65rem;
  font-weight: 400;
  line-height: 1.35;
  color: var(--c-ink);
  border-bottom: 1px solid var(--rule);
  padding-block: var(--s-3);
  transition: color var(--d-2) var(--ease-hover);
}
.nav-drawer-links a:hover,
.nav-drawer-links a[aria-current="page"]{ color: var(--c-gold-deep); }
.nav-drawer-foot{ margin-top: auto; display: flex; flex-direction: column; gap: var(--s-4); }

/* ------------------------------------------------------------- 11. footer */
.foot{ padding-block: var(--space-block) var(--s-6); }
.foot-top{
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-block);
  align-items: start;
  padding-bottom: var(--space-block);
  border-bottom: 1px solid var(--rule);
}
.foot-brand-mark{
  font-family: var(--f-display);
  font-variant-ligatures: none;
  font-size: clamp(1.6rem, 1.1rem + 1.6vw, 2.4rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--text);
}
.foot-grid{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--space-block);
  padding-block: var(--space-block);
}
.foot-col{ display: flex; flex-direction: column; gap: var(--s-3); }
.foot-col h4{
  font-family: var(--f-body);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: var(--s-2);
}
.foot-col a, .foot-col p{ color: var(--text-2); font-size: var(--t-small); line-height: 1.55; }
.foot-col a{ transition: color var(--d-2) var(--ease-hover); }
.foot-col a:hover{ color: var(--accent-text); }
.foot-bottom{
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4) var(--s-6);
  align-items: center;
  justify-content: space-between;
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
  font-size: var(--t-small);
  color: var(--text-3);
}
.foot-bottom a:hover{ color: var(--accent-text); }

@media (max-width: 640px){
  .foot-top{ grid-template-columns: 1fr; }
}

/* ------------------------------------------------------- 17. language layer
   Two artifacts share this block: the .nav-lang toggle (both languages) and the
   .ia-gate entry overlay (EN home only — partials/home/05-gate.html). The gate
   doubles as a loading curtain: the page (and the hero film's data-src attach)
   keeps loading beneath it while the visitor chooses, so by the time the door
   opens the film has had seconds of head start. Bootstrap contract lives in
   build.mjs GATE_BOOT: html.ia-gate-open is set PRE-PAINT or not at all —
   the overlay never flashes for a returning visitor. */
.nav-lang{
  position: relative;
  flex: none;
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: .12em;
  color: var(--c-ink-2);
  padding-block: var(--s-2);
  transition: color var(--d-2) var(--ease-hover);
}
.nav-lang::after{
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--c-gold);
  transform: scaleX(0);
  transform-origin: 100% 50%;
  transition: transform var(--d-2) var(--ease-hover);
}
.nav-lang:hover{ color: var(--c-ink); }
.nav-lang:hover::after{ transform: scaleX(1); transform-origin: 0 50%; }
@media (max-width: 900px){
  /* stays in the header next to the burger — switching language should never
     cost a drawer round-trip on the phone */
  .nav-lang{ display: inline-flex; }
}

/* the drawer's toggle row */
.nav-drawer-lang{
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--c-ink-2);
}

/* ---- entry gate (deep-water door over the EN home) ---- */
html.ia-gate-open{ overflow: hidden; }
.ia-gate{
  position: fixed;
  top: 0; left: 0;
  width: 100%; height: 100%;
  z-index: 130;                       /* above --z-overlay(120): it is the front door */
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  background:
    radial-gradient(120% 90% at 50% 8%, rgba(22, 64, 91, .55), transparent 60%),
    var(--c-water-deep);
  color: var(--c-bone);
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}
html.ia-gate-open .ia-gate{
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}
.ia-gate.is-leaving{
  visibility: visible;
  opacity: 0;
  pointer-events: none;
  transition: opacity .6s var(--ease-out), visibility 0s .6s;
}
.ia-gate-inner{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-6);
  max-width: 34rem;
}
.ia-gate-emblem{
  width: clamp(120px, 10rem + 4vw, 200px);
  height: auto;
  margin-bottom: var(--s-3);
}
.ia-gate-mark{
  font-family: var(--f-display);
  font-variant-ligatures: none;
  font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.4rem);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.ia-gate-han{
  font-family: "Noto Serif SC", "Songti SC", var(--f-display);
  font-size: clamp(1rem, .9rem + .5vw, 1.25rem);
  letter-spacing: .32em;
  margin-right: -.32em;               /* recentre: tracking adds a phantom right gap */
  color: var(--c-bone-2);
}
.ia-gate-sub{
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--c-bone-3);
}
.ia-gate-rule{
  width: 56px; height: 1px;
  background: var(--c-gold);
  border: 0;
  margin-block: var(--s-3);
}
.ia-gate-choose{
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: var(--s-4);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--c-bone-2);
}
.ia-gate-choose-zh{
  font-size: .8125rem;
  letter-spacing: .3em;
  margin-right: -.3em;                /* recentre: tracking adds a phantom right gap */
  color: var(--c-bone-3);
}
.ia-gate-btns{
  display: flex;
  gap: var(--s-4);
  margin-top: var(--s-3);
  flex-wrap: wrap;
  justify-content: center;
}
.ia-gate-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  min-width: 10.5rem;
  padding: var(--s-2) var(--s-6);
  border-radius: var(--r-pill);
  border: 1px solid rgba(199, 158, 76, .55);   /* --c-gold-lift ring on deep */
  color: var(--c-bone);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: .16em;
  text-transform: uppercase;
  background: transparent;
  cursor: pointer;
  transition: background var(--d-2) var(--ease-hover),
              color var(--d-2) var(--ease-hover),
              border-color var(--d-2) var(--ease-hover);
}
.ia-gate-btn:hover,
.ia-gate-btn:focus-visible{
  background: var(--c-gold-lift);
  border-color: var(--c-gold-lift);
  color: var(--c-water-deep);
}
.ia-gate-note{
  margin-top: var(--s-5);
  font-size: var(--t-micro);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--c-bone-3);
}
@media (max-width: 480px){
  .ia-gate-btns{ flex-direction: column; width: 100%; }
  .ia-gate-btn{ width: 100%; }
}

/* ---- zh typography ----
   Same two-family system, CJK companions appended: Noto Serif SC rides behind
   Cormorant (Latin glyphs still render Cormorant; Han glyphs fall to the serif),
   Noto Sans SC behind Inter. Latin-calibrated tracking is halved-ish for Han
   text — .22em between ideographs reads gappy, not editorial. */
body.lang-zh{
  --f-display: "Cormorant Garamond", "Noto Serif SC", "Songti SC", Georgia, serif;
  --f-body:    "Inter", "Noto Sans SC", "PingFang SC", system-ui, sans-serif;
}
body.lang-zh .micro,
body.lang-zh .nav-links a,
body.lang-zh .btn{ letter-spacing: .1em; }
body.lang-zh .display{ line-height: 1.18; }
body.lang-zh .nav-brand-mark{ letter-spacing: .14em; text-transform: none; }
body.lang-zh .nav-brand-sub{ letter-spacing: .18em; }

/* ------------------------------------------------- 18. slideshow (.ia-slides) ⭐
   Curated one-frame slideshow — fishes #breeds + services #tanks (Charlie
   2026-07-20: the masonry albums read untidy; show FEWER photos, one at a time).
   Slides absolute-fill a fixed-ratio stage (--slides-ratio, set inline per use);
   cross-fade rides opacity/transform only. Behaviour: js/site.js §6. First slide
   ships .is-current baked in markup so a JS-off visit still shows one photo. */
.ia-slides{ position: relative; }
.ia-slides-stage{
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--slides-ratio, 16 / 9);
  background: var(--c-ink);
  border-radius: var(--r-0);
  touch-action: pan-y;              /* horizontal swipe belongs to the slideshow */
}
.ia-slides-stage::after{            /* the house gold ring (.frame::after twin) */
  content: "";
  position: absolute; inset: 0;
  box-shadow: inset 0 0 0 1px var(--rule-accent);
  pointer-events: none;
  z-index: 3;
}
.ia-slide{
  position: absolute; inset: 0;
  margin: 0;
  opacity: 0;
  transform: scale(1.03);
  transition: opacity var(--d-4) var(--ease-out), transform 1400ms var(--ease-out);
  pointer-events: none;
}
.ia-slide.is-current{
  opacity: 1;
  transform: none;
  pointer-events: auto;
  z-index: var(--z-content);
}
.ia-slide > img,
.ia-slide > video{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: var(--frame-pos, 50% 50%);
}
@media (prefers-reduced-motion: reduce){
  .ia-slide{ transition: none; transform: none; }
}

.ia-slides-bar{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  margin-top: clamp(.9rem, .7rem + .9vw, 1.35rem);
}
.ia-slides-cap{ min-width: 0; }
.ia-slides-nav{
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex: none;
}
.ia-slides-num{
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: var(--ls-micro);
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
  min-width: 4.6em;                 /* "01 / 08" — counter swap must not nudge the buttons */
  text-align: center;
}
.ia-slides-btn{
  display: grid;
  place-items: center;
  width: 44px; height: 44px;        /* touch target floor (design.md §7) */
  padding: 0;
  border: 1px solid var(--rule-2);
  border-radius: 50%;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: border-color var(--d-2) var(--ease-hover),
              background var(--d-2) var(--ease-hover);
}
.ia-slides-btn svg{ display: block; width: 15px; height: 15px; }
.ia-slides-btn:hover{ border-color: var(--text); background: var(--wash-accent); }
.ia-slides-btn:focus-visible{ outline: 2px solid var(--accent-text); outline-offset: 2px; }


/* ===== js/motion-kit.css ===== */
/* ============================================================================
   motion-kit.css — VENDORED from skills/make-website/assets/motion-kit/kit.css
   Vendored 2026-07-17 by imperial-scaffold. Pairs with js/motion-kit.js (verbatim).

   ⚠ THIS IS NOT A COPY — it is a deliberate extraction. Read before re-vendoring.
   The upstream kit.css ships motion-lab's DARK STUDIO DEMO PAGES alongside the
   primitives. Dropping it in raw would have:
     · turned the site black — `body{background:var(--bg)}` with --bg:#08080B, on a
       brief whose first rule is "the SITE reads LIGHT";
     · overridden Cormorant on every heading — `h1,h2,h3{font-family:var(--f-disp)}`
       where --f-disp is "Space Grotesk". An element-level base reset beating our
       component type is design.md §8's logged footgun, and shipping a wrecked type
       hierarchy is what got teardown #12 binned outright (taste.md 2026-07-12);
     · painted acid-green #D8FF3E focus rings and progress bars;
     · claimed the classnames five parallel page-builders will reach for first —
       .wrap .row .cell .cap .eyebrow .meta .chip .layer .on .scene, and the
       single-letter .a .b .k .n .t. One shared site.css = one shared namespace.

   WHAT WAS KEPT: the primitive MECHANICS only, re-pointed at house tokens.
   WHAT WAS DROPPED: the :root dark tokens, the base reset (css/base.css owns that),
   STUDIO/LAB + FLAGSHIP demo chrome, the WebGL/forge/chrome/glass-card 3D lane
   (wrong register for this brief), and all demo CARD DRESSING (.hcard/.focus-card/
   .seq-cap/.pinvid-cap). Dressing is page CSS — the kit supplies structure.

   The JS custom-property contract is fixed by kit.js and must not be renamed:
     --si (stagger index) · --ci (char index) · --rd (reveal delay) ·
     --g (grow progress) · --p (progress) · --mx/--my (spotlight cursor)
   ========================================================================== */

/* ---------------------------------------------------------- reveal / enter */
/* Gated on html.js so a no-JS visitor gets content, not a blank page. site.js
   sets .js on <html> before first paint. */
html.js [data-reveal]{
  opacity: 0;
  transform: translateY(28px);
  transition: opacity var(--d-4) var(--ease-reveal) var(--rd, 0ms),
              transform var(--d-4) var(--ease-reveal) var(--rd, 0ms);
}
[data-reveal].is-in{ opacity: 1; transform: none; }

/* mask line reveal — [data-reveal="mask"]; kit.js wraps each line itself */
.mk-mask{ opacity: 1; transform: none; }
.mk-line{ display: block; overflow: hidden; }
/* Descenders (g/y/j) clip against overflow:hidden on a mask line — pad then pull
   back so the box is unchanged but the glyph survives (design.md §4). */
.mk-line-i{
  display: block;
  transform: translateY(110%);
  transition: transform 1s var(--ease-reveal);
  padding-bottom: .2em;
  margin-bottom: -.2em;
}
.mk-mask.is-in .mk-line-i{ transform: none; }
.mk-mask.is-in .mk-line:nth-child(2) .mk-line-i{ transition-delay: .08s; }
.mk-mask.is-in .mk-line:nth-child(3) .mk-line-i{ transition-delay: .16s; }
.mk-mask.is-in .mk-line:nth-child(4) .mk-line-i{ transition-delay: .24s; }

/* ------------------------------------------------------------- stagger */
html.js .mk-stag{
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .7s var(--ease-reveal), transform .7s var(--ease-reveal);
  transition-delay: calc(var(--si) * 70ms);
}
[data-stagger].is-in .mk-stag{ opacity: 1; transform: none; }

/* ------------------------------------------------------------- marquee */
.mk-marquee{
  overflow: hidden;
  white-space: nowrap;
  -webkit-mask: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.mk-marquee > *{
  display: inline-flex;
  white-space: nowrap;
  animation: mk-march linear infinite;
  will-change: transform;
}
.mk-marquee:hover > *{ animation-play-state: paused; }
@keyframes mk-march{ to{ transform: translateX(-50%); } }

/* ------------------------------------------------- scroll progress bar */
.mk-progress{
  position: fixed; top: 0; left: 0;
  height: 2px; width: 100%;
  transform-origin: 0 50%;
  transform: scaleX(0);
  background: var(--c-gold);
  z-index: var(--z-overlay);
}

/* --------------------------------------------------------- custom cursor */
.mk-cursor, .mk-cursor-ring{
  position: fixed; top: 0; left: 0;
  pointer-events: none;
  z-index: var(--z-cursor);
  border-radius: 50%;
  mix-blend-mode: difference;
  will-change: transform;
}
.mk-cursor{ width: 7px; height: 7px; margin: -3.5px; background: #fff; }
.mk-cursor-ring{
  width: 38px; height: 38px; margin: -19px;
  border: 1px solid #fff;
  transition: width .25s var(--ease-out), height .25s var(--ease-out),
              margin .25s var(--ease-out), opacity .25s;
}
body.mk-cursor-hover .mk-cursor-ring{ width: 62px; height: 62px; margin: -31px; }
@media (pointer: fine){
  body[data-cursor]{ cursor: none; }
  body[data-cursor] a, body[data-cursor] button{ cursor: none; }
}

/* ------------------------------------------- horizontal pinned rail ⭐
   BRIEF §3 signature move 3 (the traverse mechanic). Structure only — style your
   own cards in css/pages/<key>.css. Needs generous travel + enough items or it
   "feels broken" even when the math is right (design.md §9). */
[data-hscroll]{ position: relative; }
.hscroll-sticky{
  position: sticky; top: 0;
  height: 100svh;
  overflow: hidden;
  display: flex;
  align-items: center;
}
[data-hscroll-track]{
  display: flex;
  gap: clamp(1rem, 2vw, 2rem);
  padding-inline: var(--gutter);
  will-change: transform;
}

/* ------------------------------------------------- canvas image-sequence */
.seq-wrap{ position: relative; height: 420vh; }
.seq-sticky{ position: sticky; top: 0; height: 100svh; overflow: hidden; }
.seq-sticky canvas{ position: absolute; inset: 0; width: 100%; height: 100%; }

/* -------------------------------------------- pinned video + scroll steps
   A pinned element MUST give scroll feedback or it reads as broken — pair with
   [data-steps] and a scroll hint, never a passive pin (design.md §9, Charlie-tested). */
.pinvid{ position: relative; height: 300vh; }
.pinvid-sticky{ position: sticky; top: 0; height: 100svh; overflow: hidden; }
.pinvid-sticky video{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.pinvid-step{
  position: absolute; left: 0; bottom: 0;
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .5s var(--ease-out), transform .5s var(--ease-out);
}
.pinvid-step.on{ opacity: 1; transform: none; }

/* ----------------------------------------------------------- spotlight */
[data-spotlight]{ position: relative; --mx: 50%; --my: 50%; overflow: hidden; }
[data-spotlight]::before{
  content: "";
  position: absolute; inset: 0;
  pointer-events: none;
  background: radial-gradient(280px circle at var(--mx) var(--my),
              var(--wash-accent), transparent 70%);
  transition: opacity var(--d-2);
  z-index: var(--z-base);
}

/* ------------------------------------------------------ stacking cards */
[data-stack]{ display: flex; flex-direction: column; gap: 6vh; }
.stack-card{
  position: sticky; top: 14vh;
  height: 64vh;
  transform-origin: 50% 0;
  will-change: transform;
}

/* ------------------------------------------- split — per-char mask reveal */
/* kit.js groups chars into .mk-word so headlines never break mid-word. */
.mk-split .mk-word{
  display: inline-flex;
  overflow: hidden;
  vertical-align: top;
  padding-bottom: .08em;
}
.mk-char{
  display: inline-block;
  transform: translateY(112%);
  opacity: 0;
  transition: transform .8s var(--ease-reveal), opacity .8s var(--ease-reveal);
  transition-delay: calc(var(--ci) * 26ms);
}
.mk-split.is-in .mk-char{ transform: none; opacity: 1; }

/* ------------------------------------------ clip — directional curtain
   ⚠ Never point an IntersectionObserver at a fully-clipped element: Chrome reports
   it 0% intersecting and it never reveals. Observe the unclipped parent — kit.js
   already does this (design.md §9). */
html.js [data-clip]{
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1.1s var(--ease-reveal) var(--rd, 0ms);
  will-change: clip-path;
}
html.js [data-clip="down"]{ clip-path: inset(100% 0 0 0); }
html.js [data-clip="left"]{ clip-path: inset(0 100% 0 0); }
html.js [data-clip="right"]{ clip-path: inset(0 0 0 100%); }
[data-clip].is-in{ clip-path: inset(0 0 0 0); }

/* --------------------------------------- highlight — read-along word fill */
[data-highlight]{ --mk-dim: var(--text-3); --mk-bright: var(--text); }
.mk-hl{ color: var(--mk-dim); transition: color .35s var(--ease-reveal); }
.mk-hl.on{ color: var(--mk-bright); }

/* --------------------------------------------------- skew (velocity) */
[data-skew]{ will-change: transform; }

/* ------------------------------------------------------- flip card */
.mk-flip{ perspective: 1400px; position: relative; }
.mk-flip-inner{
  position: relative;
  transform-style: preserve-3d;
  transition: transform .85s var(--ease-reveal);
  will-change: transform;
}
.mk-flip:hover .mk-flip-inner,
.mk-flip.is-flipped .mk-flip-inner{ transform: rotateY(180deg); }
.mk-flip [data-flip-front], .mk-flip [data-flip-back]{
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.mk-flip [data-flip-back]{ position: absolute; inset: 0; transform: rotateY(180deg); }

/* --------------------------------------------- focus carousel [data-focus] */
[data-focus]{
  display: flex;
  gap: clamp(.8rem, 2vw, 1.4rem);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  padding: 1.4rem max(20px, calc(50% - 150px));
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
[data-focus]::-webkit-scrollbar{ display: none; }
[data-focus] > *{
  flex: 0 0 auto;
  scroll-snap-align: center;
  transform-origin: center center;
  will-change: transform, opacity;
}

/* ------------------------------------------- grow to full-bleed [data-grow] */
[data-grow]{ transform-origin: center center; will-change: transform; }

/* ---------------------------------------------- hover peek list ⭐
   design.md §9: the DEFAULT treatment for any service/work index list.
   Charlie-approved live on touchmkt.com. BRIEF §4 services page is the target. */
.mk-peek{
  position: fixed; left: 0; top: 0;
  z-index: var(--z-overlay);
  width: min(25vw, 368px);
  aspect-ratio: 4 / 3;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  background: var(--c-water-deep);
  outline: 1px solid var(--rule-accent);
  box-shadow: 0 2px 6px rgba(18, 20, 23, .2), 0 34px 90px -22px rgba(18, 20, 23, .5);
  transition: opacity var(--d-2) var(--ease-out);
  will-change: transform, opacity;
}
.mk-peek.on{ opacity: 1; }
/* Match the frame to the media: horizontal films get data-peek-wide (websites.md §11). */
.mk-peek.wide{ aspect-ratio: 16 / 9; width: min(29vw, 440px); }
.mk-peek img, .mk-peek video{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.mk-peek video{ display: none; }
@media (max-width: 900px), (pointer: coarse){ .mk-peek{ display: none !important; } }

/* ="invert": a full-bleed band sweeps behind the hovered row and inverts its text */
[data-peeklist="invert"] [data-peek-img]{ position: relative; isolation: isolate; }
[data-peeklist="invert"] [data-peek-img]::before{
  content: "";
  position: absolute; top: 0; bottom: 0; left: 50%;
  width: 100vw; margin-left: -50vw;
  background: var(--pl-band, var(--c-water-deep));
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition: transform .5s var(--ease-out);
  z-index: -1;
  pointer-events: none;
}
[data-peeklist="invert"] [data-peek-img]:hover::before{ transform: scaleY(1); }
[data-peeklist="invert"] [data-peek-img]:hover,
[data-peeklist="invert"] [data-peek-img]:hover *{ color: var(--pl-hover-fg, var(--c-paper)); }
@media (pointer: coarse){
  [data-peeklist="invert"] [data-peek-img]::before{ display: none; }
}

/* ---------------------------------------------------------------- floors */
/* Reduced motion: everything is visible and still. No transforms, no clips, no
   opacity gates — the content IS the floor. */
@media (prefers-reduced-motion: reduce){
  html.js [data-reveal],
  html.js .mk-stag,
  .mk-char{ opacity: 1 !important; transform: none !important; }
  html.js [data-clip],
  html.js [data-clip="down"],
  html.js [data-clip="left"],
  html.js [data-clip="right"]{ clip-path: none !important; }
  .mk-line-i{ transform: none !important; }
  .mk-marquee > *{ animation: none !important; }
  .mk-cursor, .mk-cursor-ring, .mk-peek{ display: none !important; }
}


/* ===== css/pages/blog.css ===== */
/* ============================================================================
   blog.css — the blog index. Namespace: .p-blog (CONTRACT.md §3).
   One featured card for now; .bl-list is ready to hold more.
   ========================================================================== */

/* Reveal-cascade workaround, same as farm/fishes/media (delete when the kit's
   fix lands): html.js [data-reveal]{opacity:0} is (0,2,1) and beats .is-in. */
.p-blog [data-reveal].is-in{ opacity: 1; transform: none; }
.p-blog [data-reveal].mk-mask{ opacity: 1; transform: none; }

.p-blog .bl-open-head{
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--gutter);
  align-items: end;
  margin-bottom: clamp(3rem, 2.5rem + 3vw, 5.5rem);
}
.p-blog .bl-open-lede{ max-width: 44ch; }

.p-blog .bl-card{
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 1rem + 3vw, 4rem);
  align-items: center;
  color: inherit;
}
.p-blog .bl-card + .bl-card{ margin-top: clamp(3rem, 2.5rem + 3vw, 5rem); }
.p-blog .bl-card .frame img{
  transition: transform 1200ms var(--ease-out);
}
.p-blog .bl-card:hover .frame img,
.p-blog .bl-card:focus-visible .frame img{ transform: scale(1.04); }
.p-blog .bl-card:focus-visible{ outline: 2px solid var(--accent-text); outline-offset: 4px; }

.p-blog .bl-card-body{ display: flex; flex-direction: column; gap: var(--s-4); }
.p-blog .bl-card-title{
  font-family: var(--f-display);
  font-size: var(--t-h2);
  font-weight: var(--w-h2);
  line-height: 1.24;
  letter-spacing: var(--ls-h);
}
.p-blog .bl-card-excerpt{ color: var(--text-2); max-width: 52ch; }
.p-blog .bl-card-more{
  position: relative;
  align-self: flex-start;
  padding-bottom: 3px;
  color: var(--accent-text);
}
.p-blog .bl-card-more::after{
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(.35);
  transform-origin: 0 50%;
  transition: transform var(--d-2) var(--ease-hover);
}
.p-blog .bl-card:hover .bl-card-more::after{ transform: scaleX(1); }

@media (max-width: 900px){
  .p-blog .bl-open-head{ grid-template-columns: 1fr; align-items: start; }
  .p-blog .bl-card{ grid-template-columns: 1fr; align-items: start; }
}


/* ===== css/pages/contact.css ===== */
/* ============================================================================
   contact.css — the Enquire page. OWNER: imperial-contact.
   Scoped under .p-contact, keyframes named contact-* (CONTRACT.md §3 — one site.css
   is one namespace). Reads semantic roles only; never a --c-* primitive.

   THE SIGNATURE (structural): the enquiry is a farm INTAKE DOSSIER — numbered hairline
   field rows with no boxes, read against a sticky margin column that narrates what
   happens after you send. The margin counter-scrolls the form on desktop.

   The gold trap (CONTRACT §2): --accent is ornament only (2.92:1). Every gold that
   carries text or meaning here reads --accent-text (5.01:1). Field errors read
   --seal-ink (7.10:1 on paper).
   ========================================================================== */

/* --- KIT BUG WORKAROUND (motion-kit.css is scaffold's file; fixing at source is qa's
   call — HANDOFFS 19:3x). `html.js [data-reveal]{opacity:0}` at motion-kit.css:33 has
   specificity (0,2,1) and OUT-SPECIFIES the kit's own reveal at :39
   `[data-reveal].is-in{opacity:1}` at (0,2,0). So the IntersectionObserver adds .is-in and
   nothing happens: every [data-reveal] stays at opacity 0 permanently — invisible to real
   USERS, not merely to the screenshot gate. Measured on this page: is-in present, computed
   opacity still 0, unchanged after scrolling it into view.
   Re-asserted here at (0,3,0). imperial-farm hit this independently and patched the same
   way, which is corroboration, not coincidence. --- */
.p-contact [data-reveal].is-in{ opacity: 1; transform: none; }

/* ---------------------------------------------------------------- 10 — open */

.p-contact .c-open-grid{
  display: grid;
  gap: clamp(2.5rem, 5vw, 4.5rem);
}

.p-contact .c-open-type > * + *{ margin-top: var(--s-4); }
.p-contact .c-open-type .display{ margin-top: var(--s-5); }

.p-contact .c-open-hr{ margin-top: var(--s-7); }

.p-contact .c-open-facts{
  list-style: none;
  margin: 0;
  padding: 0;
}
.p-contact .c-open-facts li{ padding-block: var(--s-5); }
.p-contact .c-open-facts li + li{ border-top: 1px solid var(--rule); }
.p-contact .c-open-facts p{
  margin-top: var(--s-2);
  font-size: var(--t-small);
  max-width: 46ch;
}

/* native 4:3, uncropped, at every width — see the note in partials/contact/10-hero.html */
.p-contact .c-open-media{ aspect-ratio: 4 / 3; }

@media (min-width: 900px){
  .p-contact .c-open-grid{
    grid-template-columns: minmax(0, 1.06fr) minmax(0, .94fr);
    align-items: center;
  }
}

/* ------------------------------------------------------------- 20 — dossier */

.p-contact .c-enq-grid{
  display: grid;
  gap: clamp(2.5rem, 5vw, 5rem);
}

/* --- the margin column (the counter-scrolling narrative) --- */

.p-contact .c-steps{
  list-style: none;
  margin: var(--s-5) 0 0;
  padding: 0;
}
.p-contact .c-step + .c-step{
  margin-top: var(--s-5);
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
}
.p-contact .c-step-n{
  display: block;
  margin-bottom: var(--s-2);
  color: var(--accent-text);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: var(--ls-micro);
}
.p-contact .c-step p{
  margin-top: var(--s-2);
  font-size: var(--t-small);
  max-width: 44ch;
}

.p-contact .c-margin-note{
  margin-top: var(--s-6);
  padding: var(--s-4) var(--s-5);
  background: var(--surface-3);
  border-left: 2px solid var(--accent);
  color: var(--text-2);
  font-size: var(--t-small);
  line-height: var(--lh-body);
}

/* --- the form --- */

.p-contact .c-form > .micro{ display: inline-flex; margin-bottom: var(--s-6); }

.p-contact .c-field + .c-field{ margin-top: var(--s-6); }

.p-contact .c-field-label{
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  margin-bottom: var(--s-2);
  cursor: pointer;
}
.p-contact .c-field-n{
  color: var(--accent-text);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: var(--ls-micro);
}
.p-contact .c-field-t{
  color: var(--text-2);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
}
.p-contact .c-field-opt{
  margin-left: auto;
  color: var(--text-3);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
}

/* No boxes — a hairline is the whole field. The dossier signature. */
.p-contact .c-input{
  display: block;
  width: 100%;
  padding: var(--s-3) 0;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--rule-2);
  border-radius: var(--r-0);
  color: var(--text);
  font-family: var(--f-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  transition: border-color var(--d-2) var(--ease-hover),
              background-color var(--d-2) var(--ease-hover),
              box-shadow var(--d-2) var(--ease-hover);
}
.p-contact .c-input::placeholder{ color: var(--text-3); opacity: .75; }
.p-contact .c-input:hover{ border-bottom-color: var(--text-2); }

/* Focus is designed, not defaulted: the hairline thickens to gold and the row washes.
   :focus-visible keeps a real outline on top for keyboard users. */
.p-contact .c-input:focus{
  outline: none;
  border-bottom-color: var(--accent-text);
  box-shadow: 0 1px 0 0 var(--accent-text);
  background: var(--wash-accent);
}
.p-contact .c-input:focus-visible{
  outline: 2px solid var(--accent-text);
  outline-offset: 3px;
}

.p-contact .c-input[aria-invalid="true"]{
  border-bottom-color: var(--seal-ink);
  background: var(--wash-seal);
}
.p-contact .c-input[aria-invalid="true"]:focus{ box-shadow: 0 1px 0 0 var(--seal-ink); }

.p-contact .c-textarea{
  resize: vertical;
  min-height: 6.5rem;
}

.p-contact .c-hint{
  margin-top: var(--s-2);
  color: var(--text-3);
  font-size: var(--t-small);
}
/* The error RESERVES its space and toggles visibility — it is never inserted into flow.
   Measured bug this prevents: revealing an error on blur reflowed the column and moved the
   submit button down 31.1px, so a click that fired right after the blur landed on nothing —
   the Send button dodged the cursor. visibility (not [hidden]) keeps the box, so the reserve
   is exactly the text's real wrapped height at every viewport instead of a guessed
   min-height. visibility:hidden also keeps it out of the a11y tree; js/pages/contact.js
   attaches aria-describedby only while the field is actually invalid. */
.p-contact .c-err{
  margin-top: var(--s-2);
  color: var(--seal-ink);
  font-size: var(--t-small);
  visibility: hidden;
}
.p-contact .c-field.is-invalid .c-err{ visibility: visible; }

/* tank size: the unit sits in the field, not in the label */
.p-contact .c-input-affix{ position: relative; }
.p-contact .c-input-affix .c-input{ padding-right: 2rem; }
.p-contact .c-affix{
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-3);
  font-size: var(--t-small);
  pointer-events: none;
}

/* select: native chevron killed, hairline kept */
.p-contact .c-select-wrap{ position: relative; }
.p-contact .c-select{
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--s-6);
  cursor: pointer;
}
.p-contact .c-select-wrap::after{
  content: "";
  position: absolute;
  right: 3px;
  top: 50%;
  width: 7px;
  height: 7px;
  border-right: 1.5px solid var(--accent-text);
  border-bottom: 1.5px solid var(--accent-text);
  transform: translateY(-75%) rotate(45deg);
  pointer-events: none;
}

/* honeypot: off-screen, NOT display:none — a bot reads the computed style too */
.p-contact .c-hp{
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.p-contact .c-submit{ margin-top: var(--s-7); }
.p-contact .c-formerr{
  margin-top: var(--s-4);
  color: var(--seal-ink);
  font-size: var(--t-small);
}
.p-contact .c-legal{
  margin-top: var(--s-5);
  font-size: var(--t-small);
  max-width: 52ch;
}

/* --- success: a stamped receipt, not an alert() --- */

.p-contact .c-done{
  padding: clamp(1.75rem, 4vw, 3rem);
  background: var(--surface-2);
  border: 1px solid var(--rule-accent);
  animation: contact-receipt var(--d-3) var(--ease-out) both;
}
.p-contact .c-done-seal{ margin-bottom: var(--s-4); }
.p-contact .c-done h2{ margin-bottom: var(--s-3); }
.p-contact .c-done .btns{ margin-top: var(--s-6); }

@keyframes contact-receipt{
  from{ opacity: 0; transform: translate3d(0, 12px, 0); }
  to{ opacity: 1; transform: none; }
}

@media (min-width: 900px){
  .p-contact .c-enq-grid{
    grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
    align-items: start;
  }
  /* the counter-scroll: the narrative holds while the form travels */
  .p-contact .c-margin-inner{
    position: sticky;
    top: calc(var(--nav-h) + var(--s-6));
  }
}

/* --------------------------------------------------------------- 30 — visit */

.p-contact .c-visit{
  position: relative;
  padding-bottom: var(--space-section);
}
.p-contact .c-visit-media{ aspect-ratio: 4 / 3; }

/* the paper panel overlaps the photograph's lower edge — layering, not a flat block */
.p-contact .c-visit-panel{
  position: relative;
  z-index: var(--z-content);
  margin-top: clamp(-3.5rem, -5vw, -1.5rem);
  padding: clamp(1.75rem, 4vw, 3.5rem);
  background: var(--surface);
  box-shadow: var(--sh-2);
}
.p-contact .c-visit-panel h2{ margin-top: var(--s-4); }
.p-contact .c-visit-panel .lede{ margin-top: var(--s-4); max-width: 54ch; }
.p-contact .c-visit-hr{ margin-block: clamp(1.5rem, 3vw, 2.5rem); }

.p-contact .c-visit-grid{
  display: grid;
  gap: var(--s-6);
}
.p-contact .c-visit-col .micro{ display: inline-flex; margin-bottom: var(--s-3); }
.p-contact .c-address{
  font-style: normal;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  max-width: 24ch;
}
.p-contact .c-map{ display: inline-flex; margin-top: var(--s-3); font-size: var(--t-small); }
/* flex, not inline-flex — and at (0,3,0): the label is a .micro inside .c-visit-col,
   and that rule's `.p-contact .c-visit-col .micro { display:inline-flex }` beat the
   two-class version of this selector (measured, design.md §0), leaving the label
   flowing on the map link's line */
.p-contact .c-visit-col .c-hours-label{ display: flex; margin-top: var(--s-5); }
.p-contact .c-hours{
  font-size: var(--t-body);
  line-height: var(--lh-body);
}
.p-contact .c-hours-note{ margin-top: 2px; font-size: var(--t-small); }
.p-contact .c-visit-lead{ font-size: var(--t-small); margin-bottom: var(--s-4); }

@media (min-width: 640px){
  .p-contact .c-visit-grid{ grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); gap: var(--s-7); }
}
@media (min-width: 900px){
  .p-contact .c-visit-media{ aspect-ratio: 21 / 8; }
  .p-contact .c-visit-panel{ width: min(100%, 62rem); margin-inline: auto; }
}


/* ===== css/pages/farm.css ===== */
/* ============================================================================
   farm.css — farm.html only.
   Every selector is scoped under .p-farm and every @keyframes is farm-* :
   all five pages concatenate into ONE site.css, so there is one namespace.
   Reads SEMANTIC roles only — never a --c-* primitive, or a deep section would
   render an invisible headline.
   ========================================================================== */

/* ---------------------------------------------------------------------------
   WORKAROUND — delete once the motion-kit.css cascade fix lands.

   motion-kit.css ships:
     html.js [data-reveal] { opacity:0 }   (0,2,1)  <- WINS
     [data-reveal].is-in   { opacity:1 }   (0,2,0)  <- loses
   `html.js` adds an element + a class, so the HIDDEN rule out-specifies the
   REVEALED one and every [data-reveal] on every page stays at opacity:0 forever.
   The kit's JS is correct — .is-in is applied and simply has no effect (probed:
   isIn:true while computed opacity:'0'). The reduced-motion block uses !important,
   so RM renders fine and only NORMAL motion is blank — which is why a gate can be
   green over a blank page.

   These two rules are (0,3,0) and beat the kit's (0,2,1), scoped to .p-farm so
   they cannot reach another page.
   --------------------------------------------------------------------------- */
.p-farm [data-reveal].is-in{ opacity: 1; transform: none; }
.p-farm [data-reveal].mk-mask{ opacity: 1; transform: none; }

/* ---------------------------------------------------------------- 10 opening */
.p-farm .fm-open{ overflow: hidden; }          /* the grow plate scales — clip it */

/* NO `ch` max-width on a heading's CONTAINER: ch resolves against the container's
   own font-size (body, ~17px), not the 80px display inside it — 20ch ≈ 170px, which
   crushed the h1 to four lines and let .mk-line{overflow:hidden} shear "showroom."
   to "showro". The <br> in the markup owns the breaks. Constrain the TEXT element
   (where ch means what you meant), never the wrapper. */
.p-farm .fm-open-head{
  margin-bottom: var(--space-block);
}
.p-farm .fm-open-head .micro{ margin-bottom: var(--s-5); }

/* Asymmetric, not centred: the lede column sits right of a 1fr well, so the
   display headline above it reads against open paper. Rhythm is intentional —
   tight inside the group, generous between groups. */
.p-farm .fm-open-body{
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: var(--space-block);
  align-items: start;                           /* NOT baseline: it hangs the heading
                                                   low against a tiny kicker */
}
.p-farm .fm-open-meta{
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-4);
  padding-top: var(--s-2);
  order: -1;                                    /* seal + address lead the left well */
}
.p-farm .fm-open-addr{
  color: var(--text-2);
  letter-spacing: .06em;
  max-width: 22ch;
  line-height: 1.7;
}

/* The arrival plate: authored FULL-BLEED, [data-grow] starts it at 0.80 and
   scales to 1 as it enters. Under reduced-motion setupGrow() never writes a
   transform, so it simply renders full-bleed — the finished state. */
.p-farm .fm-plate{
  margin: var(--space-block) 0 0;
  width: 100%;
}
.p-farm .fm-plate-frame{
  --frame-ratio: 21 / 9;
  --frame-pos: 50% 52%;
}
@media (max-width: 700px){
  .p-farm .fm-plate-frame{ --frame-ratio: 4 / 3; }
}
.p-farm .fm-plate-cap{
  margin-top: var(--s-3);
  padding-inline: var(--gutter);
}

@media (max-width: 900px){
  .p-farm .fm-open-body{ grid-template-columns: 1fr; gap: var(--s-6); }
  .p-farm .fm-open-meta{ order: 0; flex-direction: row; align-items: center; gap: var(--s-5); }
  .p-farm .fm-open-addr{ max-width: none; }
}

/* ------------------------------------------------------------------ 20 sites */
.p-farm .fm-sites-stage{ position: relative; }

.p-farm .fm-sites-media{ width: 100%; }
@media (min-width: 901px){
  /* Centred on the section's axis (Charlie 2026-07-18) — the card still breaks
     the photo's right edge for the depth read. */
  .p-farm .fm-sites-media{ width: 68%; margin-inline: auto; }
  /* the overlap — the panel breaks the photo's edge and lifts off it.
     Depth via layering, not a card in a grid.
     z above --z-content: the photo's .frame::after gold ring carries z-content
     and would otherwise paint its edge ACROSS this card as a stray vertical line. */
  .p-farm .fm-sites-panel{
    position: absolute;
    right: 0;
    bottom: clamp(2rem, 5vw, 5rem);
    width: min(44%, 460px);
    padding: clamp(1.75rem, 2.4vw, 2.75rem);
    background: var(--surface);
    box-shadow: var(--sh-2);
    z-index: var(--z-seal);
  }
}
@media (max-width: 900px){
  .p-farm .fm-sites-panel{
    margin-top: calc(var(--s-6) * -1);
    margin-inline: var(--s-4) 0;
    position: relative;
    padding: var(--s-6) var(--s-5);
    background: var(--surface);
    box-shadow: var(--sh-2);
    z-index: var(--z-seal);
  }
}
.p-farm .fm-sites-panel .micro{ margin-bottom: var(--s-4); }
.p-farm .fm-sites-panel h2{ margin-bottom: var(--s-5); }
.p-farm .fm-sites-panel .prose{ margin-bottom: var(--s-6); }

.p-farm .fm-sites-here{
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule-accent);
}
.p-farm .fm-sites-here .micro{ margin-bottom: var(--s-3); }
.p-farm .fm-sites-addr{
  font-family: var(--f-display);
  font-size: var(--t-h3);
  font-weight: var(--w-h3);
  line-height: 1.35;
  color: var(--text);
  margin-bottom: var(--s-3);
}

/* -------------------------------------------------------- 30 lineage (SIGNATURE)
   .pinvid / .pinvid-sticky / .pinvid-step geometry is motion-kit.css's. Only the
   film, the veil and the type are mine. */
:is(.p-farm, .p-home) .fm-lineage{ position: relative; }

/* The still is the base layer and the reduced-motion floor; the film fades in over
   it via .on (set once by farm.js on the first `playing` event, never removed —
   removing it flashes the still back on resume). */
:is(.p-farm, .p-home) .fm-lineage-still{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: var(--z-media);
}
:is(.p-farm, .p-home) .fm-lineage-film{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: var(--z-media);
  opacity: 0;
  transition: opacity var(--d-3) var(--ease-out);
}
:is(.p-farm, .p-home) .fm-lineage-film.on{ opacity: 1; }
/* ONE flat veil over ONE film. Never a second media layer at partial opacity: the
   film is the background or it is not there.
   Direction is load-bearing: the subject fills the LOWER-LEFT of this clip, so the
   veil weights RIGHT (into the black water the type sits in) and thins to almost
   nothing over the fish. Veiling toward the subject hides it and showcases the
   empty half. Never `var(--surface)` at a stop — an opaque stop erases the film
   under it, which defeats the whole section. */
:is(.p-farm, .p-home) .fm-lineage-veil{
  position: absolute; inset: 0;
  z-index: var(--z-media);
  background:
    linear-gradient(to left, rgba(14, 44, 63, .95) 0%, rgba(14, 44, 63, .88) 28%, rgba(14, 44, 63, .46) 62%, rgba(14, 44, 63, .12) 100%),
    linear-gradient(to top, rgba(14, 44, 63, .58) 0%, rgba(14, 44, 63, 0) 46%);
}
@media (max-width: 900px){
  :is(.p-farm, .p-home) .fm-lineage-veil{
    background: linear-gradient(to top, rgba(14, 44, 63, .94) 12%, rgba(14, 44, 63, .62) 58%, rgba(14, 44, 63, .30) 100%);
  }
}

/* Positioners run full-bleed (left:0/right:0) and hold a real .wrap INSIDE.
   Do NOT put .wrap on the positioner and give it width:100% — .wrap is
   `width: min(100% - gutter*2, --maxw)` (base.css:186), so a width declaration
   OVERRIDES it (0,2,0 beats 0,1,0) and the element silently goes full-bleed:
   measured left:0/w:1440 where a real wrap is left:80/w:1280, which dumped the
   headline flush against the viewport edge. */
:is(.p-farm, .p-home) .fm-lineage-head{
  position: absolute;
  top: calc(var(--nav-h) + var(--s-6));
  left: 0; right: 0;
  z-index: var(--z-content);
}
/* the right column — type in the frame's negative space */
:is(.p-farm, .p-home) .fm-lineage-col,
:is(.p-farm, .p-home) .fm-lineage-stepcol{
  margin-left: auto;
  width: min(100%, 30rem);
}
:is(.p-farm, .p-home) .fm-lineage-head .micro{ margin-bottom: var(--s-4); }
:is(.p-farm, .p-home) .fm-lineage-h{
  font-size: var(--t-h1);
  font-weight: var(--w-h1);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
  color: var(--text);
}
:is(.p-farm, .p-home) .fm-lineage-h em{ color: var(--accent-text); }

/* Steps stack in one well and cross-fade; .pinvid-step is absolute/bottom-left
   from the kit, so the well is what positions them. */
:is(.p-farm, .p-home) .fm-lineage-steps{
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(3.5rem, 9vh, 7rem);
  z-index: var(--z-content);
}
:is(.p-farm, .p-home) .fm-lineage-stepcol{
  position: relative;                 /* containing block for the absolute steps */
  height: clamp(11rem, 22vh, 14rem);
}
:is(.p-farm, .p-home) .fm-step{
  left: 0;
  right: 0;
}
:is(.p-farm, .p-home) .fm-step-n{
  color: var(--accent-text);
  margin-bottom: var(--s-4);
}
:is(.p-farm, .p-home) .fm-step-t{
  max-width: 22ch;              /* on the TEXT: 22 chars of THIS display size */
  font-family: var(--f-display);
  font-size: var(--t-h2);
  font-weight: var(--w-h2);
  line-height: 1.22;
  letter-spacing: var(--ls-h);
  color: var(--text);
}
:is(.p-farm, .p-home) .fm-step-t em{
  color: var(--accent-text);
}

/* A pin MUST give scroll feedback or it reads as broken. */
:is(.p-farm, .p-home) .fm-lineage-cue{
  position: absolute;
  right: var(--gutter);
  bottom: var(--s-6);
  z-index: var(--z-content);
  color: var(--text-3);
  writing-mode: vertical-rl;
  animation: farm-cue 2.6s var(--ease-inout) infinite;
}
@keyframes farm-cue{
  0%, 100%{ opacity: .35; transform: translateY(0); }
  50%     { opacity: 1;   transform: translateY(8px); }
}

/* Reduced motion + no-JS: nothing pins, nothing scrubs, so the stacked steps
   would print on top of each other. Un-stack them into a plain readable list —
   the section must render COMPLETE, not blank and not overlapped. */
@media (prefers-reduced-motion: reduce){
  :is(.p-farm, .p-home) .fm-lineage{ height: auto; }
  :is(.p-farm, .p-home) .fm-lineage-film{ display: none; }        /* RM never fetches a byte (§16) */
  :is(.p-farm, .p-home) .fm-lineage .pinvid-sticky{
    position: static;
    height: auto;
    padding: var(--space-section) 0;
  }
  :is(.p-farm, .p-home) .fm-lineage-head{ position: static; transform: none; margin-bottom: var(--space-block); }
  :is(.p-farm, .p-home) .fm-lineage-steps{
    position: static; transform: none;
    height: auto;
    display: flex; flex-direction: column; gap: var(--space-block);
  }
  :is(.p-farm, .p-home) .fm-step{
    position: static;
    opacity: 1;
    transform: none;
  }
  :is(.p-farm, .p-home) .fm-lineage-cue{ display: none; }
}
:is(.p-farm, .p-home) .fm-lineage .pinvid-step.on{ opacity: 1; transform: translateY(0); }

/* ------------------------------------------------------------------ 40 clean */
.p-farm .fm-clean-head{
  margin-bottom: var(--space-block);
}
.p-farm .fm-clean-head .micro{ margin-bottom: var(--s-5); }

.p-farm .fm-clean-rows{
  border-top: 1px solid var(--rule);
  margin: 0;
}
/* Rows, not cards: a hairline and a label — no box, no shadow, no uniform pad.
   Row heights are deliberately UNEVEN (the fed row carries film). */
.p-farm .fm-clean-row{
  display: grid;
  grid-template-columns: minmax(0, 9rem) minmax(0, 1fr);
  gap: var(--s-5) var(--space-block);
  padding: var(--space-block) 0;
  border-bottom: 1px solid var(--rule);
  align-items: start;
}
.p-farm .fm-clean-row dt{ padding-top: .45em; }
.p-farm .fm-clean-row dd{ margin: 0; }
.p-farm .fm-clean-t{
  font-family: var(--f-display);
  font-size: var(--t-h2);
  font-weight: var(--w-h2);
  line-height: 1.28;
  letter-spacing: var(--ls-h);
  color: var(--text);
  max-width: 30ch;
}
.p-farm .fm-clean-t em{ color: var(--accent-text); }

.p-farm .fm-clean-code{
  margin-top: var(--s-5);
  font-family: var(--f-body);
  font-size: var(--t-h3);
  font-weight: 600;
  letter-spacing: .12em;
  color: var(--accent-text);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--rule-accent);
  display: inline-block;
  background: var(--wash-accent);
}

.p-farm .fm-clean-row--fed dd{
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 15rem);
  gap: var(--space-block);
  /* start, not end: end-alignment sank the statement to the film's baseline,
     leaving it floating mid-row while the "Fed" label sat at the top (Charlie
     2026-07-19). Top-aligned, the statement reads level with its label like
     every other row; the film column keeps its own height. */
  align-items: start;
}
.p-farm .fm-clean-media{ width: 100%; }

@media (max-width: 760px){
  .p-farm .fm-clean-row{ grid-template-columns: 1fr; gap: var(--s-4); }
  .p-farm .fm-clean-row dt{ padding-top: 0; }
  .p-farm .fm-clean-row--fed dd{ grid-template-columns: 1fr; gap: var(--s-5); }
  .p-farm .fm-clean-media{ max-width: 22rem; }
}

/* -------------------------------------------------------------- 50 education */
.p-farm .fm-edu-head{
  margin-bottom: var(--space-block);
}
.p-farm .fm-edu-head .micro{ margin-bottom: var(--s-5); }
.p-farm .fm-edu-lede{
  margin-top: var(--s-5);
  max-width: 34ch;
  color: var(--text-2);
}

.p-farm .fm-edu-list{
  display: flex;
  flex-direction: column;
  gap: var(--space-block);
  margin-bottom: var(--space-block);
}
.p-farm .fm-edu-item{
  display: grid;
  grid-template-columns: minmax(0, 5rem) minmax(0, 1fr);
  gap: var(--space-block);
  align-items: start;
  padding-top: var(--space-group);
  border-top: 1px solid var(--rule);
}
/* Grid-break: alternate entries step in from the left well, so the index column
   reads as a ragged margin rather than a table. Editorial, not a card stack. */
@media (min-width: 901px){
  .p-farm .fm-edu-item:nth-child(even){ margin-left: clamp(3rem, 8vw, 9rem); }
  .p-farm .fm-edu-item:nth-child(even) .fm-edu-body{ max-width: 52ch; }
}
.p-farm .fm-edu-n{
  font-family: var(--f-display);
  font-size: var(--t-h1);
  font-weight: var(--w-display);
  line-height: 1;
  color: var(--accent);              /* ornament: a ghost numeral carries no meaning */
  opacity: .5;
}
.p-farm .fm-edu-body h3{ margin-bottom: var(--s-4); }
.p-farm .fm-edu-body .prose{ max-width: 58ch; }
.p-farm .fm-edu-body .prose em{ color: var(--accent-text); }

@media (max-width: 900px){
  .p-farm .fm-edu-item{ grid-template-columns: 1fr; gap: var(--s-4); }
  .p-farm .fm-edu-n{ font-size: var(--t-h2); }
}

.p-farm .fm-edu-media{ margin-top: var(--space-block); }
.p-farm .fm-edu-media .frame-cap{ margin-top: var(--s-3); }
@media (max-width: 700px){
  .p-farm .fm-edu-media .frame{ --frame-ratio: 4 / 3; }
}

/* ------------------------------------------------------------------ 60 visit */
.p-farm .fm-visit-inner{ text-align: left; max-width: 42ch; }
.p-farm .fm-visit-inner .micro{ margin-bottom: var(--s-5); }
.p-farm .fm-visit-h{
  font-size: var(--t-h1);
  font-weight: var(--w-h1);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
  margin-bottom: var(--s-5);
}
.p-farm .fm-visit-h em{ color: var(--accent-text); }
.p-farm .fm-visit-inner .prose{ margin-bottom: var(--space-block); }
.p-farm .fm-visit-addr{ margin-top: var(--s-6); letter-spacing: .06em; }


/* ===== css/pages/fishes.css ===== */
/* ============================================================================
   fishes.css — "Our Arowana". OWNER: imperial-fishes.
   Every selector scoped under .p-fishes; every @keyframes named fishes-*
   (CONTRACT.md §3 — one site.css = one namespace).

   THE SIGNATURE: the bloodline REGISTER (.fx-lines) — the varieties are set as
   an editorial ledger, never as a labelled photo catalogue. That is not a
   styling preference, it is MANIFEST R2: no photo↔variety mapping is verified,
   so pairing a name to a picture (caption, spread OR hover-peek) fabricates a
   claim. It is also the better call — a per-variety card grid is precisely the
   aquariusavenue anti-reference (BRIEF §3). Constraint and taste agree here.

   Colour: --bl-* are read ONLY on the register strokes, where the thing IS a
   bloodline (CONTRACT §2). They key off the COLOUR WORD IN THE NAME (Blood Red →
   red, Milk Golden → gold), never off a claim about a fish's appearance; names
   carrying no colour word (Albino Crossback, 24K Purple) take the neutral ink.
   ========================================================================== */

/* --- KIT BUG WORKAROUND (motion-kit.css is scaffold's file; the source fix is qa's call
   — full diagnosis in HANDOFFS 19:34). `html.js [data-reveal]{opacity:0}` at
   motion-kit.css:33 is (0,2,1) and out-specifies the kit's own reveal at :39
   `[data-reveal].is-in{opacity:1}` at (0,2,0), so .is-in lands and nothing happens:
   every [data-reveal] stays opacity 0 for real USERS. The reduced-motion hatch at :308
   uses !important, which is why an RM pass renders perfectly and hides this.
   Re-asserted here at (0,3,0); home/farm/contact all carry the same patch. Delete this
   block the moment the kit line is fixed — my verify script's "all reveals fired after
   priming" assert is the regression test either way. --- */
.p-fishes [data-reveal].is-in{ opacity: 1; transform: none; }
.p-fishes .mk-mask{ opacity: 1; }

/* ------------------------------------------------------------------ 1. hero */
.p-fishes .fx-hero{ padding-bottom: var(--space-block); }

.p-fishes .fx-hero-grid{
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-block);
  align-items: end;
}
@media (min-width: 900px){
  .p-fishes .fx-hero-grid{
    grid-template-columns: 1.15fr .85fr;
    gap: clamp(2rem, 4vw, 4.5rem);
  }
}

.p-fishes .fx-hero-title{
  font-size: var(--t-display);
  font-weight: var(--w-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
}

.p-fishes .fx-hero-note{
  margin-top: var(--s-2);
  display: inline-flex;
}

/* the hero clip is the page's one eager media slot.
   --frame-pos (base.css .frame hook, CONTRACT §3) pulls the 4:5 crop left of centre: the
   16:9 source keeps only ~45% of its width here, and centred that window lands on the tank
   pump at x≈1700/1920 while pushing the head out. 35% holds the eye and drops the equipment.
   Retune this number, not the markup, if the clip is ever re-cut. */
.p-fishes .fx-hero-media{ align-self: stretch; min-height: 0; --frame-pos: 35% 50%; }

/* -------------------------------------------------------------- 2. register */
.p-fishes .fx-register-head{
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-block);
  margin-bottom: var(--space-block);
}
@media (min-width: 900px){
  .p-fishes .fx-register-head{
    grid-template-columns: 1fr 1fr;
    align-items: end;
    gap: clamp(2rem, 5vw, 5rem);
  }
}
.p-fishes .fx-register-intro{ color: var(--text-2); }

.p-fishes .fx-lines{
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--rule);
}

/* The ledger row. Grid, not flex: the stroke column must hold its width even
   when a row has no note and no tag. */
.p-fishes .fx-line{
  --bl: var(--bl-black);
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  gap: var(--s-4);
  padding-block: var(--space-group);
  border-bottom: 1px solid var(--rule);
}
@media (min-width: 760px){
  .p-fishes .fx-line{
    grid-template-columns: auto 1fr auto;
    gap: var(--s-5);
    padding-block: var(--s-5);
  }
}

.p-fishes .fx-line[data-bl="red"]{ --bl: var(--bl-red); }
.p-fishes .fx-line[data-bl="golden"]{ --bl: var(--bl-golden); }
.p-fishes .fx-line[data-bl="blue"]{ --bl: var(--bl-blue); }
.p-fishes .fx-line[data-bl="black"]{ --bl: var(--bl-black); }

/* the bloodline stroke — ornament, keyed to the name's own colour word */
.p-fishes .fx-line-stroke{
  width: 3px;
  height: 1em;
  background: var(--bl);
  transform: scaleY(.62);
  transform-origin: 50% 50%;
  transition: transform var(--d-2) var(--ease-hover);
  align-self: center;
}
@media (min-width: 760px){
  .p-fishes .fx-line-stroke{ height: 2.1em; }
}

.p-fishes .fx-line-name{
  font-size: var(--t-h2);
  font-weight: var(--w-h2);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
  transition: transform var(--d-2) var(--ease-hover);
}

/* Flagships are client-evidenced (CLIENT-INFO: "high-end Blood Red and Crossback
   Golden"), so they earn real scale — the register has a top, not a uniform list.
   Size descends → weight ascends (tokens.css §3): at h1 size the name drops to
   --w-h1 and tightens tracking. Keep the inversion. */
.p-fishes .fx-line--flag .fx-line-name{
  font-size: var(--t-h1);
  font-weight: var(--w-h1);
  letter-spacing: var(--ls-h);
}
.p-fishes .fx-line--flag{ padding-block: var(--space-block); }

.p-fishes .fx-line-note{
  margin-top: var(--s-3);
  max-width: 52ch;
  font-size: var(--t-small);
  line-height: var(--lh-body);
  color: var(--text-3);
}

.p-fishes .fx-line-tag{
  grid-column: 2;
  margin-top: var(--s-2);
  color: var(--seal-ink);
}
@media (min-width: 760px){
  .p-fishes .fx-line-tag{
    grid-column: 3;
    margin-top: 0;
    align-self: center;
    white-space: nowrap;
  }
}

.p-fishes .fx-line--more .fx-line-name{ color: var(--text-2); }

@media (hover: hover) and (pointer: fine){
  .p-fishes .fx-line:hover .fx-line-stroke{ transform: scaleY(1); }
  .p-fishes .fx-line:hover .fx-line-name{ transform: translateX(6px); }
}

.p-fishes .fx-register-foot{
  margin-top: var(--space-block);
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule-accent);
  font-size: var(--t-small);
  color: var(--text-3);
}

/* ---------------------------------------------------------- 3. cinema island */
.p-fishes .fx-island-head{ margin-bottom: var(--space-block); }

.p-fishes .fx-island-title{
  font-size: var(--t-h1);
  font-weight: var(--w-h1);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
}

/* The full-bleed lead. Edge-to-edge by living outside .wrap (see the partial) —
   no 100vw breakout, so the scrollbar-overflow bug is unreachable. This is the
   page's one full-bleed beat; the register carries the type, this carries the
   scale. Its caption re-enters .wrap so it stays on the text measure. */
.p-fishes .fx-island-lead{
  margin-block: var(--space-block);
}
.p-fishes .fx-island-lead .frame{ --frame-ratio: 16 / 9; }
@media (min-width: 760px){
  .p-fishes .fx-island-lead .frame{ --frame-ratio: 21 / 9; }   /* letterbox = cinema */
}

.p-fishes .fx-island-grid{
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-block);
}
/* Asymmetric on desktop: two stills with the copy held between them, the tall
   one dropped down the page so the row never reads as a strip of equal cards.
   Deliberately NOT a rail — the horizontal rail is home's signature (BRIEF §3
   move 3) and duplicating it here would flatten both. */
@media (min-width: 900px){
  .p-fishes .fx-island-grid{
    grid-template-columns: 1.4fr .6fr 1fr;
    grid-template-areas: "wide aside tall";
    gap: clamp(1.5rem, 2.5vw, 2.5rem);
    align-items: start;
  }
  .p-fishes .fx-island-tall{ grid-area: tall; margin-top: clamp(2rem, 5vw, 5rem); }
  .p-fishes .fx-island-wide{ grid-area: wide; }
  .p-fishes .fx-island-aside{ grid-area: aside; align-self: end; }
}

.p-fishes .fx-island-aside{
  font-size: var(--t-small);
  line-height: var(--lh-body);
  color: var(--text-2);
}

/* ----------------------------------------------------------------- 4. match */
.p-fishes .fx-match-head{ margin-bottom: var(--space-block); }

.p-fishes .fx-steps{
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  border-top: 1px solid var(--rule);
}
@media (min-width: 900px){
  .p-fishes .fx-steps{ grid-template-columns: 1fr 1fr; }
  /* step 03 carries the worked example — it takes the full measure */
  .p-fishes .fx-step--wide{ grid-column: 1 / -1; }
}

.p-fishes .fx-step{
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-5);
  padding: var(--space-block) 0;
  border-bottom: 1px solid var(--rule);
}
@media (min-width: 900px){
  .p-fishes .fx-step{ padding-right: clamp(1.5rem, 4vw, 4rem); }
  .p-fishes .fx-step:nth-child(odd):not(.fx-step--wide){ border-right: 1px solid var(--rule); }
  .p-fishes .fx-step:nth-child(even):not(.fx-step--wide){ padding-left: clamp(1.5rem, 4vw, 4rem); padding-right: 0; }
}

.p-fishes .fx-step-n{
  color: var(--accent-text);
  padding-top: .35em;
}

.p-fishes .fx-step-body h3{ margin-bottom: var(--s-3); }
.p-fishes .fx-step-body .prose{ color: var(--text-2); font-size: var(--t-small); line-height: var(--lh-body); }

/* the worked example — a quiet inset well, the one place copy is quoted */
.p-fishes .fx-example{
  margin-top: var(--s-5);
  padding: var(--s-5);
  background: var(--surface-3);
  border-left: 2px solid var(--accent);
}
.p-fishes .fx-example blockquote{
  font-family: var(--f-display);
  font-variant-ligatures: none;
  font-size: var(--t-h3);
  font-weight: var(--w-h3);
  line-height: 1.35;
  color: var(--text);
}
.p-fishes .fx-example figcaption{
  margin-top: var(--s-3);
  color: var(--text-3);
}

/* ------------------------------------------------------------------ 5. wait */
.p-fishes .fx-wait-grid{
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-block);
  align-items: center;
}
@media (min-width: 900px){
  .p-fishes .fx-wait-grid{
    grid-template-columns: .85fr 1.15fr;
    gap: clamp(2rem, 5vw, 5rem);
  }
}

.p-fishes .fx-facts{
  margin: var(--s-5) 0 0;
  display: grid;
  gap: var(--s-5);
}
.p-fishes .fx-fact{
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
}
.p-fishes .fx-fact dt{ color: var(--accent-text); margin-bottom: var(--s-2); }
.p-fishes .fx-fact dd{
  margin: 0;
  max-width: 56ch;
  font-size: var(--t-small);
  line-height: var(--lh-body);
  color: var(--text-2);
}

/* -------------------------------------------------------------- 6. SOP rail */
.p-fishes .fx-sop-head{
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-block);
  margin-bottom: var(--space-section);
}
@media (min-width: 900px){
  .p-fishes .fx-sop-head{
    grid-template-columns: 1fr 1fr;
    align-items: end;
    gap: clamp(2rem, 5vw, 5rem);
  }
}
.p-fishes .fx-sop-intro{ color: var(--text-2); }

.p-fishes .fx-timeline{
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-block);
  position: relative;
}

/* The connecting line IS the timeline — one hairline threaded through the dots.
   Vertical at 390, horizontal from 760. */
.p-fishes .fx-timeline::before{
  content: "";
  position: absolute;
  left: 5px;
  top: .5rem;
  bottom: .5rem;
  width: 1px;
  background: var(--rule-accent);
}
@media (min-width: 760px){
  .p-fishes .fx-timeline{
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(1.5rem, 4vw, 3.5rem);
  }
  .p-fishes .fx-timeline::before{
    left: 0;
    right: 0;
    top: 5px;
    bottom: auto;
    width: auto;
    height: 1px;
  }
}

.p-fishes .fx-tl{
  position: relative;
  padding-left: var(--s-6);
}
@media (min-width: 760px){
  .p-fishes .fx-tl{ padding-left: 0; padding-top: var(--s-6); }
}

.p-fishes .fx-tl-dot{
  position: absolute;
  left: 0;
  top: .45rem;
  width: 11px;
  height: 11px;
  border-radius: var(--r-pill);
  background: var(--surface);
  box-shadow: inset 0 0 0 2px var(--accent);
}
@media (min-width: 760px){
  .p-fishes .fx-tl-dot{ top: 0; left: 0; }
}
/* the collection day is the one that matters — fill it */
.p-fishes .fx-tl:last-child .fx-tl-dot{ background: var(--seal-ink); box-shadow: inset 0 0 0 2px var(--seal-ink); }

.p-fishes .fx-tl-day{ color: var(--accent-text); }
.p-fishes .fx-tl-name{
  margin: var(--s-3) 0 var(--s-3);
  font-size: var(--t-h3);
}
.p-fishes .fx-tl-note{
  max-width: 44ch;
  font-size: var(--t-small);
  line-height: var(--lh-body);
  color: var(--text-3);
}

.p-fishes .fx-sop-foot{
  margin-top: var(--space-block);
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
}

/* ------------------------------------------------------------------- 7. CTA */
.p-fishes .fx-cta-inner{ text-align: center; align-items: center; }
.p-fishes .fx-cta-title{
  font-size: var(--t-h1);
  font-weight: var(--w-h1);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
}
.p-fishes .fx-cta-inner .lede{ max-width: 56ch; color: var(--text-2); }
.p-fishes .fx-cta-note{ font-size: var(--t-small); }


/* ---------------------------------------------------- 25. client album (.fx-album)
   Curated slideshow of the client's own photography (drop 2026-07-19; slideshow
   per Charlie 2026-07-20 — the masonry read untidy). Component: base.css §18. */
.p-fishes .fx-album-head{ margin-bottom: var(--space-block); }
.p-fishes .fx-album-head h2{ margin-top: var(--s-5); font-size: var(--t-h1); font-weight: var(--w-h2); line-height: 1.22; letter-spacing: var(--ls-h); font-family: var(--f-display); }
.p-fishes .fx-album-lede{ margin-top: var(--s-5); max-width: 56ch; }


/* ===== css/pages/guide.css ===== */
/* ============================================================================
   guide.css — long-form blog post (the arowana care guide). Namespace: .p-guide.
   Reading column rides .wrap--narrow (880px); article measure capped at ~68ch.
   ========================================================================== */

/* Reveal-cascade workaround, same as farm/fishes/media. */
.p-guide [data-reveal].is-in{ opacity: 1; transform: none; }
.p-guide [data-reveal].mk-mask{ opacity: 1; transform: none; }

/* ══ 10 · hero ══ */
.p-guide .gd-crumb{ margin-bottom: var(--s-6); color: var(--text-2); }
.p-guide .gd-crumb a{ color: var(--accent-text); }
.p-guide .gd-title{
  font-size: var(--t-h1);
  max-width: 22ch;
}
.p-guide .gd-meta{ margin-top: var(--s-5); }
.p-guide .gd-lede{ margin-top: var(--s-5); max-width: 60ch; }
.p-guide .gd-lede a{ color: inherit; border-bottom: 1px solid var(--rule-accent); }
.p-guide .gd-figure{ margin: clamp(2.25rem, 1.75rem + 2vw, 3.5rem) 0 0; }

/* ══ 20 · article ══ */
.p-guide .gd-body{ padding-top: 0; }
.p-guide .gd-article{ max-width: 68ch; }
.p-guide .gd-article h2{
  margin-top: clamp(2.75rem, 2.25rem + 2vw, 4rem);
  font-family: var(--f-display);
  font-size: var(--t-h2);
  font-weight: var(--w-h2);
  line-height: 1.24;
  letter-spacing: var(--ls-h);
}
.p-guide .gd-article h2:first-child{ margin-top: 0; }
.p-guide .gd-article p{ margin-top: var(--s-5); }
.p-guide .gd-article p strong{ font-weight: 600; }
.p-guide .gd-article a{ color: inherit; border-bottom: 1px solid var(--rule-accent); transition: border-color var(--d-2) var(--ease-hover); }
.p-guide .gd-article a:hover{ border-color: var(--accent-text); }
.p-guide .gd-article ul{
  list-style: disc;                 /* base reset strips markers — restore them here */
  margin-top: var(--s-5);
  padding-left: 1.1em;
  display: flex; flex-direction: column; gap: var(--s-4);
}
.p-guide .gd-article li::marker{ color: var(--accent-text); }

.p-guide .gd-faq{
  margin-top: clamp(3rem, 2.5rem + 2vw, 4.5rem);
  border-top: 1px solid var(--rule);
  padding-top: clamp(1.75rem, 1.5rem + 1vw, 2.5rem);
}
.p-guide .gd-faq dl{ margin-top: var(--s-5); display: flex; flex-direction: column; }
.p-guide .gd-faq-item{ padding-block: var(--s-5); border-bottom: 1px solid var(--rule); }
.p-guide .gd-faq-item:last-child{ border-bottom: 0; }
.p-guide .gd-faq dt{ font-weight: 600; }
.p-guide .gd-faq dd{ margin: var(--s-3) 0 0; color: var(--text-2); }

/* ══ 30 · cta ══ */
.p-guide .gd-cta-inner{ text-align: center; align-items: center; }
.p-guide .gd-cta-title{
  font-size: var(--t-h1);
  font-weight: var(--w-h1);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
}
.p-guide .gd-cta-inner .lede{ max-width: 56ch; color: var(--text-2); }


/* ===== css/pages/home.css ===== */
/* ============================================================================
   home.css — imperial-home. Scoped under .p-home (build.mjs puts it on <body>);
   every @keyframes is home-*. One site.css = one namespace (CONTRACT §3).

   Reads SEMANTIC roles only — never a --c-* primitive. The narrative island sets
   data-surface="deep" and every colour below re-points itself. That is why this
   file declares almost no colour: the surface does it.
   ========================================================================== */

/* ══════════════════════════════════════ 0 · MOTION-KIT SPECIFICITY WORKAROUND ⚠
   THIS IS NOT MY BUG AND IT IS NOT COSMETIC — without it this page photographs BLANK.
   Raised to scaffold in HANDOFFS (19:31); js/motion-kit.css is theirs, so the real fix
   is one word in their file and this is the scoped stopgap until it lands. Harmless if
   they fix it (identical declarations, just a heavier selector).

   The kit gates reveals with:
       html.js [data-reveal]  { opacity: 0 }        → (0,2,1)
       [data-reveal].is-in    { opacity: 1 }        → (0,2,0)   ← LOSES. Always.
   Same shape for [data-clip] (clip-path never re-opens). So the kit adds .is-in exactly
   as designed, and the element still never comes back: measured clip-path
   inset(0px 0px 100%) and opacity 0 WITH .is-in present, 3s after load.
   [data-stagger].is-in .mk-stag (0,3,0) beats its gate, which is why only staggered
   children were visible and the bug reads as "some sections work".

   These two rules score (0,3,0) and win on class count. No !important: the kit's
   prefers-reduced-motion floor IS !important and must keep overriding both.
   Masked headings need nothing extra — .mk-mask carries [data-reveal] + .is-in, so the
   first rule un-gates it and .mk-mask.is-in .mk-line-i (0,3,0) already drives the lines. */
.p-home [data-reveal].is-in{ opacity: 1; transform: none; }
.p-home [data-clip].is-in{ clip-path: inset(0 0 0 0); }

/* ══════════════════════════════════════════════════════════ 10 · HERO
   Full-bleed film + a warm-paper wash on the type side. The wash is the contrast
   guarantee: the film is graded light (a red arowana on a pale ground), so ink type
   over bare footage would ride 3:1 in places. The gradient holds --text at ≥12:1
   across the copy column at every viewport, and it keeps the site LIGHT instead of
   defaulting to the white-on-dark hero BRIEF §3 gates. */
.p-home .hero{
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  overflow: hidden;
  padding-bottom: var(--s-8);
}
.p-home .hero-media{
  position: absolute; inset: 0;
  z-index: var(--z-media);
}
.p-home .hero-vid{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 58% 50%;   /* keep the fish off the copy column */
}
/* Two washes, not one: a horizontal ramp protects the copy column on wide screens,
   a vertical ramp does it at 390 where the copy sits over the middle of the frame. */
/* Pulled back. Was .97@30% -> .82@46%, which held ≥82% opaque paper across the left ~46%
   of the frame: the film was not full-bleed in effect, it was erased over nearly half the
   hero, and the wash read as media-hiding rather than framing. The copy column ends at
   ~43% of 1440 (wrap 80px + hero-copy max 34rem), so paper only has to carry to there —
   past it the film is the point. Re-measured on composited pixels, not the cascade:
   ink on the wash over the film stays ≥12:1 across the whole copy column at 390/768/1440. */
.p-home .hero-wash{
  position: absolute; inset: 0;
  background:
    linear-gradient(96deg,
      var(--c-paper) 0%,
      rgba(247, 244, 237, .94) 24%,
      rgba(247, 244, 237, .74) 42%,
      rgba(247, 244, 237, .30) 62%,
      rgba(247, 244, 237, .05) 82%,
      rgba(247, 244, 237, 0) 100%),
    linear-gradient(180deg,
      rgba(247, 244, 237, .55) 0%,
      rgba(247, 244, 237, 0) 34%);
}

/* The brand beat: the lockup rises over the film, bottom-right, balancing the copy at
   left and landing after it (620ms) so the sequence reads copy -> mark, not both at once.
   Bottom-right and not centred: centre-over-film is the gradient-blob hero the brief gates,
   and the scroll cue owns bottom-left. */
.p-home .hero-mark{
  position: absolute;
  z-index: var(--z-content);
  right: clamp(1.5rem, 5vw, 4.5rem);
  bottom: clamp(2.5rem, 9vh, 5.5rem);
  width: clamp(8.5rem, 15vw, 15rem);
}
.p-home .hero-mark img{
  display: block;
  width: 100%; height: auto;
  opacity: .92;
}
@media (max-width: 900px){
  /* At 390 the copy owns the fold and the film is behind it — a mark here would stack
     into the type. It rides the lower third instead, smaller and out of the copy's way. */
  .p-home .hero-mark{
    right: auto; left: var(--gutter);
    bottom: clamp(1.5rem, 5vh, 3rem);
    width: clamp(7rem, 26vw, 9.5rem);
  }
}
.p-home .hero-inner{
  position: relative;
  z-index: var(--z-content);
  width: min(100% - var(--gutter) * 2, var(--maxw));
}
.p-home .hero-copy{ max-width: 34rem; }
.p-home .hero-kicker{ margin-bottom: var(--s-5); }
.p-home .hero-title{ margin-bottom: var(--s-5); }
.p-home .hero-sub{ margin-bottom: var(--s-6); max-width: 30rem; }

/* scroll cue — a hairline that drains and refills, not a bouncing chevron */
.p-home .hero-cue{
  position: absolute;
  left: var(--gutter); bottom: var(--s-6);
  z-index: var(--z-content);
  display: inline-flex; align-items: center; gap: var(--s-3);
  color: var(--text-3);
  text-decoration: none;
}
.p-home .hero-cue i{
  display: block;
  width: 1px; height: 44px;
  background: var(--rule-2);
  overflow: hidden;
  position: relative;
}
.p-home .hero-cue i::after{
  content: "";
  position: absolute; inset: 0;
  background: var(--accent);
  transform-origin: 50% 0;
  animation: home-cue 2.4s var(--ease-inout) infinite;
}
@keyframes home-cue{
  0%   { transform: scaleY(0); transform-origin: 50% 0; }
  45%  { transform: scaleY(1); transform-origin: 50% 0; }
  55%  { transform: scaleY(1); transform-origin: 50% 100%; }
  100% { transform: scaleY(0); transform-origin: 50% 100%; }
}
@media (prefers-reduced-motion: reduce){
  .p-home .hero-cue i::after{ animation: none; transform: scaleY(1); opacity: .5; }
}
@media (max-width: 900px){
  .p-home .hero-vid{ object-position: 62% 42%; }
  .p-home .hero-copy{ max-width: none; }
  .p-home .hero-cue{ display: none; }   /* at 390 the fold already says "scroll" */
}

/* ══════════════════════════════════════════════════════════ 20 · INTRO
   Asymmetric editorial split — the statement holds the left column and stays put,
   the argument runs down the right. Not a centred block. */
.p-home .intro-grid{
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(2rem, 1rem + 5vw, 6rem);
  align-items: start;
}
.p-home .intro-lead{ position: sticky; top: calc(var(--nav-h) + var(--s-6)); }
.p-home .intro-title{
  margin-top: var(--s-5);
  font-size: var(--t-h1);
  max-width: 12ch;               /* force the break — 3 tight lines of Cormorant */
}
.p-home .intro-prose{ margin-bottom: var(--space-block); }
.p-home .intro-muted{
  font-size: var(--t-lede);
  line-height: var(--lh-lede);
  max-width: 44ch;
  padding-left: var(--s-5);
  border-left: 1px solid var(--rule-accent);
}
/* Centred colophon closing the section (Charlie 2026-07-18: "chop centralise") —
   the seal sits on the section's centre axis, note centred beneath it. */
.p-home .intro-chop{
  margin-top: var(--space-block);
  padding-top: var(--space-block);
  border-top: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s-4);
}
/* The client's real mark (ink-on-transparent PNG, 564×364). Width tuned so the
   medallion core reads at roughly the old seal's presence; height follows ratio. */
.p-home .intro-mark{
  width: clamp(180px, 20vw, 250px);
  height: auto;
}
.p-home .intro-chop-note{
  font-size: var(--t-small);
  line-height: 1.6;
  max-width: 52ch;
}
/* ══ 65 · ON FILM — one press row, type column against the film plate. The .md-cap
   flex pairing (credit + quoted title) is shared with media.css by classname; the
   layout here is home's own. */
.p-home .film-row{
  display: grid;
  grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: var(--gutter);
  align-items: center;
}
.p-home .film-copy .lede{ max-width: 36ch; margin-bottom: var(--s-5); }
.p-home .film-row .md-cap{
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2) var(--s-3);
}
@media (max-width: 900px){
  .p-home .film-row{ grid-template-columns: 1fr; }
}

/* Registration receipts under the colophon — quiet by design, tabular-adjacent
   letterspacing from .micro; never louder than the mark above it. */
.p-home .intro-reg{ margin-top: var(--s-2); }
/* 皇宫 — the brand's real name. An explicit CJK stack so it never falls back to a
   fake-bolded default, and a size where the fallback difference cannot be read. */
.p-home .intro-han{
  font-family: "Songti SC", "Noto Serif SC", "Source Han Serif SC", "PingFang SC",
               "Hiragino Sans GB", "Microsoft YaHei", serif;
  font-size: 1.5em;
  line-height: 1;
  color: var(--seal-ink);
  margin-right: .3em;
  vertical-align: -.08em;
}
@media (max-width: 900px){
  .p-home .intro-grid{ grid-template-columns: 1fr; gap: var(--space-block); }
  .p-home .intro-lead{ position: static; }
  .p-home .intro-title{ max-width: none; }
  .p-home .intro-chop{ gap: var(--s-4); }
}

/* ══════════════════════════════════════════════════════ 30 · NARRATIVE ⭐
   The signature. Sticky underlay + negative-margin overlap = the "fixed" film without
   position:fixed (which the nav's backdrop-filter containing block would break, and
   which would bleed behind the whole document).
   The pull MUST equal the media height exactly or the section opens with a 100svh hole. */
.p-home .nar{ position: relative; }
.p-home .nar-media{
  position: sticky; top: 0;
  height: 100svh;
  margin-bottom: -100svh;        /* == height. Keep these two in lockstep. */
  z-index: var(--z-media);
  overflow: hidden;
}
.p-home .nar-vid{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* The veil is the contrast contract, not decoration — and it is now PAPER, not navy.
   Prior version stacked radial .74→.95 over a flat .5, compositing to ~87% opaque at its
   most transparent point: 1-(1-.74)(1-.5)=.87. The fish were fully loaded and playing and
   simply could not be seen. farm.css .fm-lineage-veil already states the rule this broke —
   "Veiling toward the subject hides it and showcases the empty half."

   Panels alternate left/right, so this cannot be one-directional like farm's: it opens a
   WINDOW down the centre (where the fish swim) and weights paper at both edges (where the
   cards sit). Centre thinned to .14 (judge R7 P2: at the old .30 the underlay measured
   YAVG ~226 = invisible; sig-move #2 wasn't paying). Composited centre ≈ 1-(1-.14)(1-.22)
   = .33 paper over a pale clip → the schooling fish now legibly read; edges ≈
   1-(1-.88)(1-.52) = .94 → effectively paper under type. The thin only touches the centre
   window; edge/text contrast is unchanged. Ink #121417 on the veiled clip stays ≥12:1 at
   every sampled panel (qa/nar-contrast). Do not thin the EDGE stops without re-measuring;
   the centre is free — nothing but fish live there. */
.p-home .nar-veil{
  position: absolute; inset: 0;
  background:
    linear-gradient(90deg,
      rgba(247, 244, 237, .88) 0%,
      rgba(247, 244, 237, .48) 22%,
      rgba(247, 244, 237, .14) 50%,
      rgba(247, 244, 237, .48) 78%,
      rgba(247, 244, 237, .88) 100%),
    linear-gradient(180deg,
      rgba(247, 244, 237, .52) 0%,
      rgba(247, 244, 237, .22) 32%,
      rgba(247, 244, 237, .22) 68%,
      rgba(247, 244, 237, .52) 100%);
}
.p-home .nar-flow{
  position: relative;
  z-index: var(--z-content);
}
.p-home .nar-open{
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
}
.p-home .nar-title{
  margin-top: var(--s-5);
  font-size: var(--t-display);
  font-weight: var(--w-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
}
.p-home .nar-open-lede{ margin: var(--s-6) auto 0; }

.p-home .nar-panel{
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-block: var(--space-block);
}
.p-home .nar-panel .wrap{ display: flex; }
.p-home .nar-panel--l .wrap{ justify-content: flex-start; }
.p-home .nar-panel--r .wrap{ justify-content: flex-end; }

/* The card is a held breath, not a box: a hairline top rule and a whisper of the
   water behind it. No filled panel, no shadow stack.

   The veil no longer does all the lifting (it opens a centre window so the fish read),
   so type gets a LOCAL halo instead: an oversized soft radial that fades to zero before
   it reaches an edge. No border, no corner, no visible box — the paper just thickens
   where the words are. This is what keeps "held breath" true while ink rides a live clip;
   a filled panel here would be the card-slop the brief gates. */
.p-home .nar-card{
  position: relative;
  max-width: 30rem;
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule-accent);
}
/* DIRECTIONAL, not radial, and feathered top/bottom by a mask. A radial sized to cover
   the type does not reach zero before the element's own edge, so it paints a visible
   rectangle — the filled card this section must not have (caught on the render, not in
   review: it looked correct in CSS). So: paper fades ONLY toward the water, the outer
   side runs into the veil's own .88 edge weighting and disappears into it, and the mask
   feathers the horizontal bounds so no top/bottom seam can exist. */
.p-home .nar-card::before{
  content: "";
  position: absolute;
  inset: calc(var(--s-7) * -1) calc(var(--s-7) * -1);
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(to right,
    rgba(247, 244, 237, .95) 0%,
    rgba(247, 244, 237, .88) 44%,
    rgba(247, 244, 237, .42) 76%,
    rgba(247, 244, 237, 0) 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 16%, #000 84%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 16%, #000 84%, transparent 100%);
}
.p-home .nar-panel--r .nar-card::before{
  background: linear-gradient(to left,
    rgba(247, 244, 237, .95) 0%,
    rgba(247, 244, 237, .88) 44%,
    rgba(247, 244, 237, .42) 76%,
    rgba(247, 244, 237, 0) 100%);
}
.p-home .nar-step{
  display: flex; align-items: center; gap: var(--s-4);
  margin-bottom: var(--s-4);
  font-family: var(--f-body);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: var(--ls-micro);
  color: var(--accent-text);
}
.p-home .nar-step-rule{
  flex: 1 1 auto;
  height: 1px;
  background: var(--rule);
}
.p-home .nar-h{
  font-size: var(--t-h1);
  font-weight: var(--w-h1);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
  margin-bottom: var(--s-4);
}
.p-home .nar-prose{ color: var(--text-2); }
.p-home .nar-card--sop{ border-top-color: var(--seal-ink); }

@media (max-width: 900px){
  .p-home .nar-panel--r .wrap{ justify-content: flex-start; }
  .p-home .nar-card{ max-width: none; }
  /* The halo's -3rem bleed is wider than the gutter once max-width is dropped above, so it
     pushed 28px past the viewport (card right 370 + 48 = 418 at 390). Bleed to the gutter
     instead: the paper still runs off both screen edges, it just stops at the edge. */
  .p-home .nar-card::before{ inset-inline: calc(var(--gutter) * -1); }

  /* NO pinned water on phones (Charlie IMG_1474, 2026-07-21): a 16:9 clip
     cover-cropped into a portrait viewport is ~3.4× zoomed featureless water, and
     full-viewport panels around ~300px cards left half a screen of that blur
     between every step — it reads as a broken page, not a signature. Mobile gets
     paper and a tight step sequence; the fish stay a desktop move. With .nar-media
     display:none it never intersects, so home.js never attaches src/poster —
     the clip costs zero bytes here. */
  .p-home .nar-media{ display: none; }
  .p-home .nar-open{ min-height: 0; padding-block: var(--space-section) var(--space-block); }
  .p-home .nar-panel{ min-height: 0; padding-block: clamp(1.6rem, 5vw, 2.75rem); }
}

/* ══════════════════════════════════════════════════════════ 40 · RAIL ⭐
   Traverse. Structure comes from motion-kit (.hscroll-sticky / [data-hscroll-track]);
   this is the dressing. Widths deliberately unequal — a run of identical tiles is the
   conveyor-belt tell, and rhythm is the whole point of a horizontal read. */
.p-home .rail{ padding-top: var(--space-section); }
.p-home .rail-head{ margin-bottom: var(--space-block); }
.p-home .rail-title{
  margin-top: var(--s-5);
  font-size: var(--t-h1);
}
.p-home .rail-cap{
  margin-top: var(--s-4);
  font-size: var(--t-small);
  max-width: 46ch;
}
.p-home .rail-track{ align-items: center; }
.p-home .rail-item{
  flex: 0 0 auto;
  width: var(--rail-w, 30vw);
}
.p-home .rail-item--wide  { --rail-w: clamp(20rem, 44vw, 40rem); }
.p-home .rail-item--mid   { --rail-w: clamp(15rem, 30vw, 26rem); }
.p-home .rail-item--tall  { --rail-w: clamp(13rem, 25vw, 22rem); }
.p-home .rail-item--narrow{ --rail-w: clamp(11rem, 20vw, 17rem); }
/* the tall/narrow items ride lower — breaks the centre line, kills the conveyor read */
.p-home .rail-item--tall,
.p-home .rail-item--narrow{ transform: translateY(clamp(1rem, 3vw, 2.5rem)); }
.p-home .rail-item--last{ padding-right: var(--gutter); }
.p-home .rail-vid{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.p-home .rail-item .frame-cap .micro{ color: var(--text-3); }

@media (max-width: 900px){
  /* No pin on touch: a hijacked vertical scroll on a phone is a hostage situation.
     The kit still translates the track, but a native swipe is the honest interaction —
     so the rail becomes a snap scroller and [data-hscroll]'s height is neutralised. */
  .p-home .rail-scroller{ height: auto !important; }
  .p-home .rail-scroller .hscroll-sticky{
    position: static;
    height: auto;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .p-home .rail-scroller .hscroll-sticky::-webkit-scrollbar{ display: none; }
  .p-home .rail-track{
    transform: none !important;   /* beat the kit's inline translate3d */
    padding-block: var(--s-2);
  }
  .p-home .rail-item{
    scroll-snap-align: center;
    --rail-w: 74vw;
  }
  .p-home .rail-item--narrow, .p-home .rail-item--tall{
    --rail-w: 58vw;
    transform: none;
  }
}

/* ══════════════════════════════════════════════════════ 50 · SERVICES
   Editorial index. Rows, rules, numbers — no cards. */
.p-home .svc-head{
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 1rem + 3vw, 4rem);
  align-items: end;
  margin-bottom: var(--space-block);
}
.p-home .svc-title{ margin-top: var(--s-5); font-size: var(--t-h1); }
.p-home .svc-head-r{ padding-bottom: var(--s-2); }

.p-home .svc-index{ border-top: 1px solid var(--rule); }
.p-home .svc-row{
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: clamp(1rem, 3vw, 2.5rem);
  padding-block: clamp(1.5rem, 1rem + 1.6vw, 2.5rem);
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
  transition: color var(--d-2) var(--ease-hover);
}
.p-home .svc-n{
  font-family: var(--f-body);
  font-size: var(--t-micro);
  font-weight: var(--w-micro);
  letter-spacing: var(--ls-micro);
  color: var(--accent-text);
  transition: color var(--d-2) var(--ease-hover);
}
.p-home .svc-body{ display: block; }
.p-home .svc-name{
  display: block;
  font-family: var(--f-display);
  font-variant-ligatures: none;
  font-size: var(--t-h2);
  font-weight: var(--w-h2);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
}
.p-home .svc-note{
  display: block;
  margin-top: var(--s-2);
  max-width: 56ch;
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--text-3);
  transition: color var(--d-2) var(--ease-hover);
}
.p-home .svc-go{
  font-size: 1.1rem;
  color: var(--text-3);
  transition: transform var(--d-2) var(--ease-hover), color var(--d-2) var(--ease-hover);
}
.p-home .svc-row:hover .svc-go{ transform: translateX(6px); color: var(--accent-text); }

/* The ink band sweeps up behind the hovered row (kit's ="invert"); these flip the text
   ON TOP of it. The band is --c-water-deep, so bone/gold here are the deep-surface
   roles — same values the deep surface would hand out, applied to a row that has no
   data-surface of its own. */
.p-home [data-peeklist="invert"] .svc-row:hover{ color: var(--c-bone); }
.p-home [data-peeklist="invert"] .svc-row:hover .svc-n{ color: var(--c-gold-lift); }
.p-home [data-peeklist="invert"] .svc-row:hover .svc-note{ color: var(--c-bone-2); }
.p-home [data-peeklist="invert"] .svc-row:hover .svc-go{ color: var(--c-gold-lift); }
/* Keyboard parity: hover-only affordances strand keyboard users. */
.p-home .svc-row:focus-visible .svc-go{ transform: translateX(6px); color: var(--accent-text); }

@media (max-width: 760px){
  .p-home .svc-head{ grid-template-columns: 1fr; align-items: start; }
  .p-home .svc-row{ grid-template-columns: auto minmax(0, 1fr); }
  .p-home .svc-go{ display: none; }
}

/* ══════════════════════════════════════════════════════ 60 · AWARDS
   Framed certificates. Restraint: hairline mounts, no medals, no ribbon dressing.
   (No trophies exist — MANIFEST §5. This is an awards strip wearing the trophy keys.) */
.p-home .awards-head{
  max-width: 62ch;
  margin-bottom: var(--space-block);
}
.p-home .awards-title{ margin-top: var(--s-5); font-size: var(--t-h1); }
.p-home .awards-lead{ margin-top: var(--s-5); }
/* Not four equal cells. The head says "first and second, same class" — so the two
   Crossback Golden certificates that ARE that sentence lead the composition, and the
   other two sit smaller and lower as supporting evidence. Four identical frames would
   give a 3rd-place Gold Head the same weight as the win and flatten the whole claim. */
.p-home .awards-strip{
  display: grid;
  grid-template-columns: 1.34fr 1.34fr 1fr 1fr;
  gap: clamp(1rem, 2.5vw, 2.25rem);
  align-items: start;
}
/* A certificate is paper: it gets a paper mount and a real (soft) drop, so it reads as
   an artefact on a wall rather than a JPEG in a grid cell. */
.p-home .awards-item .frame--cert{
  background: var(--c-paper);
  padding: clamp(.5rem, 1.2vw, 1rem);
  box-shadow: var(--sh-1);
  transition: transform var(--d-2) var(--ease-hover), box-shadow var(--d-2) var(--ease-hover);
}
.p-home .awards-item .frame--cert > img{
  position: static;
  width: 100%; height: auto;
  object-fit: contain;   /* never crop a document */
}
.p-home .awards-item:hover .frame--cert{
  transform: translateY(-4px);
  box-shadow: var(--sh-2);
}
/* The supporting pair: dropped off the shared baseline and mounted thinner. The offset is
   what stops the row reading as a grid — it makes the eye land on the win first. */
.p-home .awards-item--minor{ margin-top: clamp(2rem, 6vw, 5.25rem); }

/* Two labelled rows since 2026-07-19 (the FULL cabinet): each event gets a micro
   rule and its own strip. The 2014 row runs five across — Grand Champion and
   Champion lead at the wider tracks, the three runner-ups drop to the minor
   baseline, same hierarchy grammar as the 2013 row. */
.p-home .awards-group{ margin-top: clamp(2.5rem, 5vw, 4.5rem); }
.p-home .awards-group:first-of-type{ margin-top: 0; }
.p-home .awards-group-label{ margin-bottom: clamp(1.25rem, 2.5vw, 2rem); }
.p-home .awards-strip--five{ grid-template-columns: 1.3fr 1.3fr 1fr 1fr 1fr; }
.p-home .awards-item--minor .frame--cert{ padding: clamp(.375rem, .9vw, .75rem); }
.p-home .awards-item .frame-cap{ font-size: var(--t-micro); letter-spacing: .04em; }

@media (max-width: 760px){
  /* Two-up: the offset would strand a certificate mid-column, so the pairing carries
     the hierarchy on its own (lead row first, supporting row under it). */
  .p-home .awards-strip,
  .p-home .awards-strip--five{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .p-home .awards-item--minor{ margin-top: 0; }
}

/* ══════════════════════════════════════════════════════════ 70 · CTA ⭐
   The enquiry close, rebuilt as a DEEP CINEMA ISLAND (judge R7 bounce: the ink slab
   measured 27% fill / 73% bare black, taste.md:128 "no bare viewport allowed"). A
   full-bleed golden-black arowana glide fills the frame; copy sits in a LEFT well over
   a deep-blue veil that carries the AA contract. data-surface="deep" re-points every role
   (bone type, gold seal), CONTRACT §2 guarantees AA on #0E2C3F. */
.p-home .cta{
  position: relative;
  overflow: hidden;
  min-height: 88svh;
  display: flex;
  align-items: center;
  padding-block: var(--space-section);
}
/* The film fills the whole island (padding included) so the section reads full, not empty. */
.p-home .cta-media{
  position: absolute; inset: 0;
  z-index: var(--z-media);
  overflow: hidden;
}
.p-home .cta-vid{
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 68% 50%;   /* keep the fish in the open right, clear of the copy well */
}
/* Deep-blue scrim — NOT decoration, it is the contrast contract. Weighted heavy on the left
   (where the copy sits) and thinning toward the right (where the fish swims), plus a soft
   floor so the note holds. Composited left ≈ .93 deep over a dark clip → bone type rides at
   the deep surface's verified AA; right ≈ .34 → the arowana reads. Re-measured computed
   luma/contrast on the rendered copy (qa/cta-contrast), do not thin the left stops. */
.p-home .cta-veil{
  position: absolute; inset: 0;
  background:
    linear-gradient(100deg,
      rgba(14, 44, 63, .95) 0%,
      rgba(14, 44, 63, .90) 34%,
      rgba(14, 44, 63, .58) 64%,
      rgba(14, 44, 63, .34) 100%),
    linear-gradient(180deg,
      rgba(14, 44, 63, .30) 0%,
      rgba(14, 44, 63, 0) 34%,
      rgba(14, 44, 63, 0) 66%,
      rgba(14, 44, 63, .42) 100%);
}
.p-home .cta-wrap{ position: relative; z-index: var(--z-content); }
.p-home .cta-inner{ max-width: 42rem; }   /* left-aligned in the centred wrap = a left well */
.p-home .cta{ text-align: left; }
.p-home .cta-title{ margin-top: var(--s-5); font-size: var(--t-display); }
.p-home .cta-lead{ margin: var(--s-5) 0 0; max-width: 48ch; }
.p-home .cta-btns{ justify-content: flex-start; margin-top: var(--space-block); }
.p-home .cta-note{
  margin-top: var(--s-6);
  font-size: var(--t-small);
}

@media (max-width: 760px){
  /* On a phone the fish can't share the row with the copy, so the veil goes full and the
     media weights the floor — copy stacks over a solid deep top, fish glints at the base. */
  .p-home .cta{ min-height: 82svh; }
  .p-home .cta-vid{ object-position: 60% 40%; }
  .p-home .cta-veil{
    background:
      linear-gradient(180deg,
        rgba(14, 44, 63, .93) 0%,
        rgba(14, 44, 63, .88) 46%,
        rgba(14, 44, 63, .62) 74%,
        rgba(14, 44, 63, .40) 100%);
  }
}

/* ------------------------------------------------- 62. google reviews (.rev)
   Ruled editorial columns, not a card grid: quotes flow in CSS columns with a
   hairline top rule each, so uneven lengths make the rhythm. The badge is the
   trust strip: G mark + 5.0 + stars + count + the outbound "see all" link. */
.p-home .rev-head{
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-6);
  margin-bottom: var(--space-block);
}
.p-home .rev-head .micro{ margin-bottom: var(--s-5); }
/* line-height ≥1.2, load-bearing: the mask reveal wraps each line in an
   overflow:hidden box sized by line-height, and at the inherited 1.06 the
   serif's descenders measured 45px against a 38px box — permanently clipped
   (probed 2026-07-19), not an animation artifact. */
.p-home .rev-title{
  font-family: var(--f-display);
  font-size: var(--t-h1);
  font-weight: var(--w-h2);
  line-height: 1.22;
  letter-spacing: var(--ls-h);
}
.p-home .rev-title em{ color: var(--accent-text); }

.p-home .rev-badge{
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--s-3);
  padding: var(--s-5) var(--s-6);
  border: 1px solid var(--rule-2);
  background: var(--surface);
}
.p-home .rev-badge-row{
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
}
.p-home .rev-g{ flex: none; }
.p-home .rev-score{
  font-size: var(--t-h3);
  font-weight: 600;
  line-height: 1;
  color: var(--text);
}
.p-home .rev-stars{
  color: var(--c-gold);
  font-size: .95rem;
  letter-spacing: .12em;
  line-height: 1;
  white-space: nowrap;
}
.p-home .rev-count{ letter-spacing: .14em; }
.p-home .rev-all{ margin-top: var(--s-2); }

.p-home .rev-cols{
  columns: 3;
  column-gap: var(--space-block);
}
@media (max-width: 1100px){ .p-home .rev-cols{ columns: 2; } }
@media (max-width: 700px){ .p-home .rev-cols{ columns: 1; } }

.p-home .rev-card{
  break-inside: avoid;
  margin: 0 0 var(--s-7);
  padding-top: var(--s-5);
  border-top: 1px solid var(--rule);
}
.p-home .rev-card .rev-stars{ font-size: .8rem; }
.p-home .rev-q{
  margin: var(--s-4) 0 0;
}
.p-home .rev-q p{
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--text-2);
  /* verbatim text stays in the DOM; the clamp only bounds the visual block */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 9;
  overflow: hidden;
}
.p-home .rev-phs{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-3);
  margin-top: var(--s-4);
}
.p-home .rev-phs .rev-ph:only-child{ grid-column: 1 / -1; }
.p-home .rev-by{
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-3);
  margin-top: var(--s-4);
}
.p-home .rev-name{
  font-weight: 600;
  font-size: var(--t-small);
  letter-spacing: .02em;
}
.p-home .rev-when{ font-size: var(--t-small); }
.p-home .rev-note{ margin-top: var(--s-5); max-width: 60ch; }

@media (max-width: 700px){
  .p-home .rev-badge{ width: 100%; }
}


/* ===== css/pages/media.css ===== */
/* ============================================================================
   media.css — the press page. OWNER: imperial-media. Namespace: .p-media
   (CONTRACT.md §3 — every rule scoped).
   Structure over decoration: one flagship film, then a mixed-ratio index where
   the single 9:16 short intentionally breaks the landscape row.
   ========================================================================== */

/* Reveal-cascade workaround, same as farm.css/fishes.css (delete when the kit's
   fix lands): html.js [data-reveal]{opacity:0} is (0,2,1) and beats .is-in (0,2,0),
   so without these (0,3,0) re-assertions the whole page stays blank under normal
   motion while the gate stays green (RM uses !important and renders fine). */
.p-media [data-reveal].is-in{ opacity: 1; transform: none; }
.p-media [data-reveal].mk-mask{ opacity: 1; transform: none; }

/* ══ 10 · FEATURED ══ */
.p-media .md-open-head{
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--gutter);
  align-items: end;
  margin-bottom: var(--s-7);
}
.p-media .md-open-lede{ max-width: 44ch; }
.p-media .md-cap{
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2) var(--s-3);
}
@media (max-width: 900px){
  .p-media .md-open-head{ grid-template-columns: 1fr; align-items: start; }
}

/* ══ 20 · FILM INDEX ══ */
.p-media .md-films-head{ margin-bottom: var(--s-7); }
.p-media .md-grid{
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)) minmax(0, .62fr);
  gap: var(--s-7) var(--gutter);
  align-items: start;
}
/* the short holds the narrow third column for both rows — the vertical frame is
   the row-breaker, not a bug */
.p-media .md-item--short{ grid-column: 3; grid-row: 1 / span 2; }
@media (max-width: 900px){
  .p-media .md-grid{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .p-media .md-item--short{ grid-column: auto; grid-row: auto; }
}
@media (max-width: 640px){
  .p-media .md-grid{ grid-template-columns: 1fr; }
  .p-media .md-item--short{ max-width: 320px; }
}

/* ══ 30 · FOLLOW ══ */
.p-media .md-follow-row{
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--s-5) var(--gutter);
  padding-top: var(--s-7);
  border-top: 1px solid var(--rule);
}
.p-media .md-follow-body .lede{ max-width: 40ch; }
.p-media .md-follow-stats{ margin-top: var(--s-3); }


/* ---------------------------------------------------- 25. client album (.md-album)
   Labelled groups of the client's own photography (drop 2026-07-19). Masonry via
   CSS columns — mixed aspects make the rhythm; no uniform card grid. */
.p-media .md-album-head{ margin-bottom: var(--space-block); }
.p-media .md-album-head h2{ margin-top: var(--s-5); font-size: var(--t-h1); font-weight: var(--w-h2); line-height: 1.22; letter-spacing: var(--ls-h); font-family: var(--f-display); }
.p-media .md-album-lede{ margin-top: var(--s-5); max-width: 56ch; }
.p-media .md-album-group{ margin-top: clamp(2.5rem, 5vw, 4.25rem); }
.p-media .md-album-group:first-of-type{ margin-top: 0; }
.p-media .md-album-label{ margin-bottom: clamp(1.1rem, 2.2vw, 1.75rem); }
.p-media .md-album-cols{ columns: 3; column-gap: clamp(1rem, 2.2vw, 2rem); }
.p-media .md-album-item{ break-inside: avoid; margin: 0 0 clamp(1rem, 2.2vw, 2rem); }
@media (max-width: 1100px){ .p-media .md-album-cols{ columns: 2; } }
@media (max-width: 640px){ .p-media .md-album-cols{ columns: 1; } }


/* ===== css/pages/services.css ===== */
/* ============================================================================
   services.css — the five services. OWNER: imperial-fishes (two pages, one mind).
   Every selector scoped under .p-services; every @keyframes named services-*.

   THE SIGNATURE: a numbered editorial index of alternating asymmetric spreads —
   explicitly not cards (BRIEF §2 "NEVER uniform cards"; the uniform card grid is
   the aquariusavenue anti-reference). The rhythm: media side alternates, and 02
   breaks the pattern entirely by carrying no photograph at all.

   02 is the honest slot, not a lazy one. No tank-build or pond-build photography
   exists in the client's material — verified by assets across all 40 stills and
   33 clips — and a finished-tank shot next to "we build tanks" implies a build we
   cannot evidence (BRIEF §6). Setting it in type turns the constraint into the
   index's rhythm break. If build photography ever lands, this modifier is the
   only thing to remove.
   ========================================================================== */

/* --- KIT BUG WORKAROUND — same defect and same patch as fishes.css; see the block there
   and HANDOFFS 19:34. motion-kit.css:33 (0,2,1) out-specifies its own :39 reveal (0,2,0),
   so every [data-reveal] stays opacity 0 for real users. Delete once the kit is fixed. --- */
.p-services [data-reveal].is-in{ opacity: 1; transform: none; }
.p-services .mk-mask{ opacity: 1; }

/* --- A11Y WORKAROUND, base.css `.link` (scaffold's file; source fix is qa's call —
   HANDOFFS 19:5x). At rest `.link` is INDISTINGUISHABLE from body text: measured live,
   colour rgb(18,20,23) === the surrounding prose, text-decoration none, and the ::after
   hairline sits at scaleX(0). The only affordance is a hover wipe — which does not exist
   for touch, and leaves the link invisible-as-a-link for everyone until they happen to
   point at it (WCAG 1.4.1: not even colour distinguishes it, let alone a non-colour cue).
   Here the rest state carries a gold hairline and hover deepens it to ink, so the wipe's
   intent survives as a colour transition. Delete once base.css owns a rest-state cue. --- */
.p-services .link::after{ transform: scaleX(1); background: var(--rule-accent);
  transition: background var(--d-2) var(--ease-hover); }
.p-services .link:hover::after{ background: currentColor; }

/* ------------------------------------------------------------------ 1. hero */
.p-services .sv-hero{ padding-bottom: var(--space-block); }

.p-services .sv-hero-title{
  font-size: var(--t-display);
  font-weight: var(--w-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
}

/* ----------------------------------------------------------------- 2. index */
.p-services .sv-list{
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--rule);
}

.p-services .sv-item{
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--s-4) var(--s-5);
  padding-block: var(--space-block);
  border-bottom: 1px solid var(--rule);
  align-items: start;
}

/* The spread. Numeral keeps its own column so the titles align down the whole
   index; media takes the second half and swaps side on alternate rows. */
@media (min-width: 900px){
  .p-services .sv-item{
    grid-template-columns: auto minmax(0, 1fr) minmax(0, .95fr);
    gap: clamp(1.5rem, 3vw, 3rem);
    padding-block: var(--space-section);
    align-items: center;
  }
  /* Alternate the MEDIA-BEARING rows, not the raw child index. 02 carries no
     photograph, so :nth-child(even) would swap only 04 and leave 01/03/05 all
     media-right — three rows the same, one odd one out, which reads as an
     accident rather than a rhythm. Swapping 03 and 05 gives the media rows a
     true R–L–R–L beat across the index. Indices are hardcoded because the five
     services are a fixed, named set (BRIEF §2), not a loop. */
  .p-services .sv-item:nth-child(3) .sv-body,
  .p-services .sv-item:nth-child(5) .sv-body{ order: 2; }
  .p-services .sv-item:nth-child(3) .sv-media,
  .p-services .sv-item:nth-child(5) .sv-media{ order: 1; }
  .p-services .sv-item:nth-child(3) .sv-n,
  .p-services .sv-item:nth-child(5) .sv-n{ order: 0; }
}

.p-services .sv-n{
  font-family: var(--f-display);
  font-variant-ligatures: none;
  font-size: var(--t-h2);
  font-weight: var(--w-display);
  line-height: 1;
  color: var(--accent-text);
  padding-top: .18em;
}
@media (min-width: 900px){
  .p-services .sv-n{ font-size: var(--t-h1); align-self: start; }
}

.p-services .sv-title{
  font-size: var(--t-h2);
  font-weight: var(--w-h2);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
  margin-bottom: var(--s-4);
}

.p-services .sv-promise{
  max-width: 54ch;
  font-size: var(--t-lede);
  line-height: var(--lh-lede);
  color: var(--text);
}

.p-services .sv-detail{
  margin-top: var(--s-4);
  max-width: 60ch;
  font-size: var(--t-small);
  line-height: var(--lh-body);
  color: var(--text-3);
}

.p-services .sv-more{ margin-top: var(--s-5); }

/* the media column spans the numeral's gutter on mobile so it isn't indented
   into a thin strip at 390 */
.p-services .sv-media{ grid-column: 1 / -1; }
@media (min-width: 900px){
  .p-services .sv-media{ grid-column: auto; }
}

/* --- 02: the typographic break ------------------------------------------- */
.p-services .sv-item--type{ background: var(--surface-2); }
@media (min-width: 900px){
  /* No media column — the eye should register the missing picture as intent, not
     as a hole. Left as one long measure it read as dead space at 1440, so the
     copy itself becomes the two-column composition: the promise holds the left
     at display scale, the process detail and the aside stack down the right. */
  .p-services .sv-item--type{ grid-template-columns: auto minmax(0, 1fr); }
  .p-services .sv-item--type .sv-body{
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(2rem, 4vw, 4rem);
    align-items: start;
  }
  .p-services .sv-item--type .sv-title{ grid-column: 1 / -1; }
  .p-services .sv-item--type .sv-promise{
    grid-column: 1; grid-row: 2;
    font-size: var(--t-h3);
    font-weight: var(--w-h3);
    max-width: 34ch;
  }
  .p-services .sv-item--type .sv-detail{ grid-column: 2; grid-row: 2; margin-top: 0; }
  .p-services .sv-item--type .sv-aside{ grid-column: 2; grid-row: 3; }
}
.p-services .sv-item--type{
  padding-inline: var(--s-5);
  border-left: 2px solid var(--accent);
}
@media (min-width: 900px){
  .p-services .sv-item--type{ padding-inline: clamp(1.5rem, 3vw, 3rem); }
}

.p-services .sv-aside{
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule-accent);
  max-width: 52ch;
  font-size: var(--t-small);
  line-height: var(--lh-body);
  color: var(--text-2);
}

/* ------------------------------------------------------------- 3. secondary */
.p-services .sv-secondary-head{ margin-bottom: var(--space-block); }

.p-services .sv-secondary-title{
  font-size: var(--t-h2);
  font-weight: var(--w-h2);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
  max-width: 24ch;
}

/* Secondary by construction: a hairline-divided row, no frames, no fills —
   these must read quieter than the five above them (emphasis budget). */
.p-services .sv-mentions{
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
}
@media (min-width: 760px){
  .p-services .sv-mentions{
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(1.5rem, 3vw, 3rem);
  }
}

.p-services .sv-mention{
  padding-block: var(--s-5);
  border-top: 1px solid var(--rule);
}
@media (min-width: 760px){
  .p-services .sv-mention{ padding-bottom: 0; }
}

.p-services .sv-mention-name{
  font-size: var(--t-h3);
  margin-bottom: var(--s-3);
}
.p-services .sv-mention-note{
  max-width: 40ch;
  font-size: var(--t-small);
  line-height: var(--lh-body);
  color: var(--text-3);
}

/* ------------------------------------------------------------------- 4. CTA */
.p-services .sv-cta-inner{ text-align: center; align-items: center; }
.p-services .sv-cta-title{
  font-size: var(--t-h1);
  font-weight: var(--w-h1);
  line-height: var(--lh-h);
  letter-spacing: var(--ls-h);
}
.p-services .sv-cta-inner .lede{ max-width: 54ch; color: var(--text-2); }
.p-services .sv-cta-note{ font-size: var(--t-small); }


/* ---------------------------------------------------- 25. client album (.sv-tanks)
   Curated portrait slideshow of the client's tank builds (drop 2026-07-19;
   slideshow per Charlie 2026-07-20 — the four-column masonry read untidy).
   Desktop: the head is a sticky left rail beside the 3/4 stage; mobile stacks.
   Component: base.css §18. */
.p-services .sv-tanks-head h2{ margin-top: var(--s-5); font-size: var(--t-h1); font-weight: var(--w-h2); line-height: 1.22; letter-spacing: var(--ls-h); font-family: var(--f-display); }
.p-services .sv-tanks-lede{ margin-top: var(--s-5); max-width: 56ch; }
.p-services .sv-tanks-grid{
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 460px);
  gap: clamp(2.5rem, 3rem + 3vw, 6rem);
  align-items: start;
}
.p-services .sv-tanks-head{ position: sticky; top: calc(var(--nav-h) + 2.5rem); }
@media (max-width: 900px){
  .p-services .sv-tanks-grid{ grid-template-columns: minmax(0, 1fr); }
  .p-services .sv-tanks-head{ position: static; margin-bottom: var(--space-block); }
  .p-services .sv-tanks-slides{ max-width: 480px; }
}
