@import url('https://fonts.googleapis.com/css2?family=Caprasimo:wght@400&family=Figtree:wght@400;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600&family=Source+Serif+4:opsz,wght@8..60,400;8..60,600&display=swap');
/* Organic — design-system tokens and component classes. This file is the source of truth for the system's look; retune it here and see readme.md. */


:root {
  --color-bg: #f5ead8;
  --color-surface: #ebddc5;
  --color-text: #201e1d;
  --color-accent: #c67139;
  --color-accent-2: #7a8a5e;
  --color-divider: color-mix(in srgb, #201e1d 16%, transparent);

  /* Tonal ramps — generated in OKLCH on one shared lightness scale, so the
     same step of any role matches the others in visual value. */
  --color-neutral-100: #f9f4ed;
  --color-neutral-200: #eee7db;
  --color-neutral-300: #dcd3c4;
  --color-neutral-400: #c0b6a5;
  --color-neutral-500: #a19786;
  --color-neutral-600: #82796a;
  --color-neutral-700: #645c50;
  --color-neutral-800: #474238;
  --color-neutral-900: #2e2b25;

  --color-accent-100: #fff2eb;
  --color-accent-200: #ffe1d0;
  --color-accent-300: #ffc6a5;
  --color-accent-400: #f6a06b;
  --color-accent-500: #d67f48;
  --color-accent-600: #b2622d;
  --color-accent-700: #8c491a;
  --color-accent-800: #643312;
  --color-accent-900: #402310;

  --color-accent-2-100: #f0fae1;
  --color-accent-2-200: #e1eecc;
  --color-accent-2-300: #ccdbb2;
  --color-accent-2-400: #aebf92;
  --color-accent-2-500: #8fa073;
  --color-accent-2-600: #728157;
  --color-accent-2-700: #56633f;
  --color-accent-2-800: #3d472b;
  --color-accent-2-900: #272e1b;

  --font-heading: "Caprasimo", system-ui, sans-serif;
  --font-heading-weight: 400;
  --font-body: "Figtree", system-ui, sans-serif;

  --space-1: 4.4px;
  --space-2: 8.8px;
  --space-3: 13.2px;
  --space-4: 17.6px;
  --space-6: 26.4px;
  --space-8: 35.2px;

  --radius-sm: 8px;
  --radius-md: 16px;
  --radius-lg: 28px;

  /* Elevation — derived from the ground: soft ink-tinted shadows on a
     light theme, a hairline edge + ambient darkness on a dark one. */
  --shadow-sm: 0 1px 2px color-mix(in srgb, #2e2b25 14%, transparent);
  --shadow-md: 0 3px 10px color-mix(in srgb, #2e2b25 16%, transparent);
  --shadow-lg: 0 12px 32px color-mix(in srgb, #2e2b25 22%, transparent);
}

body {
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
}
h1, h2, h3, h4 { font-family: var(--font-heading); font-weight: var(--font-heading-weight); }

.washed{filter:saturate(0.6) contrast(0.85) brightness(1.1) opacity(0.94)}

/* ══════════════════════════════════════════════════════════════════════════
   Components — built with the tokens above. Plain CSS
   on plain HTML: no JavaScript, no build step. Each class is documented in
   readme.md and demonstrated in foundations/ and components/.
   ══════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-size: 15px; line-height: 1.55; font-weight: 400; }
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  line-height: 1.12; letter-spacing: -0.015em; margin: 0 0 var(--space-2);
}
h1 { font-size: 42px; }
h2 { font-size: 32px; }
h3 { font-size: 25px; }
h4 { font-size: 20px; }
h5 { font-size: 16px; }
h6 { font-size: 13px; }
h6 { letter-spacing: 0.08em; text-transform: uppercase; }
p { margin: 0 0 var(--space-3); }
a { color: var(--color-accent); text-underline-offset: 3px; }
img { display: block; max-width: 100%; }
figure { margin: 0; }
figcaption {
  font-size: 11px; margin-top: var(--space-1);
  color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.text-muted { color: color-mix(in srgb, var(--color-text) 55%, transparent); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

/* — rules — */
.hr {
  height: 1px; border: 0; margin: var(--space-4) 0;
  background: var(--color-divider);
}

/* — buttons — */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  cursor: pointer; text-decoration: none;
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 14px; line-height: 1.2; color: var(--color-text); /* matches the .input's 14px —
     the pair sits side by side in sign-up rows */
  background: transparent; border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: var(--radius-md);
}
.btn svg { display: block; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary { background: var(--color-accent); color: var(--color-bg); }
.btn-primary:hover { background: var(--color-accent-600); }
.btn-primary:active { background: var(--color-accent-700); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-1); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
.btn-icon { width: 36px; height: 36px; padding: 0; }
.btn-block { width: 100%; margin-top: var(--space-2); }

/* — forms — */
.field > label {
  display: block; font-size: 12px; margin-bottom: 5px;
  color: color-mix(in srgb, var(--color-text) 70%, transparent);
}
.input {
  width: 100%; min-height: 36px; padding: 6px 10px; font: inherit;
  font-size: 14px; color: var(--color-text); caret-color: var(--color-accent);
  background: var(--color-surface);
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
textarea.input { min-height: 90px; resize: vertical; }
.radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; }
.radio input, .seg-opt input {
  position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
}
.radio .dot {
  width: 16px; height: 16px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--color-divider);
}
.radio:hover .dot { border-color: var(--color-accent); }
.radio input:checked + .dot {
  border-color: var(--color-accent); background: var(--color-accent);
  box-shadow: inset 0 0 0 4px var(--color-bg);
}
.radio input:focus-visible + .dot { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.seg {
  display: inline-flex; overflow: hidden;
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.seg-opt {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; font-size: 13px; cursor: pointer;
}
.seg-opt + .seg-opt { border-left: 1px solid var(--color-divider); }
.seg-opt:has(input:checked) { background: var(--color-accent); color: var(--color-bg); }
.seg-opt:not(:has(input:checked)):hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.seg-opt:has(input:focus-visible) { outline: 2px solid var(--color-accent); outline-offset: -2px; }

/* — cards — */
.card {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface);
}
.card-kicker { font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent); }
.card-title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 17px; line-height: 1.2;
}
.card-body { margin: 0; font-size: 13px; opacity: 0.8; flex: 1; }
.card-meta {
  display: flex; align-items: center; gap: 6px; font-size: 11px;
  color: color-mix(in srgb, var(--color-text) 50%, transparent);
}
.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }

/* — tags — */
.tag {
  display: inline-flex; align-items: center; font-size: 11px;
  letter-spacing: 0.02em; padding: 3px 10px;
  border-radius: calc(var(--radius-md) * 0.75);
}
.tag-accent { background: var(--color-accent-100); color: var(--color-accent-800); }
.tag-accent-2 { background: var(--color-accent-2-100); color: var(--color-accent-2-800); }
.tag-neutral { background: var(--color-neutral-100); color: var(--color-neutral-800); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }

/* — navigation — */
.nav {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: none;
}
.nav-brand {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 18px; margin-right: auto;
}
.nav a { color: inherit; text-decoration: none; font-size: 14px; }
.nav a:hover, .nav a[aria-current='page'] { color: var(--color-accent); }

/* — tables — */
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
  text-align: left; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  padding: var(--space-2); border-bottom: 1px solid var(--color-divider);
}
.table td {
  padding: var(--space-2);
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}
.table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }

/* — dialog — */
.dialog-backdrop {
  position: fixed; inset: 0; display: grid; place-items: center;
  padding: var(--space-4);
  background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
}
.dialog {
  width: min(440px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-4); border-radius: var(--radius-lg);
  background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.dialog-title {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 20px;
}
.dialog-body { font-size: 14px; opacity: 0.85; }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }

/* — rounded frame: everything softens, small controls go pill — */
.card, .dialog { border-radius: calc(var(--radius-lg) * 1.15); }
.btn, .tag, .seg, .input { border-radius: 999px; }
.input { padding-inline: 14px; }


/* i2 Systems — kit/grid.css
   THE SPATIAL SYSTEM. Venture-agnostic: every venture front uses this file unchanged.
   Contains no colour, no font, no fact. See records/ui-kit-container-standard.md.

   Three separate concepts, never mixed:
     --page-margin  viewport edge -> container   (belongs to the page)
     --gutter       column -> column             (belongs to the grid)
     padding        component border -> content  (belongs to the component)
*/

:root {
  /* sm — phone, <= 599 */
  --page-margin: 20px;
  --gutter: 16px;
  --cols: 4;
  --container-max: none;
  --section-gap: 64px;

  /* vertical ladder — every vertical value comes from a rung. Nothing between. */
  --s-1: 4px;   --s-2: 8px;   --s-3: 12px;  --s-4: 16px;  --s-5: 24px;
  --s-6: 32px;  --s-7: 40px;  --s-8: 48px;  --s-9: 64px;  --s-10: 80px; --s-11: 96px;

  /* the measure: running text never exceeds this, whatever the grid says */
  --measure: 68ch;
}

@media (min-width: 600px) {           /* md — tablet */
  :root { --page-margin: 32px; --gutter: 20px; --cols: 8; --section-gap: 80px; }
}
@media (min-width: 1024px) {          /* lg — desktop, container starts binding */
  :root { --page-margin: 48px; --gutter: 24px; --cols: 12; --container-max: 1200px; --section-gap: 96px; }
}
/* xl >= 1440: nothing changes. The container stops growing and centres itself.
   A layout that keeps stretching to a 27" monitor is unbounded, not responsive. */

/* ---- the container: one container, one left edge, every section ---- */
.k-container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--page-margin);
}

/* ---- the grid ---- */
.k-grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: var(--gutter);
}

/* Whole columns only. A card is 3 or 4 columns, never 3.4.
   Spans map DOWN predictably across breakpoints - one design reflowing,
   not three designs. */
.k-span-12 { grid-column: span 4; }
.k-span-8  { grid-column: span 4; }
.k-span-7  { grid-column: span 4; }
.k-span-6  { grid-column: span 4; }
.k-span-5  { grid-column: span 4; }
.k-span-4  { grid-column: span 4; }
.k-span-3  { grid-column: span 2; }

@media (min-width: 600px) {
  .k-span-12 { grid-column: span 8; }
  .k-span-8  { grid-column: span 8; }
  .k-span-7  { grid-column: span 8; }
  .k-span-6  { grid-column: span 4; }
  .k-span-5  { grid-column: span 4; }
  .k-span-4  { grid-column: span 4; }
  .k-span-3  { grid-column: span 4; }
}
@media (min-width: 1024px) {
  .k-span-12 { grid-column: span 12; }
  .k-span-8  { grid-column: span 8; }
  .k-span-7  { grid-column: span 7; }
  .k-span-6  { grid-column: span 6; }
  .k-span-5  { grid-column: span 5; }
  .k-span-4  { grid-column: span 4; }
  .k-span-3  { grid-column: span 3; }
}

/* ---- vertical rhythm: ONE section gap, applied everywhere ---- */
.k-section { padding-block: var(--section-gap); }
.k-section + .k-section { padding-block-start: var(--section-gap); }

/* ---- the measure. Overrides the grid for running text, on purpose. ---- */
.k-prose { max-width: var(--measure); }

/* ---- full-bleed is TOTAL, never partial ---- */
.k-bleed {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  max-width: none;
  padding-inline: var(--page-margin);
}

/* ---- optical alignment: pull a large glyph's visible edge onto the line ---- */
.k-optical { margin-inline-start: var(--optical-pull, -0.06em); }

/* ================= the proof overlay =================
   Add ?grid to the URL (a script sets data-grid="on" on <html>) and the columns,
   gutters and page margins draw over the live page. The export ships one
   screenshot per breakpoint with this on - a grid claim is verified the same way
   a colour claim is: by a picture, not an assertion. Debug only; never shipped on.
*/
[data-grid="on"] .k-container { position: relative; }
[data-grid="on"] .k-container::before {
  content: "";
  position: absolute; inset-block: 0;
  inset-inline: var(--page-margin);
  pointer-events: none; z-index: 9999;
  background: repeating-linear-gradient(
    to right,
    color-mix(in srgb, red 12%, transparent) 0,
    color-mix(in srgb, red 12%, transparent)
      calc((100% - (var(--cols) - 1) * var(--gutter)) / var(--cols)),
    transparent
      calc((100% - (var(--cols) - 1) * var(--gutter)) / var(--cols)),
    transparent
      calc((100% - (var(--cols) - 1) * var(--gutter)) / var(--cols) + var(--gutter))
  );
}
[data-grid="on"] body::after {
  content: "cols " counter(k) " · margin " var(--page-margin) " · gutter " var(--gutter);
  position: fixed; inset-block-end: 0; inset-inline-start: 0; z-index: 10000;
  font: 11px ui-monospace, monospace; padding: 4px 8px;
  background: #000; color: #fff;
}

/* i2 Systems - kit/grid-container.css
   ADDITIVE COMPANION to kit/grid.css. Adds NO new numbers: every value here is copied from grid.css.
   Load AFTER grid.css.

   WHY THIS EXISTS - finding 1 in kit/grid-findings.md.
   grid.css scopes the spatial system with @media (min-width: …), i.e. to the VIEWPORT. Every venture front we
   build is rendered inside a design-tool preview pane at an arbitrary viewport width, and the standard's own
   proof is "one screenshot per breakpoint" - produced by sizing the PAGE ROOT, not the viewport. With media
   queries a 390-wide root inside a 909-wide viewport resolves md tokens: 32px margin, 8 columns, 20px gutter.
   The grid would be measurably wrong in exactly the screenshots that are supposed to prove it right.

   So the page root declares container-type and the same table is re-expressed as @container. Identical numbers,
   correct scope. If the standard adopts container queries in grid.css itself, delete this file - it is a
   workaround for a scope mismatch, not a second grid. Do not add a value here that is not in grid.css. */

.k-root { container-type: inline-size; container-name: page; }

/* AN ELEMENT CANNOT QUERY ITS OWN CONTAINER. .k-root establishes the container, so a rule inside
   @container page {} can never target .k-root itself - it only matches DESCENDANTS. A first attempt set the
   tokens on .k-root and every breakpoint silently stayed at sm: measured 20/16/4 at a 1280 root.
   So the tokens land on the direct children and every deeper element inherits them. */
.k-root > * {
  --page-margin: 20px; --gutter: 16px; --cols: 4; --container-max: none; --section-gap: 64px;
}
@container page (min-width: 600px) {
  .k-root > * { --page-margin: 32px; --gutter: 20px; --cols: 8; --section-gap: 80px; }
}
@container page (min-width: 1024px) {
  .k-root > * { --page-margin: 48px; --gutter: 24px; --cols: 12; --container-max: 1200px; --section-gap: 96px; }
}

/* Span classes, same mapping as grid.css, re-scoped to the container. */
@container page (min-width: 600px) {
  .k-span-12, .k-span-8, .k-span-7 { grid-column: span 8; }
  .k-span-6, .k-span-5, .k-span-4, .k-span-3 { grid-column: span 4; }
}
@container page (min-width: 1024px) {
  .k-span-12 { grid-column: span 12; }
  .k-span-8  { grid-column: span 8; }
  .k-span-7  { grid-column: span 7; }
  .k-span-6  { grid-column: span 6; }
  .k-span-5  { grid-column: span 5; }
  .k-span-4  { grid-column: span 4; }
  .k-span-3  { grid-column: span 3; }
}

/* FINDING 4 - the section gap doubles. grid.css sets .k-section { padding-block: var(--section-gap) }, so two
   adjacent sections contribute 64 + 64 = 128px between their content, not the 64 the table specifies (the
   standard cites INVERSA's single 59px gap as the target). Half the padding per section edge so the GAP equals
   --section-gap. The `.k-section + .k-section` rule in grid.css restates a value the element already has and is
   a no-op; it is left alone rather than edited. */
.k-section { padding-block: calc(var(--section-gap) / 2); }

/* FINDING 2 - the overlay readout cannot render as written: var() is not substituted inside `content`, and
   counter(k) has no counter-reset anywhere so it resolves to 0. The label either drops or reads "cols 0".
   A per-breakpoint literal does not work either: body is an ANCESTOR of the page container, so @container
   cannot reach body::after and the label sticks at sm no matter how wide the root gets - the same
   own-container mistake as finding 1, one level up. attr() IS valid in content, so the page writes the
   resolved tokens onto body from a descendant probe and the label reads them back. */
[data-grid="on"] body::after { content: attr(data-grid-info); }

/* i2 Systems — kit/motion.css
   THE MOTION LAYER. Venture-agnostic, like grid.css. No colour, no font, no fact.
   See records/ui-kit-container-standard.md.

   Three non-negotiables, in order:

   1. THE PAGE SHOWS AT REST. Everything meant to be read is visible on load. A section
      may animate IN, but from a visible resting state - never parked at opacity:0
      waiting on an observer. The first still frame is what a thumbnail, a shared link
      and a skimming reader all get.
   2. REDUCED MOTION IS HONOURED EVERYWHERE, once, at the bottom of this file.
   3. MOTION CARRIES MEANING OR IT DOES NOT SHIP. Scattered effects read as
      AI-generated. One orchestrated moment beats six ambient ones.
*/

:root {
  /* durations - short enough to feel like response, not performance */
  --t-fast: 120ms;   /* hover, focus, press - must feel instant */
  --t-base: 240ms;   /* state changes, disclosure */
  --t-slow: 560ms;   /* entrances, the one hero moment */

  /* easing - standard is for most things; exit/enter differ on purpose */
  --e-standard: cubic-bezier(.2, 0, 0, 1);
  --e-enter:    cubic-bezier(.05, .7, .1, 1);   /* decelerate in */
  --e-exit:     cubic-bezier(.3, 0, .8, .15);   /* accelerate out */

  /* travel - small. Big travel is what makes motion look cheap. */
  --m-rise: 12px;
}

/* ============ 1 · REVEAL ============
   From a VISIBLE resting state. The element is fully opaque and in place by default;
   the animation only runs where the browser supports scroll-driven timelines, and
   only shifts it a little. If nothing runs, the page is already correct. */
.k-reveal { opacity: 1; transform: none; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .k-reveal {
      animation: k-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 40%;
    }
  }
}
/* FINDING against this file, fixed here - see kit/motion-findings.md.
   The from-state used to be opacity .35. With `animation: k-rise linear both` on a timeline that never
   advances - any non-Chromium browser, and any preview where an inner frame scrolls rather than the root -
   the backwards fill HOLDS that from-state, so every .k-reveal section renders permanently at 35% opacity.
   Measured on this page with JS disabled: 6 of 6 sections dim. That breaks non-negotiable 1, "the page shows
   at rest", and no JS can rescue it because the rule fires when JS is off.
   So the parked state is now fully opaque and only the 12px rise remains in the keyframe. The fade is supplied
   by kit/motion.js, which can guarantee it resolves. Where native timelines do run, the rise still reads. */
@keyframes k-rise {
  from { opacity: 1; transform: translateY(var(--m-rise)); }
  to   { opacity: 1; transform: none; }
}

/* stagger for a row of siblings - set --i on each child */
.k-stagger > * { animation-delay: calc(var(--i, 0) * 60ms); }

/* ============ 2 · THE HERO PARALLAX ============
   The founder's ask: the image holds while the copy moves, so the hero feels anchored.
   Pure CSS scroll-driven; no scroll listener, no jank, nothing to clean up.

   Markup:
     <div class="k-parallax"><img class="k-parallax-img" ...></div>
   The wrapper clips; the image is oversized and drifts SLOWLY against the scroll.
   Keep --m-parallax small: this is a 6-8% drift, not a funfair ride. */
.k-parallax { overflow: hidden; position: relative; }
.k-parallax-img {
  width: 100%;
  height: calc(100% + var(--m-parallax, 8%));
  object-fit: cover;
  will-change: transform;
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .k-parallax-img {
      animation: k-drift linear both;
      animation-timeline: scroll(root block);
      animation-range: entry 0% exit 100%;
    }
  }
}
@keyframes k-drift {
  from { transform: translateY(calc(var(--m-parallax, 8%) * -1)); }
  to   { transform: translateY(0); }
}

/* ============ 3 · MICRO-INTERACTIONS ============
   What makes a page feel built rather than rendered. All at --t-fast: a hover that
   takes 300ms feels broken, not smooth. */
.k-interactive {
  transition:
    background-color var(--t-fast) var(--e-standard),
    border-color     var(--t-fast) var(--e-standard),
    color            var(--t-fast) var(--e-standard),
    transform        var(--t-fast) var(--e-standard);
}
.k-interactive:active { transform: translateY(1px); }   /* press = physical */

/* Photo tiles: the image moves, the frame does not. */
.k-tile { overflow: hidden; }
.k-tile img { transition: transform var(--t-base) var(--e-standard); }
@media (hover: hover) { .k-tile:hover img { transform: scale(1.03); } }

/* Underline that draws from the left rather than appearing all at once. */
.k-link-underline {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size var(--t-base) var(--e-standard);
}
.k-link-underline:hover, .k-link-underline:focus-visible { background-size: 100% 1px; }

/* Focus is never animated away and never removed. */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

/* ============ 4 · SKELETONS ============
   A loading state is a design surface. Shape of the thing, not a spinner. */
.k-skeleton { position: relative; overflow: hidden; }
@media (prefers-reduced-motion: no-preference) {
  .k-skeleton::after {
    content: ""; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgb(255 255 255 / .12), transparent);
    animation: k-sweep 1.4s var(--e-standard) infinite;
  }
}
@keyframes k-sweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }

/* ============ 5 · THE GLOBAL OFF SWITCH ============
   Declared once, last, so it wins. Motion is a preference, not a decoration we impose. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .k-parallax-img { height: 100%; transform: none; }
}

/* i2 Systems — kit/behaviours.css
   THE BEHAVIOUR PACKAGE. The standard set of site interactions every venture gets,
   versioned once here instead of re-invented per build. Venture-agnostic: no facts,
   no colours of its own - it reads theme tokens.

   Pairs with kit/behaviours.js. Everything degrades to a working static page
   without the JS: no behaviour here hides content or blocks an action.
*/

/* ============ 1 · SKIP LINK ============
   First focusable thing on every page. Keyboard users should not tab through a
   nav on every single page load. */
.k-skip {
  position: absolute; left: var(--page-margin, 20px); top: 0;
  transform: translateY(-120%);
  z-index: 200; padding: 10px 16px; border-radius: 8px;
  background: var(--panel); color: var(--on-panel, #fff);
  text-decoration: none;
  transition: transform var(--t-fast, 120ms) var(--e-standard, ease);
}
.k-skip:focus-visible { transform: translateY(12px); }

/* ============ 2 · SCROLL-AWARE HEADER ============
   Flat at the top of the page; gains a hairline and a backdrop once scrolled.
   Hides on scroll-down and returns on scroll-up, which gives phone users their
   whole screen back while keeping the CTA one flick away.
   JS sets data-k-head="top | up | down" on <html>. */
.k-header {
  position: sticky; top: 0; z-index: 100;
  background: var(--paper);
  transition: transform var(--t-base, 240ms) var(--e-standard, ease),
              box-shadow var(--t-base, 240ms) var(--e-standard, ease),
              background-color var(--t-base, 240ms) var(--e-standard, ease);
}
[data-k-head="down"] .k-header { transform: translateY(-100%); }
[data-k-head="up"]   .k-header,
[data-k-head="down"] .k-header { box-shadow: 0 1px 0 var(--tint-line, rgb(0 0 0 / .10)); }

/* Frosted variant - use over imagery, never over plain text. */
.k-header--glass {
  background: color-mix(in srgb, var(--paper) 72%, transparent);
  -webkit-backdrop-filter: saturate(1.6) blur(12px);
  backdrop-filter: saturate(1.6) blur(12px);
}
@supports not (backdrop-filter: blur(1px)) { .k-header--glass { background: var(--paper); } }

/* ============ 3 · BACK TO TOP ============
   Hidden until it is useful. Sits clear of the sticky action bar on phones. */
.k-totop {
  position: fixed; right: var(--page-margin, 20px);
  bottom: calc(var(--page-margin, 20px) + var(--k-bar-h, 0px));
  z-index: 90;
  width: 48px; height: 48px;            /* 44 minimum, 48 comfortable */
  display: grid; place-items: center;
  border: 1px solid var(--tint-line, rgb(0 0 0 / .12));
  border-radius: 999px;
  background: var(--paper); color: var(--brand);
  cursor: pointer;
  opacity: 0; visibility: hidden; transform: translateY(8px);
  transition: opacity var(--t-base, 240ms) var(--e-standard, ease),
              transform var(--t-base, 240ms) var(--e-standard, ease),
              visibility var(--t-base, 240ms);
}
.k-totop.k-on { opacity: 1; visibility: visible; transform: none; }
.k-totop:hover { background: var(--tint); }

/* ============ 4 · STICKY ACTION BAR (phone) ============
   The primary action stays reachable without the visitor hunting for it.
   Generalised from the one-off Fant's StickyBar. */
.k-actionbar {
  position: sticky; bottom: 0; z-index: 95;
  display: flex; gap: var(--s-2, 8px);
  padding: var(--s-2, 8px) var(--page-margin, 16px) calc(var(--s-3, 12px) + env(safe-area-inset-bottom));
  background: var(--paper);
  border-top: 1px solid var(--tint-line, rgb(0 0 0 / .12));
}
.k-actionbar > :first-child { flex: 2; }
.k-actionbar > :last-child  { flex: 1; }
@media (min-width: 600px) { .k-actionbar { display: none; } }

/* ============ 5 · PINNED SECTION — the "Apple lock" scroll ============
   A tall section in which the media PINS to the viewport while the copy scrolls
   past it. Pure CSS position:sticky - no scroll-jacking, no hijacked wheel, the
   scrollbar keeps telling the truth. That is the difference between the Apple
   effect and the cheap imitation of it.

     <section class="k-pin">
       <div class="k-pin-media">…image or video…</div>
       <div class="k-pin-flow">…steps / copy blocks…</div>
     </section>

   JS adds --k-pin-progress (0 -> 1) on the section for anything that wants to
   drive an indicator off it. */
.k-pin { display: grid; gap: var(--gutter, 24px); align-items: start; }
.k-pin-media { position: sticky; top: var(--k-head-h, 72px); }
.k-pin-flow > * + * { margin-block-start: 60vh; }   /* the scroll distance per step */

@media (min-width: 1024px) {
  .k-pin { grid-template-columns: 7fr 5fr; }
  .k-pin-media { height: calc(100svh - var(--k-head-h, 72px) - var(--s-9, 64px)); }
  .k-pin-media > img, .k-pin-media > video { width: 100%; height: 100%; object-fit: cover; }
}
/* On phones the pin is a held image with the copy beneath - never a tall dead zone. */
@media (max-width: 1023px) {
  .k-pin-media { position: static; }
  .k-pin-flow > * + * { margin-block-start: var(--s-9, 64px); }
}

/* ============ 6 · SCROLL PROGRESS ============ */
.k-progress {
  position: fixed; inset-inline: 0; top: 0; z-index: 101;
  height: 2px; transform-origin: 0 50%;
  transform: scaleX(var(--k-progress, 0));
  background: var(--brand);
}

/* ============ 7 · ANCHOR OFFSET ============
   Stops an in-page anchor landing underneath the sticky header - the single most
   common broken thing on a one-page site. */
:target, [id] { scroll-margin-top: calc(var(--k-head-h, 72px) + var(--s-5, 24px)); }
html { scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .k-header { transition: none; }
  [data-k-head="down"] .k-header { transform: none; }   /* never hide it if motion is off */
}

/* Fant's Landscape tokens - retunes the Organic kit's variables (kit readme: "to change the look, edit the tokens").
   v3.1 (2026-09-15) - THE REAL ARTWORK LANDED. Every hex below is measured off it, not sampled off a screen photo.

   PROVENANCE, AND WHY THE OLD NUMBERS WERE WRONG. Until now every hex came from a photograph of a screen, so the
   blue read too dark and the two greens were swapped. The file Darren supplied is a 3072x4098 cut-out whose opaque
   area is only 0.24% soft edge - a vector-origin export, not a screen grab. Binned over 3.9M opaque pixels:
        #2E3191  37.5%  the blue field          (recorded as #0C1884 - out by 34/25/13 per channel)
        #025B2E  24.2%  the green FIELD
        #FFFFFF  18.6%  lettering, keyline, rules
        #006837  16.8%  the green BORDER and the lighter lower-right wedge
   The border is the BRIGHTER green and the field is the DARKER one. The previous tokens had that inverted, which is
   why the mark read flat: the ring that should advance was receding. --brand-2 is now the border green #006837,
   because it is the legible one (6.45:1 on paper against the field green's deeper reading).

   THE BRAND IS BLUE, AND THE GROUNDS DERIVE FROM IT. Founder's call, 2026-09-10: the navy is the half of the shield
   that carries the name, and every competing landscaper in Memphis is green - so navy is the differentiation.

   A TINT IS A TWO-BODY PROBLEM: THE BRAND *AND* THE PAPER. --paper is #F5F7FA, a COOL near-white, and that is not a
   taste preference - it is the whole fix for the muddy band. A warm paper's yellow cancels a cool brand. Measured:
        navy 8%  into warm cream #F6F4EE -> #E3E2E6  dead grey, faint violet cast
        navy 30% into warm cream #F6F4EE -> #B0B2CE  periwinkle
        navy 8%  into cool white #F5F7FA -> #E2E5F1  clean pale blue
        navy 30% into cool white #F5F7FA -> #AFB4D7  clean, still blue
   DO NOT "WARM UP" THE PAPER. Doing so silently re-greys every tint on the site and brings the lavender back. An
   earlier pass mis-read the grey band as evidence against a blue brand and moved the grounds to green; that was
   wrong, and the founder's call reversed it. Green is an ACCENT - icon strokes, a rule, the ok state, the grass
   band in the mark. It is never a section ground.

   HARD CONSTRAINT, RECOMPUTED AGAINST THE REAL HEXES: GREEN ON --panel IS 2.65:1 - still unusable. No green text,
   icon or border on the dark panel; on that surface it is paper type and the mark only, and the mark takes its paper
   keyline variant. Blue on green is 1.55:1 and blue on panel 1.71:1, so the primary still inverts on the panel.
   Every other pair the site uses was recomputed and passes: ink/paper 16.18, ink-2/paper 6.58, ink/tint 14.09,
   ink-2/tint 5.73, paper/blue 9.96, paper/panel 17.07, blue/paper 9.96, blue/tint 8.68, green/paper 6.45.
   Accent-on-tint at body size is now ALLOWED (8.68:1); the v2.2 note banning it was asserted, never computed.

   v3 also retires the slab-serif display for a
   grotesk (R1/R3) with the serif kept for captions. Radius lives on interactive elements ONLY (R3). Separation comes
   from surface inversion, never elevation (R4) - --shadow-sm is none and .fl-card carries a hairline instead.
   Every contrast pair recomputes: paper luminance moves .907 -> .964, so all ratios gain ~6% and nothing that passed can fail.
   No achromatic state exists. Grey is not in the palette. Flat custom properties only in :root - the export copies this block as tokens.css. */

:root, [data-theme="light"] {
  /* COOL near-white. Not #FBFBFA (neutral) and never #F6F4EE (warm) - see the two-body note above. */
  --color-bg:#F5F7FA; --paper:#F5F7FA; --color-text:#1C1A17; --ink-2:#5C5853;
  /* Blue - the brand, measured off the artwork. The source of every ground. */
  --brand:#2E3191; --brand-ink:#2E3191; --on-brand:#F5F7FA;
  /* Grounds re-derived at the SAME mix ratios v3 established, so the rhythm is unchanged and only the hue moves. */
  --tint:#E5E7F2; --tint-line:#B9BCDB;
  /* Blue at 14% - one step past --tint, so an honest-empty frame and the hiring band stay visible on both. */
  --tint-deep:#D9DBEB;
  /* Green - accent only: icon strokes, a rule, the ok state. --tint-2 is its rare ground, never a section ground.
     --brand-2 is the mark's BORDER green; --brand-2-deep is its FIELD green, for the rare case that needs weight. */
  --brand-2:#006837; --brand-2-ink:#006837; --brand-2-deep:#025B2E; --on-brand-2:#F5F7FA; --tint-2:#E4EDEC; --tint-2-line:#B0CFC3;
  --panel:#101131; --on-panel:#F5F7FA; --on-panel-2:#DCDEEC;
  --color-surface:var(--tint); --color-accent:var(--brand); --color-accent-2:var(--brand-2); --on-accent:var(--on-brand); --accent-ink:var(--brand-ink);
  --color-divider:var(--tint-line);
  --color-neutral-100:#F7F6F2;--color-neutral-200:var(--tint);--color-neutral-300:var(--tint-line);--color-neutral-400:#9AA2CC;--color-neutral-500:#7E7970;--color-neutral-600:#6E6A62;--color-neutral-700:#5C5853;--color-neutral-800:#44413C;--color-neutral-900:#2A2824;
  --color-accent-100:var(--tint);--color-accent-200:var(--tint);--color-accent-300:var(--tint-line);--color-accent-400:var(--brand);--color-accent-500:var(--brand);--color-accent-600:var(--brand-ink);--color-accent-700:var(--brand-ink);--color-accent-800:var(--brand-ink);--color-accent-900:var(--brand-ink);
  --color-accent-2-100:var(--tint-2);--color-accent-2-200:var(--tint-2);--color-accent-2-300:var(--tint-2-line);--color-accent-2-400:var(--brand-2);--color-accent-2-500:var(--brand-2);--color-accent-2-600:var(--brand-2-ink);--color-accent-2-700:var(--brand-2-ink);--color-accent-2-800:var(--brand-2-ink);--color-accent-2-900:var(--brand-2-ink);
  --font-heading:"Archivo",system-ui,sans-serif; --font-heading-weight:500; --font-body:"Archivo",system-ui,sans-serif;
  --font-caption:"Source Serif 4",Georgia,serif; --display-track:-0.02em;
  --space-1:4px; --space-2:8px; --space-3:16px; --space-4:24px; --space-5:32px; --space-6:48px; --space-7:64px; --space-8:96px;
  /* SPATIAL TOKENS ARE NOT DEFINED HERE. --page-margin, --gutter, --cols, --container-max and --section-gap
     are owned by kit/grid.css (via kit/grid-container.css for container scope). A theme that declares --gutter
     silently overrides the shared grid by link order - that was finding 4. Do not re-add them. */
  /* Fallback aspect for a slot with NO photograph measured - landscape, because an honest-empty frame carries no
     image to crop and a portrait placeholder eats half a phone screen. Real photographs override this inline. */
  --tile-ratio:4/3;
  /* Radius on interactive elements only (R3). Images, photo blocks, cards and panels are sharp. */
  /* Radius on interactive elements only (R3): pill buttons, 4px inputs, everything else sharp. The ONE square
     exception is the gallery reel's arrows - see .fl-reel-btn. */
  --radius-sm:0; --radius-md:0; --radius-lg:0; --radius-pill:999px; --radius-input:4px;
  /* Hierarchy from surface inversion, not elevation (R4). --shadow-sm is none by design; md/lg exist for the one lifted thing. */
  --shadow-sm:none; --shadow-md:0 8px 24px rgba(16,17,49,.12); --shadow-lg:0 24px 60px rgba(16,17,49,.18);
  /* THE MARK'S SHAPE, AS A REUSABLE TOKEN. Traced off the artwork at 97.4% IoU, so the silhouette is trustworthy
     even though a hand redraw of the interior is not. Used for the crest point and honest-empty frames - never to
     restate the mark, which appears once in the header, once in the footer and as the favicon. */
  --crest-point: polygon(0 0, 100% 0, 100% 78%, 50% 100%, 0 78%);
  --motion-fast:180ms; --motion-slow:640ms; --ease:cubic-bezier(.2,.7,.2,1);
}
/* Kit overrides the theme needs (kit extension recorded in CHANGELOG) */
[data-theme] a:not(.btn) { color: var(--brand-ink); text-decoration: none; text-underline-offset: 3px; }
[data-theme] a:not(.btn):hover { text-decoration: underline; }
[data-theme] .btn { border-radius: var(--radius-pill); min-height: 48px; padding-inline: 24px; font-size: 16px; font-weight: 500; font-family: var(--font-body); text-decoration: none; transition: background var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease), border-color var(--motion-fast) var(--ease), transform var(--motion-fast) var(--ease), filter var(--motion-fast) var(--ease); }
[data-theme] .btn:active { transform: translateY(1px); }
[data-theme] .btn-brand { background: var(--brand); color: var(--on-brand); border: 1px solid var(--brand); }
[data-theme] .btn-brand:hover { filter: brightness(.92); }
[data-theme] .btn-line { background: transparent; color: var(--brand-ink); border: 1px solid var(--brand); }
[data-theme] .btn-line:hover { background: var(--tint); }
/* THE PRIMARY INVERTS ON DARK GROUNDS. --panel IS the brand blue taken to depth, so a .btn-brand fill on it
   measures 1.33:1 against the ground - a forbidden pairing. On --panel the primary is .btn-paper (17.61:1 fill
   vs ground) and the secondary drops to .btn-ghost-paper so it cannot out-rank it. Never .btn-brand on --panel.
   The hero used to flip its ground at a container boundary, which produced first a cascade-order bug and then a
   stranded-paint bug; the crisp hero has one ground, so both are gone. Full history in theme.notes.md. */
[data-theme] .btn-paper { background: var(--on-panel); color: var(--panel); border: 1px solid var(--on-panel); }
[data-theme] .btn-paper:hover { background: #ffffff; border-color: #ffffff; }
[data-theme] .btn-line-paper { background: transparent; color: var(--on-panel); border: 1px solid var(--on-panel); }
[data-theme] .btn-line-paper:hover { background: rgba(255,255,255,.12); }
/* Ghost on dark: no border, so it cannot compete with the paper-filled primary. */
[data-theme] .btn-ghost-paper { background: transparent; color: var(--on-panel); border: 1px solid transparent; }
[data-theme] .btn-ghost-paper:hover { background: rgba(255,255,255,.10); }
[data-theme] .input { border-radius: var(--radius-input); min-height: 48px; font-size: 16px; background: var(--paper); color: var(--color-text); border: 1px solid var(--tint-line); }
[data-theme] .input:focus { border-color: var(--brand); }
[data-theme] textarea.input { min-height: 96px; }
[data-theme] :focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.fl-card { background: var(--paper); border: 1px solid var(--tint-line); border-radius: 0; box-shadow: none; }
.fl-label { font-family: var(--font-caption); font-size: 14px; line-height: 1.45; color: var(--ink-2); }
/* R1's caption: serif, 16px, directly beneath the image at a 10px gap. */
.fl-caption { font-family: var(--font-caption); font-size: 16px; line-height: 1.5; color: var(--ink-2); }
/* R1's open image block: no radius, no border, no shadow - the photograph at real size, chrome-free. */
.fl-block { display: flex; flex-direction: column; gap: 10px; }
.fl-block img { display: block; width: 100%; height: auto; border-radius: 0; }
/* Motion - reveal on scroll; off for reduced motion */
[data-reveal] { opacity: 0; transform: translateY(12px); transition: opacity var(--motion-slow) var(--ease), transform var(--motion-slow) var(--ease); }
[data-reveal][data-in] { opacity: 1; transform: none; }
.fl-zoom { position: absolute; inset: 0; opacity: 0; transition: transform var(--motion-slow) var(--ease), opacity var(--motion-slow) var(--ease); }
.fl-zoom[data-in] { opacity: 1; }
.fl-tile:hover .fl-zoom { transform: scale(1.03); }
.fl-skel { position: absolute; inset: 0; background: linear-gradient(100deg, var(--tint-deep) 30%, var(--tint) 50%, var(--tint-deep) 70%); background-size: 240% 100%; animation: fl-shimmer 1.4s linear infinite; }
@keyframes fl-shimmer { from { background-position: 120% 0 } to { background-position: -120% 0 } }
@media (prefers-reduced-motion: reduce) { [data-reveal] { opacity: 1; transform: none; transition: none; } .fl-zoom { transition: none; } .fl-zoom[data-in] { opacity: 1; } .fl-skel { animation: none; } }
/* Responsive layout - the one place classes are used: container queries can't be inline.
   BREAKPOINTS MATCH kit/grid.css: 600 (md) and 1024 (lg).
   .fl-wrap IS .k-container, expressed in the grid's tokens. Page margin comes from the grid and is never
   overridden per section - that is the one-left-edge rule. */
.fl { container-type: inline-size; }
.fl-wrap { width: 100%; max-width: var(--container-max); margin-inline: auto; padding-inline: var(--page-margin); box-sizing: border-box; }
/* Half the section gap per edge, so the GAP between two sections equals --section-gap (finding 3).
   ONE STATED EXCEPTION: the hero. Its photo band is full-bleed and edge-to-edge, so the boundary below it gets
   only the next section's half-gap - measured 40px, not 80px. That is deliberate, not a miss: a full-bleed
   photograph's own edge is the delineation (R1 separates sections by a shift in ground, not by space), and
   adding the hero's half-gap would insert a strip of bare navy under the photograph. Every .fl-sec-to-.fl-sec
   boundary on the page does measure the full --section-gap. */
.fl-sec { padding-block: calc(var(--section-gap) / 2); }
.fl-tint { background: var(--tint); }
.fl-g2, .fl-g3, .fl-g4, .fl-split { display: grid; gap: var(--gutter); grid-template-columns: 1fr; }
/* THE MEASURE - running text caps at --measure (68ch, about 7 of 12 columns at 1200), never the container.
   No prose element carries an inline max-width; 520/560/600px at eight sites used to beat this rule. */
.fl-lead, .fl-prose { max-width: var(--measure); }
.fl-tiles { display: block; columns: 2; column-gap: var(--gutter); }
.fl-tiles > figure { break-inside: avoid; margin: 0 0 var(--gutter) !important; }
/* R1's gallery: open image blocks. COLUMN FLOW, NOT A GRID - every tile carries its photograph's own measured
   aspect, and grid rows take the tallest item's height, which left ragged dead space. Do not convert back. */
.fl-gallery { display: block; columns: 1; column-gap: var(--gutter); }
.fl-gallery > figure { margin: 0 0 40px !important; break-inside: avoid; display: flex; flex-direction: column; gap: 10px; }
/* THE TILE SET - one aspect ratio, whole columns, rows that fill. Deliberately not the gallery's column flow:
   on a small labelled service set that produced a ragged 1+2 / 1+2 with a dead lower-left gap. */
.fl-tileset { display: grid; gap: var(--gutter); grid-template-columns: 1fr; }
.fl-tileset > figure { margin: 0; display: flex; flex-direction: column; gap: 10px; }
/* Square, not 4:3: every supplied photograph is portrait 0.75, where a 4:3 tile discards 44% of the frame and a
   square discards 25%. One ratio across the set still, so the rows fill. */
.fl-tileset .fl-tile { aspect-ratio: 1/1 !important; }
/* The page's one dark full-bleed section (R3/R4) - the spine. Surface inversion carries the break. */
.fl-panel { background: var(--panel); color: var(--on-panel); }
.fl-panel .fl-h2, .fl-panel .fl-display { color: var(--on-panel); }
.fl-panel .fl-label, .fl-panel .fl-caption { color: var(--on-panel-2); }
.fl-panel .fl-tile { background: rgba(255,255,255,.06); }
.fl-tile { position: relative; aspect-ratio: var(--tile-ratio); overflow: hidden; border-radius: 0; background: var(--tint); }
.fl-display { font-family: var(--font-heading); font-weight: 500; font-size: 34px; line-height: 1.05; letter-spacing: var(--display-track); }
.fl-h2 { font-family: var(--font-heading); font-weight: 500; font-size: 28px; line-height: 1.1; letter-spacing: var(--display-track); }
.fl-h3 { font-family: var(--font-heading); font-weight: 600; font-size: 18px; line-height: 1.3; letter-spacing: -0.01em; color: var(--color-text); }
.fl-lead { font-size: 16px; line-height: 1.6; }
.fl-desk { display: none !important; }
.fl-nav-links { display: none; }
/* The header primary is hidden below 700 (the sticky bar carries it) AND while the hero is still on screen, so the
   first screenful never shows two identical primaries. It is rendered only past 600px of scroll - see showTop. */
.fl-cta-tab { display: none !important; }
.fl-book { display: grid; gap: 16px; grid-template-columns: 1fr; }
@container (min-width: 600px) {
  .fl-g2, .fl-g4 { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .fl-g3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
  .fl-tiles { columns: 3; }
  .fl-gallery { columns: 3; }
  .fl-tileset { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .fl-split { grid-template-columns: 5fr 7fr; align-items: start; }
  .fl-book { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .fl-step-foot { flex-direction: row; flex-wrap: wrap; align-items: center; }
  .fl-step-foot .btn { width: auto; }
  .fl-step-foot .fl-step-next { margin-left: auto; }
  .fl-display { font-size: 44px; }
  .fl-h2 { font-size: 32px; }
  .fl-h3 { font-size: 20px; }
  .fl-lead { font-size: 18px; }
  .fl-cta-tab { display: inline-flex !important; }
}
@container (min-width: 900px) {
}
@container (min-width: 1024px) {
  .fl-g4 { grid-template-columns: repeat(4, minmax(0,1fr)); }
  .fl-tileset { grid-template-columns: repeat(3, minmax(0,1fr)); }
  .fl-display { font-size: 52px; }
  .fl-h2 { font-size: 40px; }
  .fl-desk { display: flex !important; }
  .fl-mob { display: none !important; }
  .fl-nav-links { display: flex; }
}

/* THE HERO - crisp direction, replacing the old .fl-hero. Always the navy panel, full-bleed, with the photograph
   as a band beneath rather than a scrimmed backdrop. That is the design change, and it also deletes two
   fragilities that only existed because the old hero flipped its ground at a container boundary:
     · the three-way same-specificity .btn-brand stack that silently took the wrong branch at 390, and
     · the variable indirection that replaced it, whose paint stranded on resize because .btn transitions
       background and no interpolation is scheduled for an inherited custom property.
   A hero with one ground needs neither. The primary here is .btn-paper, unconditionally. */
.fl-hero-c { background: var(--panel); color: var(--on-panel); }
/* cqw, NOT vw. vw reads the VIEWPORT, and every breakpoint on this project is produced by sizing the page root
   inside a preview pane - the exact scope error filed as blocking finding 1 in kit/grid-findings.md. With vw the
   hero's type scale, rhythm and band height measured identically at root 320, 390, 560 and 1280, so the mobile
   artboard overstated the design by 41% on the headline. .fl / .k-root already establish the container. */
.fl-hero-c .fl-hero-c-body { display: flex; flex-direction: column; gap: clamp(16px, 2cqw, 24px); padding-block: clamp(22px, 2.8cqw, 40px) clamp(16px, 2cqw, 24px); }
/* Sign layout: offer on the left, mark on the right. The mark is sized off the container so it stays large
   without ever crowding the headline, and it is the reverse file because green on the navy is 2.70:1. */
/* Headline and mark share one rank at every width - the mark sits in the space the headline's short line
   leaves, so the panel does not grow taller to accommodate it. */
.fl-hero-c-top { display: flex; flex-direction: row; justify-content: space-between; align-items: flex-start; gap: 16px; }
.fl-hero-c-mark { display: block; flex: none; max-width: none; width: auto; height: clamp(74px, 15cqw, 168px); align-self: flex-start; }
.fl-hero-c-foot { display: flex; flex-direction: column; gap: 18px; align-items: stretch; }
.fl-hero-c-foot > div { flex-direction: column; align-items: stretch; }
.fl-hero-c-foot .btn { justify-content: center; width: 100%; }
@container page (min-width: 560px) {
  .fl-hero-c-foot > div { flex-direction: row; align-items: center; }
  .fl-hero-c-foot .btn { width: auto; }
}
@container page (min-width: 720px) {
  .fl-hero-c-top { gap: var(--gutter); }
  .fl-hero-c-foot { flex-direction: row; justify-content: space-between; align-items: center; gap: var(--gutter); }
}
.fl-hero-c-h1 { font-family: var(--font-heading); font-weight: 500; letter-spacing: -0.03em; line-height: .94; margin: 0; font-size: clamp(34px, 5.4cqw, 66px); color: var(--on-panel); max-width: 15ch; }
.fl-hero-c-rule { border: 0; border-top: 1px solid rgba(245,247,250,.22); margin: 0; }
.fl-hero-c-grid { display: grid; grid-template-columns: 1fr; gap: 20px var(--gutter); }
/* A wrapping flex row let the long Towns value collide with the next kicker. A grid gives every item its own
   cell and a row gap that a wrapped value cannot eat. */
/* TRACKS SIZED TO CONTENT, AND THE LONG VALUE GETS ITS OWN ROW.
   The three values are wildly unequal: "Free, on site" and "Darren Fant" are ~12 characters, the towns list is
   ~45. Equal 1fr tracks forced the 45 into a 124px column and wrapped it four deep beside two single-line
   neighbours. minmax cannot fix that - 15ch gave two columns with an empty cell, 13ch gave three columns with a
   four-line wrap; both are the same error. So: max-content tracks, and the towns item is ordered last and spans
   the full row. The four towns are a confirmed fact; the layout gives, not the copy. */
/* minmax(0, max-content), not bare max-content: bare max-content cannot shrink, so at a 390 root the two tracks
   plus the gap measured 360px inside a 350px content box - a 10px overflow. With a 0 floor the tracks give and
   the towns value takes a second line at the narrowest widths instead of pushing the page sideways. */
.fl-hero-c-spec { display: flex; flex-wrap: wrap; gap: 14px clamp(24px, 4cqw, 44px); align-items: flex-start; }
.fl-hero-c-band { height: clamp(340px, 58cqw, 720px); overflow: hidden; border-top: 1px solid rgba(245,247,250,.22); position: relative; }
@container page (min-width: 600px) { .fl-hero-c-grid { grid-template-columns: 6fr 6fr; } }

/* Stagger index without a template hole: motion.css reads --i, and nth-child supplies it. Twelve is the
   largest row on the page (the jobs gallery); beyond that the delay simply caps at the last value. */
.k-stagger > *:nth-child(1){--i:0}.k-stagger > *:nth-child(2){--i:1}.k-stagger > *:nth-child(3){--i:2}
.k-stagger > *:nth-child(4){--i:3}.k-stagger > *:nth-child(5){--i:4}.k-stagger > *:nth-child(6){--i:5}
.k-stagger > *:nth-child(7){--i:6}.k-stagger > *:nth-child(8){--i:7}.k-stagger > *:nth-child(9){--i:8}
.k-stagger > *:nth-child(n+10){--i:9}
/* The hero band clips and the image is oversized, so the drift never exposes an edge. */
.fl-hero-c-band .k-parallax-img { width: 100%; height: calc(100% + var(--m-parallax, 8%)); object-fit: cover; }

/* THE HERO REEL. Controls sit on the photograph, so they are paper-filled rather than outlined: a hairline
   button over an unknown image has no reliable contrast, a solid fill always does. 44px hit targets. */
.fl-reel-ui { position: absolute; inset: auto 0 0 0; display: flex; flex-wrap: wrap; gap: 12px 16px; align-items: center; justify-content: space-between;
  padding: clamp(14px, 2cqw, 22px) var(--page-margin);
  background: linear-gradient(180deg, rgba(5,10,58,0) 0%, rgba(5,10,58,.62) 100%); }
.fl-reel-cap { font-family: var(--font-caption); font-size: 15px; line-height: 1.4; color: var(--on-panel); text-shadow: 0 1px 10px rgba(5,10,58,.7); }
.fl-reel-nav { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.fl-reel-pos { font-family: var(--font-body); font-weight: 600; font-size: 13px; letter-spacing: .06em; color: var(--on-panel); min-width: 5ch; text-align: center; text-shadow: 0 1px 10px rgba(5,10,58,.7); }
/* Square on purpose, and only here: the reel arrows sit on the photograph and read as part of the image
   frame rather than as page furniture. Every other button on the site stays a pill. */
.fl-reel-btn { width: 44px; height: 44px; border-radius: 0; border: 1px solid var(--on-panel); background: var(--on-panel); color: var(--panel); display: grid; place-items: center; cursor: pointer; padding: 0; }
.fl-reel-btn:hover { background: #ffffff; border-color: #ffffff; }

/* THE UTILITY RAIL. Hours, phone, email and the service area above the nav: the facts a homeowner checks
   BEFORE they call, which is exactly where a lead is won or lost. Tint ground so it reads as furniture rather
   than navigation, and it never competes with the mark beneath it. */
.fl-rail { background: var(--tint); border-bottom: 1px solid var(--tint-line); font-size: 14px; }
/* One line, never two: the rail holds hours, phone and email and nothing else. A fourth item wrapped it into a
   ragged second row, and the service area is already stated twice on the page. */
.fl-rail-item { display: inline-flex; align-items: center; gap: 8px; color: var(--ink-2); white-space: nowrap; }
.fl-rail-link { min-height: 32px; color: var(--brand-ink); font-weight: 600; }
.fl-rail-link:hover { color: var(--brand); text-decoration: underline; text-underline-offset: 3px; }
.fl-rail-wide { display: none; }
@container page (min-width: 760px) { .fl-rail-wide { display: inline-flex; } }

/* FOOTER. Four stacked blocks on a phone, four columns at desk. The headings are labels, not headings, so the
   footer never enters the page's heading outline. */
.fl-foot-h { font-family: var(--font-body); font-weight: 600; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--on-panel-2); }
[data-theme] footer a:not(.btn), [data-theme] .fl-foot-link { color: var(--on-panel); }
.fl-foot-col { display: flex; flex-direction: column; align-items: flex-start; gap: 9px; }
.fl-foot-link { display: inline-flex; align-items: center; padding-block: 3px; }
.fl-foot-tel { font-size: 20px; font-weight: 600; letter-spacing: -0.01em; }
.fl-foot-pages { display: flex; flex-direction: column; gap: 9px; }
/* The plaque is gone: logo-reverse.svg is DRAWN with a paper border, so it separates on the navy by
   construction. A light block behind the mark was a workaround for a raster that had no reverse version. */
[data-theme] footer a:not(.btn):hover { color: var(--on-panel); text-decoration: underline; text-underline-offset: 3px; }
.fl-foot-social { width: 40px; height: 40px; border-radius: var(--radius-pill); border: 1px solid var(--on-panel); color: var(--on-panel); display: grid; place-items: center; }
.fl-foot-social:hover { background: var(--on-panel); color: var(--panel); }
[data-footer-grid] { grid-template-columns: 1fr; }
@container page (min-width: 600px) { [data-footer-grid] { grid-template-columns: 1fr 1fr; } }
@container page (min-width: 1024px) { [data-footer-grid] { grid-template-columns: 1.5fr 1fr 1fr 1.2fr; } }

/* THE CHIP. A time window is a SELECTION, not an action. It used to render .btn-brand when picked, which put a
   second navy pill in the booking card and left the submit button competing with it. Checked state is carried
   by border weight, ink and a tint - never by the primary's fill, which belongs to the one button that
   advances the form. */
/* 44px, not 48: the submit stays the taller element so a selection never out-ranks the action. */
.fl-chip { min-height: 44px; padding: 0 20px; border-radius: var(--radius-pill); border: 1px solid var(--brand); background: transparent; color: var(--brand-ink); font-family: var(--font-body); font-weight: 500; font-size: 16px; display: inline-flex; align-items: center; gap: 9px; cursor: pointer; }
.fl-chip:hover { background: var(--tint); }
[data-theme] .fl-chip-on, [data-theme] .fl-chip[aria-pressed="true"] {
  border-width: 2px; padding: 0 19px; background: var(--tint-2); color: var(--brand-ink); font-weight: 600; }
[data-theme] .fl-chip-on:hover { background: var(--tint-2); }
.fl-chip-on::before { content: ""; width: 16px; height: 16px; flex: none; border-radius: var(--radius-pill); background: var(--brand);
  clip-path: polygon(14% 47%, 40% 72%, 86% 20%, 96% 32%, 41% 92%, 4% 57%); }

/* THE STAMP. The mark is the shape of a seal, so it is applied where a promise is made: the expectations block
   and the booked state. Paper ground with a hairline frame so it reads as APPLIED to the page rather than
   printed in it - and on a light ground the master file needs nothing behind it. Never used as ornament, never
   repeated inside one section. */
.fl-stamp-row { display: flex; flex-direction: column; gap: 24px; align-items: flex-start; }
.fl-stamp { display: inline-flex; flex-direction: column; align-items: center; gap: 8px; padding: 16px 22px 13px; background: var(--paper); border: 1px solid var(--tint-line); flex: none; align-self: flex-start; }
.fl-stamp-line { font-family: var(--font-body); font-weight: 600; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2); white-space: nowrap; }
.fl-stamp-sent { align-self: flex-start; }
/* The value-statement stamp: mark beside the commitment it is sealing, so the seal says what is promised
   rather than only asserting that something is. */
.fl-stamp-wide { flex-direction: row; align-items: center; gap: 18px; padding: 18px 22px; max-width: 40ch; }
.fl-stamp-text { display: flex; flex-direction: column; gap: 8px; }
.fl-stamp-claim { font-family: var(--font-caption); font-size: 16px; line-height: 1.5; color: var(--color-text); }
/* On the work panel the seal asserts authorship of the photographs. Reverse file on a transparent ground:
   a paper block would punch a hole in the dark section. */
.fl-stamp-dark { flex-direction: row; align-items: center; gap: 14px; padding: 0; background: transparent; border: 0; max-width: 100%; }
/* The seal line is nowrap for short labels; on the dark seal the caption is a sentence, so it must wrap or it
   forces horizontal scroll at 390. */
.fl-stamp-dark .fl-stamp-line { white-space: normal; min-width: 0; line-height: 1.45; }
@container page (min-width: 760px) { .fl-stamp-row { flex-direction: row; justify-content: space-between; align-items: flex-end; gap: var(--gutter); } }

/* BACK TO TOP. Bottom right, above the mobile action bar so it never covers the primary CTA. A real button with
   a label, a 48px target, the theme's focus ring, and a hover that lifts rather than recolours - the navy fill
   is the page's primary and this control must not read as one. */
.fl-totop { position: fixed; right: 20px; bottom: 20px; z-index: 40; width: 48px; height: 48px; border-radius: var(--radius-pill);
  border: 1px solid var(--brand); background: var(--paper); color: var(--brand-ink); display: grid; place-items: center; cursor: pointer;
  box-shadow: 0 6px 20px rgba(5,10,58,.16); transition: transform var(--motion-fast) var(--ease), background var(--motion-fast) var(--ease), color var(--motion-fast) var(--ease); }
.fl-totop:hover { background: var(--brand); color: var(--on-brand); transform: translateY(-3px); }
.fl-totop:active { transform: translateY(-1px); }
.fl-totop:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .fl-totop { transition: none; } .fl-totop:hover { transform: none; } }
@container page (max-width: 1023px) { .fl-totop { bottom: 84px; } }

/* The dolly image is pinned to the band's bottom edge and scaled from a bottom origin, so the ground line stays
   put while the photograph pulls back into full view. No CSS transition: the transform is written every scroll
   frame and a transition would lag behind the finger. */
.fl-dolly-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  object-position: center 84%; will-change: object-position; }
@media (prefers-reduced-motion: reduce) { .fl-dolly-img { object-position: center 66% !important; } }

/* The form's advance button is the one solid navy object in the card: 48px against the chips' 44px, semibold,
   and wider than its label. Fill alone is not enough separation when a selected control sits directly above it. */
[data-theme] form .btn-brand { min-height: 48px; font-weight: 600; padding-inline: 26px; }

/* THE STEPPER IS A PROGRESS METER, NOT A CONTROL. It used to paint the current step as a --brand filled block,
   which broke the same rule .fl-chip is written to enforce: the primary's fill belongs to the one button that
   advances the form, and a second navy object in the card competes with it. Squat filled blocks also read as
   disabled fields, which is the defect the old trust card was rebuilt to remove. Progress is now a counter, a
   step title and a rule that fills left to right, none of which can be mistaken for something to press. */
.fl-step-head { display: flex; flex-direction: column; gap: 10px; }
.fl-step-meta { display: flex; align-items: baseline; justify-content: space-between; gap: 8px 16px; flex-wrap: wrap; }
.fl-step-count { font-family: var(--font-caption); font-size: 14px; line-height: 1.45; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.fl-step-title { font-family: var(--font-heading); font-weight: 500; font-size: 22px; line-height: 1.15; letter-spacing: -0.01em; color: var(--color-text); }
.fl-step-rail { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 4px; }
.fl-step-seg { height: 3px; background: var(--tint-line); }
.fl-step-seg-on { background: var(--brand); }
/* Optional is marked, required is not. One mark on one field beats an asterisk on every other. */
.fl-opt { font-family: var(--font-caption); font-size: 13px; line-height: 1.45; color: var(--ink-2); }
/* The form's own actions sit on a hairline so the card ends on structure rather than on floating buttons. */
/* Below 600 the footer stacks and both buttons go full width, matching .fl-hero-c-foot. A wrapping row put the
   submit on its own line pushed flush right by margin-left:auto while Back stayed flush left, which reads as a
   wrap accident. column-reverse keeps Back earlier in the DOM for tab order while the primary sits on top. */
.fl-step-foot { display: flex; flex-direction: column-reverse; gap: 12px; padding-top: 20px; border-top: 1px solid var(--tint-line); }
.fl-step-foot .btn { width: 100%; justify-content: center; }
.fl-field { display: flex; flex-direction: column; gap: 8px; }
.fl-field > label, .fl-field > legend { font-family: var(--font-caption); font-size: 14px; line-height: 1.45; font-weight: 600; color: var(--color-text); padding: 0; }

/* overrides.css — appended AFTER the design export's fants-theme.css.
   Everything here is a defect in the export that cannot be fixed by data, and
   each one is filed. The export itself is never edited: the next cut would
   overwrite the edit and the bug would come back silently. */

/* [hidden] MUST WIN OVER AN INLINE display.
   The export's mobile menu renders as
     <nav data-menu hidden style="...;display:flex;...">
   and an inline `display:flex` outranks the user-agent's `[hidden]{display:none}`.
   So the menu sheet was open on EVERY page at EVERY width — the full mobile nav
   printed under the desktop header. This is the single most visible defect in
   the v2.2 bundle and it is invisible in the component source, because there
   the `hidden` attribute reads as sufficient.
   Filed: export-gap-fants-v22-hidden-loses-to-inline-display. */
[hidden] { display: none !important; }

/* PORTED 2026-09-11 — the named page container the v1.2 theme queries.
   theme/fants-theme.css:205 uses `@container page (min-width: 600px)` and every hero clamp is in
   `cqw`. Without an ancestor declaring `container-name: page` those queries never match and every
   clamp resolves at its minimum, so the hero renders one-column at 1280 with the smallest type.
   v1.2 declares this on `.k-root` in its own kit/grid-container.css. That file is NOT adopted: it
   re-expresses grid.css's whole token table container-scoped, and loading both is exactly the
   two-sources-for-one-token collision check 17 exists to catch. Only the container declaration is
   taken; the tokens stay owned by kit/grid.css alone. */
.k-root { container-type: inline-size; container-name: page; }

/* BUTTON LABELS WERE STRANDED AT THE TOP OF EVERY BUTTON.
 *
 * Measured on the deployed page, not inferred: a .btn box is 197x50 and the
 * label's ink box sits 4px from the top and 28px from the bottom. Horizontal
 * centring was already fine (25px each side).
 *
 * MECHANISM. `.btn` is an <a>, so its default display is `inline`. Every
 * button on this site sits in a flex container (.fl-nav-links, the hero foot,
 * the step foot), and CSS BLOCKIFIES flex items — so the anchor computes
 * `display: block`. That makes `min-height: 48px` apply, which it does not for
 * a non-replaced inline, so the box grows to 50px while the single 25.6px line
 * box stays at the top. Nothing is centring it vertically.
 *
 * THIS IS THE DESIGN'S BUG, NOT THE PORT'S, and the design's own intent is
 * unambiguous:
 *   - `[data-theme] .btn` sets min-height and padding-inline but NEVER display
 *   - the theme writes `justify-content: center` for .btn in TWO places
 *     (.fl-hero-c-foot .btn, .fl-step-foot .btn) — a property that does nothing
 *     unless the element is a flex or grid container
 *   - the one button that renders correctly, .fl-mob's, declares
 *     `display: grid; align-items: center`
 * So the theme assumes .btn is a flex container and forgets to say so.
 *
 * WHY IT IS FIXED HERE AND NOT IN THE EXPORT. design/export/ is landed
 * untouched from the canvas and must stay byte-identical to it; the direction is
 * one-way and nothing writes back to Claude Design. This override implements the
 * design's STATED intent rather than inventing a look, so the site can ship
 * tonight.
 *
 * THIS IS A DECLARED TEMPORARY DEVIATION. The real fix is three declarations in
 * the canvas's own fants-theme.css. When the next export carries them, DELETE
 * this block — an override that outlives its cause becomes a second source of
 * truth, which is the drift this pipeline exists to prevent.
 *
 * inline-flex, not flex: the buttons must keep sizing to their own content
 * inside the flex rows, and the theme already sets width explicitly where it
 * wants a full-width button (.fl-step-foot .btn { width: 100% }). */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* GRID BLOWOUT IN THE BOOKING SECTION, below 600 only.
 *
 * .fl-split is grid-template-columns:1fr at base, and `1fr` means
 * `minmax(auto, 1fr)` — `auto` will not shrink below the item's min-content. The
 * booking copy column and the .fl-card form have min-width:auto, so the column
 * was pinned open at 405px inside a 350px content box and every descendant
 * spilled 35px to the right. min-width:0 lets a single-column layout do the one
 * thing it has to do at 390.
 *
 * At 600+ the design deliberately switches to `5fr 7fr` and there is room, so
 * this touches the narrow case only.
 *
 * The OTHER half of the 390 overflow — 25 elements, including both hero CTAs —
 * was a missing `box-sizing: border-box` reset, and it is NOT fixed here any
 * more. It was never ours to fix: the reset lives in the Organic design
 * system's own styles.css, which the canvas loads and this build was not
 * shipping at all. build.py now composes design/ds/*/styles.css first, exactly
 * as the artboard does, so the reset arrives from the design system rather than
 * from a local patch. A hand-written copy of something the design already says
 * is a second source of truth, which is the drift this pipeline exists to stop.
 *
 * This block belongs in the canvas's theme too; delete it when an export
 * carries it. */
@container page (max-width: 599px) {
  .fl-split > *, .fl-g2 > *, .fl-g3 > *, .fl-g4 > * { min-width: 0; }
  .fl-split > * > *, .fl-card > * { min-width: 0; }
}

/* THE MOBILE MENU OPENS BUT DOES NOT STACK — declared temporary deviation.
 *
 * Founder, on the live client site: "the menu on mobile isn't working right now
 * on production."
 *
 * Measured on production at a 390px page ROOT, not inferred. The menu DOES open:
 * the toggle is found, site.js removes the `hidden` attribute, aria-expanded
 * flips to true and there is no console error. But the panel then computes
 * `display: block`, so its inline `flex-direction: column` is inert — all six
 * links compute `display: inline` and FIVE of them share `top: 152`, running
 * together on one wrapped line. 91px of squashed text instead of a stacked menu.
 * From the outside that reads as "the menu is broken", and it is.
 *
 * THE CAUSE IS IN THE CANVAS, NOT THE PORT:
 *   components/Nav.js:50-51  emits flex-direction:column with NO display
 *   nothing, anywhere,       supplies one — verified by walking every served
 *                            stylesheet for a rule naming #fl-mobile-nav: ZERO,
 *                            including the _ds base we now ship.
 *
 * v2.3's changelog explains how it got here. MobileNav used to carry `hidden`
 * AND an inline `display:flex`, which printed the full mobile nav under the
 * desktop header on every page, so the fix removed the inline display and had
 * site.js toggle `hidden` alone. That half is right and must stay. What never
 * landed is the CSS that makes the panel a flex column when it is NOT hidden —
 * so the component now declares a flex property on an element that is not a flex
 * container. Same class as the hero reel, where site.js reads data-reel-json and
 * Hero.js never emits it.
 *
 * WHY IT IS FIXED HERE ANYWAY. Normally a canvas defect goes back to the canvas
 * and nothing is shimmed. This is on a client's live domain, and the design's
 * intent is not ambiguous in the slightest: a nav whose rows carry
 * border-bottom separators, at font-size 18px, with flex-direction:column
 * already declared. One declaration implements exactly what the design says and
 * invents nothing.
 *
 * Note this composes correctly with the [hidden] rule above: that carries
 * !important, so a hidden panel stays display:none and only an OPEN panel picks
 * up the flex. Closing still works.
 *
 * RETIRE THIS when a cut carries `display:flex` for #fl-mobile-nav in
 * fants-theme.css. An override that outlives its cause becomes a second source
 * of truth. */
#fl-mobile-nav { display: flex; }
