/* ==========================================================================
   John DeSouza — portfolio

   Direction: editorial restraint. Warm paper ground, oversized light serif
   statements, vast whitespace, hairline rows. Color is carried by the work
   imagery, not by the page; the one accent is a forest green.

   Order: tokens → type roles → base → primitives → nav → home → case study
          → gate → unlock opener → motion → per-project grounds → phone
          → dashboard → blog → chat
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  color-scheme: light;

  /* color */
  --paper: #F6F4F0;
  --paper-2: #EFECE6;
  --surface: #FFFFFF;
  --surface-2: #EFECE6;
  --ink: #14100C;
  --ink-2: #5C564E;
  --ink-3: #6F675D;   /* 4.7:1 on paper-2, the darkest ground it sits on; #8A8279 was 3.2:1 */
  --hair: #E3DFD7;
  --hair-2: #CFC9BF;
  --accent: #3B6B44;
  --accent-ink: #2B5233;
  --accent-2: #6E9A5A;
  --glow: rgba(59, 107, 68, .12);
  --danger: #C0392B;
  --shadow-lift: 0 30px 60px -34px rgba(20, 16, 12, .30);
  --shadow-screen: 0 18px 40px -22px rgba(20, 16, 12, .45), 0 0 0 1px rgba(20, 16, 12, .06);
  --shadow-cutout: drop-shadow(0 18px 28px rgba(20, 16, 12, .18));   /* a piece of UI keyed out of its screen */
  /* waiting: the one amber on the site, for a step held in a queue (No-code tools' before and after) */
  --wait: #F4EAD6;
  --wait-edge: #C9A227;
  --wait-ink: #6E5620;   /* 5.9:1 on --wait */
  --contact-ground: linear-gradient(155deg, #241D17 0%, #14100C 58%, #100D0A 100%);   /* the contact block's ground and rings, inverted in the dark register */
  --contact-rings: rgba(246, 244, 240, .07);
  --scrim: rgba(20, 16, 12, .55);
  --plate-ink: #4A443D;    /* text on a panel ground, which stays paper in both registers; dark enough for the ruled and tinted grounds */
  --plate-ink-2: #5C564E;
  --plate-ink-0: #14100C;
  --plate-surface: #FFFFFF;
  --plate-paper: #F6F4F0;
  --plate-paper-2: #EFECE6;
  --plate-hair: #E3DFD7;
  --plate-hair-2: #CFC9BF;
  --plate-glow: rgba(59, 107, 68, .12);
  --plate-accent: #3B6B44;
  --plate-accent-ink: #2B5233;   /* the green on a panel: the accent's paper value, kept in the dark register too */
  --plate-accent-2: #6E9A5A;
  --plate-danger: #C0392B;

  /* panel ground: the warm sand every screenshot sits on. Each case study
     overrides these from its own subject matter (section 10). */
  --panel-base: #EFE8DB;
  --panel: linear-gradient(155deg, #F4EFE5 0%, #E4DAC6 100%);
  --panel-art: none;
  --panel-size: auto;

  /* faces */
  --font-display: "Crimson Pro", "Iowan Old Style", Georgia, serif;
  --font-body: "DM Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "DM Mono", ui-monospace, "SF Mono", Menlo, monospace;   /* the labels: eyebrows, figure numbers, the facts' keys */

  /* Grain: a static tile of noise multiplied into every panel ground, so a
     screenshot sits on paper rather than on a gradient. One SVG, rasterised
     once at its own size and repeated; never a live filter over the page. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .22 0 0 0 0 .18 0 0 0 0 .13 0 0 0 .13 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  --grain-size: 180px;

  /* Type scale: ten roles, and nothing on the site sets a size outside them.
     The serif speaks (display, title, heading, stat, quote); the sans does
     the work (subhead, lede, body, small, micro). Section 2 turns these into
     the roles themselves. */
  --t-display: clamp(2.625rem, 1.5rem + 3.9vw, 4.25rem);   /* 42 → 68 */
  --t-title:   clamp(2rem, 1.25rem + 2.4vw, 2.875rem);     /* 32 → 46 */
  --t-heading: clamp(1.5rem, 1.2rem + 1vw, 1.875rem);      /* 24 → 30 */
  --t-subhead: 1.25rem;                                    /* 20 */
  --t-lede:    clamp(1.125rem, 1rem + .45vw, 1.3125rem);   /* 18 → 21 */
  --t-body:    1.0625rem;                                  /* 17 */
  --t-small:   .9375rem;                                   /* 15 */
  --t-micro:   .75rem;                                     /* 12 */
  --t-stat:    clamp(2.75rem, 2rem + 2.5vw, 4rem);         /* 44 → 64 */
  --t-quote:   clamp(1.375rem, 1.15rem + 1vw, 1.75rem);    /* 22 → 28 */

  --lh-display: 1.02; --lh-title: 1.08; --lh-heading: 1.15; --lh-subhead: 1.3;
  --lh-lede: 1.4;     --lh-body: 1.55;  --lh-small: 1.5;    --lh-micro: 1.4;
  --lh-prose: 1.62;   /* running text in a post: looser than body, after craft.do */
  --lh-stat: 1;       --lh-quote: 1.3;

  --ls-display: -.02em; --ls-title: -.015em; --ls-heading: -.012em; --ls-subhead: -.005em;
  --ls-lede: -.005em;   --ls-micro: .06em;   --ls-stat: -.015em;    --ls-quote: -.01em;   /* micro is mono, which is wide already */

  /* measure */
  --prose: 40rem;          /* the reading column in a post: ~73 characters */
  --m-display: 16ch; --m-title: 24ch; --m-heading: 26ch; --m-quote: 44ch; --m-lede: 58ch; --m-body: 60ch;

  /* spacing scale: 4 · 8 · 12 · 16 · 24 · 32 · 48 · 64 · 96 · 128 */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px; --sp-5: 24px;
  --sp-6: 32px; --sp-7: 48px; --sp-8: 64px; --sp-9: 96px; --sp-10: 128px;

  /* rhythm */
  --stack-label: var(--sp-4);                    /* eyebrow → heading */
  --stack-head: var(--sp-5);                     /* heading → text */
  --section: clamp(64px, 9vw, 128px);            /* between sections */
  --row: clamp(48px, 6vw, 96px);                 /* between rows in a section */
  --panel-h: clamp(360px, 44vw, 620px);          /* a visual panel beside copy */
  --col-pad: clamp(20px, 3vw, 40px);             /* inset of a divided column */
  --panel-pad: clamp(24px, 4vw, 48px);           /* inset of a panel or a quote card */
  --card-pad: clamp(32px, 5vw, 56px);            /* inset of a leadership card */

  /* layout */
  --radius: 18px;
  --radius-sm: 10px;
  --radius-btn: 4px;       /* every button: squared, never a pill */
  --max: 1240px;
  --gutter: clamp(20px, 5vw, 56px);
  --nav-h: 82px;
  /* Motion. Enter/exit and hover use the ease-out; movement that starts and
     ends on screen uses the in-out. Controls answer inside 200ms; nothing a
     user triggers runs past 300ms, save the unlock opener and the LinkedIn
     button (.talk), both by choice. Sections arriving on scroll are the one
     slow move: they glide up on the soft ease so the travel is seen. */
  --ease: cubic-bezier(.23, 1, .32, 1);
  --ease-in-out: cubic-bezier(.77, 0, .175, 1);
  --ease-soft: cubic-bezier(.33, 1, .68, 1);
  --ease-settle: cubic-bezier(.34, 1.56, .64, 1);   /* overshoots by a tenth and settles back: the phone menu's rows, nothing else */
  --dur-press: 140ms;
  --dur-hover: 200ms;
  --dur-ui: 240ms;
  --dur-reveal: 700ms;
  --reveal-y: 48px;
  --dur-reveal-in: var(--dur-reveal);   /* the block's rise; --dur-reveal itself also times .dash-bar */
  --reveal-col-wait: 120ms;   /* columns and figures follow their block */
  --reveal-col-step: 100ms;
  --reveal-fig-step: 120ms;
  --reveal-col-dur: .5s;
  --reveal-fig-dur: .7s;
}
/* A phone flicks a screen in a few hundred ms, so a block rises quicker and
   its columns land inside ~600ms (main.js starts it earlier, too). */
@media (max-width: 640px), (hover: none) and (pointer: coarse) {
  :root {
    --dur-reveal-in: 300ms;
    --reveal-y: 24px;
    --reveal-col-wait: 60ms;
    --reveal-col-step: 60ms;
    --reveal-fig-step: 50ms;
    --reveal-col-dur: .35s;
    --reveal-fig-dur: .4s;
  }
}

/* The dark register (after cursor.com's warm near-black): the same tokens
   in ink and the accent lifted so it still reads on the ground. The panel
   grounds and the covers stay as they are, plates of paper the screens were
   drawn for. The switch in the footer (main.js initTheme) stamps
   data-theme on the root and keeps it; with nothing stamped, the reader's
   system setting decides. Every colour is set here and nowhere else, so a
   dark rule never sits alone inside a media query. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  color-scheme: dark;
  --paper: #15120D;
  --paper-2: #1C1812;
  --surface: #221D16;
  --surface-2: #1C1812;
  --ink: #F0EBE2;
  --ink-2: #BDB4A7;
  --wait-text: var(--wait-ink);   /* --wait stays light in dark mode, so a step waiting on it takes the wait ink */
  --ink-3: #9C9387;   /* 5.8:1 on paper-2 */
  --hair: #2C261F;
  --hair-2: #3F382F;
  --accent: #7FB279;
  --accent-ink: #A9D1A0;
  --accent-2: #8FBE7C;
  --glow: rgba(127, 178, 121, .16);
  --danger: #E2705A;
  --shadow-lift: 0 30px 60px -34px rgba(0, 0, 0, .75);
  --shadow-screen: 0 18px 40px -22px rgba(0, 0, 0, .8), 0 0 0 1px rgba(255, 255, 255, .06);
  --shadow-cutout: drop-shadow(0 18px 28px rgba(0, 0, 0, .45));
  --contact-ground: linear-gradient(155deg, #F6F4F0 0%, #ECE8E1 58%, #E4DFD6 100%);
  --contact-rings: rgba(20, 16, 12, .08);
  --scrim: rgba(0, 0, 0, .55);
  }
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --paper: #15120D;
  --paper-2: #1C1812;
  --surface: #221D16;
  --surface-2: #1C1812;
  --ink: #F0EBE2;
  --ink-2: #BDB4A7;
  --wait-text: var(--wait-ink);   /* --wait stays light in dark mode, so a step waiting on it takes the wait ink */
  --ink-3: #9C9387;   /* 5.8:1 on paper-2 */
  --hair: #2C261F;
  --hair-2: #3F382F;
  --accent: #7FB279;
  --accent-ink: #A9D1A0;
  --accent-2: #8FBE7C;
  --glow: rgba(127, 178, 121, .16);
  --danger: #E2705A;
  --shadow-lift: 0 30px 60px -34px rgba(0, 0, 0, .75);
  --shadow-screen: 0 18px 40px -22px rgba(0, 0, 0, .8), 0 0 0 1px rgba(255, 255, 255, .06);
  --shadow-cutout: drop-shadow(0 18px 28px rgba(0, 0, 0, .45));
  --contact-ground: linear-gradient(155deg, #F6F4F0 0%, #ECE8E1 58%, #E4DFD6 100%);
  --contact-rings: rgba(20, 16, 12, .08);
  --scrim: rgba(0, 0, 0, .55);
}

/* --------------------------------------------------------------------------
   2. Type roles
   Every piece of text on the site takes exactly one of ten roles, and a role
   sets all of type at once: face, size, weight, line-height, letter-spacing,
   color, measure and a zero margin. Headings and paragraphs carry their
   role as a class in the markup (.t-title, .t-body, .eyebrow); the inline
   and structural text of a component (a cell, a cite, a button) is listed
   under its role here, and so is a blog post's body: the copy is drafted as
   prose, so .prose h2/h3/p/li/blockquote/figcaption are listed here rather
   than classed in the markup. Nothing after this section sets a face, size,
   weight, line-height or letter-spacing, with three exceptions: .btn and .chip set
   the line-height of their own box; the dashboard's visitor hashes and the
   map's credit line take the micro size without the role's caps; and the
   phone rules at the end move a few things a role up or down.
   -------------------------------------------------------------------------- */

/* The serif speaks: display, title, heading, stat, quote. It is never bold. */
.t-display, .t-title, .t-heading, .t-stat, .t-quote,
.brand, .facts dd, .avatar.glyph,
.prose h2, .prose blockquote p, .pillar-nav a
  { font-family: var(--font-display); font-weight: 400; color: var(--ink); margin: 0; }
.t-display
  { font-size: var(--t-display); line-height: var(--lh-display); letter-spacing: var(--ls-display); max-width: var(--m-display); text-wrap: balance; }
.t-title, .pillar-nav a
  { font-size: var(--t-title); line-height: var(--lh-title); letter-spacing: var(--ls-title); max-width: var(--m-title); text-wrap: balance; }
.t-heading, .brand, .facts dd, .avatar.glyph, .prose h2
  { font-size: var(--t-heading); line-height: var(--lh-heading); letter-spacing: var(--ls-heading); max-width: var(--m-heading); text-wrap: balance; }
.t-stat
  { font-size: var(--t-stat); line-height: var(--lh-stat); letter-spacing: var(--ls-stat); }
.t-quote, .prose blockquote p
  { font-size: var(--t-quote); line-height: var(--lh-quote); letter-spacing: var(--ls-quote); max-width: var(--m-quote); text-wrap: pretty; }
.t-stat, .facts dd { font-variant-numeric: lining-nums tabular-nums; }

/* The sans does the work: subhead, lede, body, small, micro */
.t-subhead, .t-lede, .t-body, .t-small, .t-micro, .eyebrow,
.testimonial-foot cite b,
.prose p, .prose li, .prose h3, .prose figcaption, .prose blockquote cite,
.post-meta, .post-cta, .sources li, .pillar-count,
.card-desc, .post-search input, .view-toggle button,
.layer-tab, .work-cta, .gate-form input, .arcade-post input, .seg-tab,
.btn, .chip, .nav-links ul a, .nav-kbd, .back, .cs-prev-link, .cs-all-link, .crumbs, .tools span, footer,
.facts dt, table, .tensions li, .testimonial-foot cite, .avatar, .testimonial-mark,
.gallery figcaption, .proof-head small, .tooltip, .chart-pill, details.data,
.dash-live, .dash-list li, .leaflet-container, .leaflet-popup-content,
thead th, .stake-row .lbl, .table-wrap td[data-label]::before, .dash-kind, .dash-now,
.chat-msg, .chat-offer-text, .chat-form textarea, .chat-unlock label, .chat-unlock input, .chat-unlocked, .chat-error, .dash-settings :is(label, input, textarea, legend)
  { font-family: var(--font-body); margin: 0; }
.t-subhead, .testimonial-foot cite b, .prose h3
  { font-size: var(--t-subhead); line-height: var(--lh-subhead); letter-spacing: var(--ls-subhead); font-weight: 500; color: var(--ink); }
.t-lede
  { font-size: var(--t-lede); line-height: var(--lh-lede); letter-spacing: var(--ls-lede); font-weight: 400; color: var(--ink-2); max-width: var(--m-lede); }
.t-body, .layer-tab, .work-cta, .gate-form input, .arcade-post input, .chat-form textarea, .chat-unlock input, .dash-settings :is(input, textarea)
  { font-size: var(--t-body); line-height: var(--lh-body); font-weight: 400; color: var(--ink-2); max-width: var(--m-body); }
.t-small, .btn, .chip, .nav-links ul a, .nav-kbd, .back, .cs-prev-link, .cs-all-link, .crumbs, .tools span, footer, .seg-tab,
table, .tensions li, .testimonial-foot cite, .avatar, .testimonial-mark,
.gallery figcaption, .proof-head small, .tooltip, .chart-pill, details.data,
.dash-live, .dash-list li, .leaflet-container, .leaflet-popup-content,
.prose figcaption, .prose blockquote cite, .post-meta, .post-cta, .sources li, .pillar-count,
.card-desc, .post-search input, .view-toggle button,
.chat-msg, .chat-offer-text, .chat-unlock label, .chat-unlocked, .chat-error, .dash-settings :is(label, legend)
  { font-size: var(--t-small); line-height: var(--lh-small); font-weight: 400; color: var(--ink-2); }
.prose p, .prose li
  { font-size: var(--t-body); line-height: var(--lh-prose); font-weight: 400; color: var(--ink-2); max-width: none; }
/* The mono labels (after linear.app's "FIG 0.1" and warp.dev's "[ fig. 1 ]"):
   the micro role is the one place the site speaks in a third face, so a label
   reads as the system's annotation on the page rather than as more copy. The
   facts' keys and a figure's number take it too. */
.t-micro, .eyebrow, thead th, .stake-row .lbl, .table-wrap td[data-label]::before, .dash-kind, .dash-now, .facts dt, .fig-no
  { font-family: var(--font-mono); font-size: var(--t-micro); line-height: var(--lh-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; font-weight: 500; color: var(--ink-2); }
.eyebrow { display: block; }
.t-lede, .t-body { text-wrap: pretty; }   /* no paragraph ends on a lone word */
.facts dt, .fig-no { color: var(--ink-3); }
/* Every captioned figure on a page is numbered in order, the way a report's
   plates are: "fig. 03" in mono before the caption's own words. */
body { counter-reset: fig; }
.panel figcaption { color: var(--plate-ink); }
.panel figcaption::before, .panel .gallery figcaption::before { color: var(--plate-ink-2); }   /* the gallery rule below would otherwise win on order */
.gallery figcaption::before, .prose figcaption::before, .walk-note::before {
  counter-increment: fig; content: "fig. " counter(fig, decimal-leading-zero);
  font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing: var(--ls-micro); font-weight: 500; color: var(--ink-3);
  margin-right: .75em;
}

/* Weight: 500 is the one emphasis, for whatever names or acts. */
strong, b, tbody th, .t-body a, .t-lede a, .prose p a, .prose li a, .post-cta,
.btn, .chip, .chart-pill, .tooltip b, .tip-val, .nav-links ul a, .nav-kbd, .back, .cs-prev-link, .cs-all-link, .crumbs, .tools span, .layer-tab, .layer-mark, .work-cta, .avatar, .testimonial-mark, .seg-tab,
.chat-msg a, .chat-unlock label, .chat-unlocked, .dash-settings :is(label, legend)
  { font-weight: 500; }

/* Tone: ink for what leads, ink-2 for what runs (the roles' default), ink-3
   for what sits beside on paper (a caption, a cite, the footer). Ink-3 is
   4.7:1 on paper-2 and under 4.5:1 on the panel grounds, so a caption inside
   a panel (.gallery figcaption) keeps ink-2. The accent marks the one action
   or number in a block. */
strong, b, tbody th, .chip, .tools span, .avatar, .testimonial-mark, .tooltip b, .tip-val, .crumbs [aria-current], .dash-label { color: var(--ink); }
.chart-pill { color: var(--surface); }
footer, .walk-note, .testimonial-foot cite, .kudos cite, .exp-dates, .crumb-sep, .gate-form input::placeholder, .arcade-post input::placeholder, .chat-form textarea::placeholder,
.prose figcaption, .prose blockquote cite, .pillar-count { color: var(--ink-3); }
.work-cta, .t-body a, .t-lede a, .outcome-stat .t-stat, .proof-head .t-stat, thead .hl { color: var(--accent-ink); }
.prose p a, .prose li a, .sources a, .chat-msg a { color: var(--accent-ink); }

/* Number flow: a headline stat that main.js rolls in digit by digit. Each
   digit is a column of 0 to 9 behind a soft mask, and the whole stat is lit
   from above with the accent, darker at the baseline. Without JS, or under
   reduced motion, the stat is plain text in the same gradient. */
[data-flow] { --flow-lit: color-mix(in srgb, var(--accent) 62%, var(--accent-2)); }
[data-flow]:not(.flow-on), .flow-num, .flow-char {
  background: linear-gradient(0deg, var(--accent-ink), var(--accent) 55%, var(--flow-lit));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.flow-on { display: flex; align-items: flex-start; white-space: nowrap; }
.flow-digit, .flow-char { display: block; height: 1em; line-height: 1; }
/* Crimson Pro's figures sit from .155em to .745em of the line box, so a .22em
   fade over an .08em bleed never touches the digit at rest. */
.flow-digit { overflow: hidden; padding-block: .08em; margin-block: -.08em; box-sizing: content-box;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 .22em, #000 calc(100% - .22em), transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 .22em, #000 calc(100% - .22em), transparent); }
.flow-strip { display: block; will-change: transform; }
.flow-num { display: block; height: 1em; line-height: 1; }

/* Stacks: the fixed distances between a label, its heading and its text */
.eyebrow + .t-display, .eyebrow + .t-title { margin-top: var(--stack-label); }
.eyebrow + .t-heading, .eyebrow + .t-subhead, .eyebrow + .t-stat { margin-top: var(--sp-3); }
.t-display + .t-lede, .t-title + .t-lede, .t-title + .t-body { margin-top: var(--stack-head); }
.t-heading + .t-body, .t-heading + .t-lede { margin-top: var(--sp-4); }
.t-subhead + .t-body, .t-subhead + .t-small { margin-top: var(--sp-3); }
.t-stat + .t-small, .t-stat + small { margin-top: var(--sp-2); }
.t-body + .t-body { margin-top: var(--sp-4); }

/* --------------------------------------------------------------------------
   3. Base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img { max-width: 100%; height: auto; display: block; }
[hidden] { display: none !important; }
a { color: inherit; }
/* Text links carry an underline in their own ink, so a link never rests on
   colour alone (WCAG 1.4.1); it thickens on hover. The nav's rows, the
   buttons and the whole-card links (a work row, a post card) are the
   exceptions: their shape says link. A flex label (.pillar-label, .work-cta)
   is underlined itself, since its parent link lays out more than text. */
.t-body a, .t-lede a, .t-small a, .prose a, .sources a, .chat-msg a,
footer a, .crumbs a, .back, .cs-prev-link, .cs-all-link, .work-cta, .post-cta, .pillar-label, .cs-next-title a, .post-next .t-title a
  { text-decoration-line: underline; text-decoration-thickness: 1px; text-decoration-color: currentColor; text-underline-offset: .22em; }
:is(.t-body, .t-lede, .t-small, .prose, .sources, .chat-msg, footer, .crumbs) a:hover, .back:hover, .cs-prev-link:hover, .cs-all-link:hover, .post-cta:hover,
.pillar-nav a:hover .pillar-label, .cs-next-title a:hover, .post-next .t-title a:hover, .case-row:hover .work-cta
  { text-decoration-thickness: 2px; }
button { font-family: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.skip { position: absolute; left: -999px; top: 12px; z-index: 200; background: var(--ink); color: var(--paper); padding: 8px 12px; border-radius: var(--radius-btn); }
.skip:focus { left: 12px; }
.wrap { max-width: var(--max); margin-inline: auto; padding-inline: var(--gutter); position: relative; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
main, .nav { position: relative; z-index: 1; }

/* --------------------------------------------------------------------------
   4. Primitives
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 13px 22px; border-radius: var(--radius-btn); line-height: 1;   /* the box is the padding, so the label sits at its own height */
  text-decoration: none; cursor: pointer; position: relative;
  border: 1px solid transparent;
  transition: transform var(--dur-press) var(--ease), background var(--dur-hover), border-color var(--dur-hover), box-shadow var(--dur-hover), color var(--dur-hover);
}
/* Every pressable thing on the site scales down on press, so the interface
   answers the hand before the page does. Hover lifts are gated to pointers
   that can hover: a tap on a phone would otherwise leave the button raised. */
.btn:active, .icon-btn:active, .layer-tab:active, .testimonial-controls button:active { transform: scale(.97); }
@media (hover: hover) and (pointer: fine) {
  .btn:hover { transform: translateY(-2px); }
  .btn:hover:active { transform: translateY(-2px) scale(.97); }
}
/* The walkthrough's play control is centered by its transform, so it keeps that
   through the press; the .btn hover lift above would drop it and move the
   button out from under the cursor before the click lands. */
.walk .play:active, .walk .play:hover:active { transform: translate(-50%, -50%) scale(.97); }
.btn-primary { background: var(--ink); color: var(--paper); }
.btn-primary:hover { background: var(--accent); color: var(--surface); box-shadow: 0 10px 30px -10px var(--glow); }
.btn-ghost { border-color: var(--hair-2); color: var(--ink); background: var(--surface); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent-ink); }
.btn-sm { padding: 10px 16px; }
.btn svg { width: 16px; height: 16px; }
/* Reach-out buttons: a leading icon settles into a second one on hover —
   download becomes a check, an envelope becomes a send arrow, the LinkedIn
   mark becomes an external-link cue. Two icons stacked in place, crossfaded. */
.icon-swap { position: relative; width: 16px; height: 16px; }
.icon-swap svg { position: absolute; inset: 0; transition: opacity var(--dur-hover), transform var(--dur-hover) var(--ease); }
.icon-swap svg:last-child { opacity: 0; transform: scale(.5); }
@media (hover: hover) and (pointer: fine) {
  .btn:hover .icon-swap svg:first-child { opacity: 0; transform: scale(.5); }
  .btn:hover .icon-swap svg:last-child { opacity: 1; transform: scale(1); }
}
.icon-btn {
  width: 38px; height: 38px; border-radius: var(--radius-btn); display: grid; place-items: center;
  border: 1px solid var(--hair-2); background: var(--surface); color: var(--ink); cursor: pointer;
  transition: transform var(--dur-press) var(--ease), border-color var(--dur-hover), color var(--dur-hover);
}
@media (hover: hover) and (pointer: fine) {   /* color only for pointers that hover; a tap on a phone would leave the ring green */
  .icon-btn:hover { border-color: var(--accent); color: var(--accent-ink); }
}
.icon-btn svg { width: 17px; height: 17px; }

/* Chip: one shape for every tag on the site */
.chip {
  display: inline-block; line-height: 1.25;
  background: color-mix(in srgb, var(--surface) 92%, var(--paper));
  border: 1px solid color-mix(in srgb, var(--ink) 10%, transparent); border-radius: 10px;
  padding: 7px 12px; white-space: nowrap;
}
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chips .chip { white-space: normal; text-wrap: pretty; }   /* one line whenever it fits; a chip wider than its row wraps inside itself instead of pushing the card past a narrow phone */

/* Panel: the tinted ground every screenshot sits on, from the --panel-* tokens
   (the site's warm sand by default; each project sets its own in section 10). */
.panel, .case-media {
  position: relative; overflow: hidden; margin: 0;
  border-radius: var(--radius);
  background-color: var(--panel-base);
  background-image: var(--grain), var(--panel-art), var(--panel);
  background-size: var(--grain-size), var(--panel-size), auto;
  background-blend-mode: multiply, normal, normal;
  display: flex; align-items: center; justify-content: center;
  gap: clamp(16px, 2.6vw, 32px); padding: var(--panel-pad);
}
.panel img, .case-media img { width: auto; max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 14px; box-shadow: var(--shadow-screen); }
.panel.wide img { border-radius: 10px; }
.row.full > .panel.wide > img { width: 100%; }   /* its width attributes then reserve the height before it loads */
.panel.flat img { box-shadow: none; }   /* an illustration: keeps the 14px radius of every panel image, drops the screen shadow */
.panel > .gallery { width: 100%; }

/* Section intro: a centred label + title (+ lede), the way a section opens */
.section-intro { display: flex; flex-direction: column; align-items: center; text-align: center; margin-bottom: clamp(40px, 5vw, 64px); }
.section-intro > * { margin-inline: auto; }

/* Columns: icon, heading, text. A dashed rule divides the columns, with a
   solid accent segment beside each heading. Used for the facts beneath a
   hero and for every three-across block in a case study. */
.cols { display: grid; grid-template-columns: repeat(var(--n, 3), minmax(0, 1fr)); gap: 0; margin: 0; padding: 0; list-style: none; }
.col { position: relative; min-width: 0; padding: var(--sp-2) var(--col-pad); border-left: 1px dashed var(--hair-2); }
.col:first-child { padding-left: 0; border-left: 0; }
.col:last-child { padding-right: 0; }
.col-head { display: flex; align-items: center; gap: var(--sp-3); margin: 0 0 var(--sp-3); }   /* icon beside its heading, not stacked above it */
.col-icon { width: 22px; height: 22px; flex: none; display: block; margin: 0; color: var(--ink); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.col-icon .ac { stroke: var(--accent); }
.col-icon .ac-fill { stroke: var(--accent); fill: var(--glow); }
.col .t-subhead { position: relative; }
.col:not(:first-child) .t-subhead::before {
  content: ""; position: absolute; left: calc(-1 * var(--col-pad) - 1px); top: .1em; height: 1.1em; width: 2px;
  background: var(--accent); border-radius: 2px;
}
@media (max-width: 900px) {
  .cols { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: var(--sp-7); }
  .col:nth-child(odd) { padding-left: 0; border-left: 0; }
  .col:nth-child(odd) .t-subhead::before { display: none; }
  .col:nth-child(even) { padding-right: 0; }
}
@media (max-width: 560px) {
  .cols { grid-template-columns: 1fr; row-gap: 0; }
  .col { padding: var(--sp-6) 0; border-left: 0; border-top: 1px dashed var(--hair-2); }
  .col:first-child { border-top: 0; padding-top: 0; }
  .col .t-subhead::before { display: none; }
}

/* Tables. A frame whose table is wider than it scrolls sideways, and main.js
   (dash.js on the dashboard) makes it a tab stop only while it does; the ring
   is the one the inputs wear. */
.table-wrap { overflow-x: auto; border: 1px solid var(--hair); border-radius: 16px; background: var(--surface); }
.table-wrap:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
table { border-collapse: collapse; width: 100%; min-width: 640px; }
th, td { text-align: left; padding: 15px 16px; border-bottom: 1px solid var(--hair); vertical-align: top; }
thead th { background: var(--surface-2); white-space: nowrap; }
tbody th { white-space: nowrap; }
tr:last-child td, tr:last-child th { border-bottom: 0; }
.hl { background: var(--glow); color: var(--ink); }

/* --------------------------------------------------------------------------
   5. Nav
   -------------------------------------------------------------------------- */
.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 50; height: var(--nav-h);
  display: flex; align-items: center;
  background: color-mix(in srgb, var(--paper) 86%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.5); backdrop-filter: blur(18px) saturate(1.5);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-hover), background var(--dur-hover);
}
.nav.scrolled { border-color: var(--hair); }
.nav .wrap { display: flex; align-items: center; gap: 28px; width: 100%; }
.brand { display: flex; align-items: center; text-decoration: none; white-space: nowrap; }   /* the name alone; the tally signs the tab, the home screen and the link preview */
.nav-links { margin-left: auto; }
.nav-links ul { display: flex; gap: 28px; list-style: none; padding: 0; margin: 0; }
.nav-links ul a { text-decoration: none; transition: color var(--dur-hover); }   /* the rows; the phone foot holds plain .btns */
.nav-links a.nav-icon { display: inline-flex; align-items: center; gap: 10px; padding-block: 2px; }
.nav-links a.nav-icon svg { width: 17px; height: 17px; display: block; }
.nav-links a.nav-icon span { display: none; }   /* the icon alone in the bar; the word alone in the phone menu */
.nav-links a.nav-download { display: inline-flex; align-items: center; gap: 6px; padding-block: 2px; }   /* the word plus a download arrow, so it reads as a file, not a page */
.nav-links a.nav-download svg { width: 15px; height: 15px; display: block; }
.nav-links ul a:hover, .nav-links ul a[aria-current] { color: var(--ink); }
/* The current page, or on the home page the section under the reader
   (main.js marks it location), carries a hairline in the accent. */
@media (min-width: 1024px) {
  .nav-links ul a[aria-current] { text-decoration: underline; text-decoration-color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 7px; }
}
.nav-links a.nav-icon svg { position: relative; top: 2px; }   /* the mark's optical centre sits on the words' x-height, not the line box's */
.nav-actions { display: flex; gap: 10px; align-items: center; }
.nav-toggle { display: none; }
.nav-foot { display: none; }
/* The bar's row (the name, six links and the button) needs 1024px; below
   that, tablets included, the menu sheet takes over. */
@media (max-width: 1023px) {
  .nav .wrap { gap: 12px; position: static; }   /* the sheet measures from the bar, not from the wrap centred inside it */
  /* The phone menu, after harvey.ai: open, the bar goes solid and a sheet of
     paper fills the screen beneath it. The rows take the title role and rise
     into place one after another once the sheet is in; the two things a
     reader does from here, Contact and Resume, are buttons at the foot.
     Everything stays in the tree and transitions both ways. Opening: the
     sheet fades in (--dur-ui), the foot follows from --dur-press, and the
     rows rise with the sheet, 20ms apart, each a 16px rise over --dur-hover
     that overshoots by a hair and settles (--ease-settle), so the last is in
     place by about 320ms. Closing
     runs in reverse order and faster: rows and foot fade
     together without moving (--dur-press), then the sheet (--dur-ui).
     visibility waits for the last fade, so a hidden sheet takes no taps or
     focus. */
  .nav.open { background: var(--paper); border-color: transparent; }
  .nav-links {
    position: absolute; left: 0; right: 0; top: var(--nav-h);
    height: calc(100vh - var(--nav-h)); height: calc(100dvh - var(--nav-h));
    display: flex; flex-direction: column;
    background: var(--paper);
    opacity: 0; visibility: hidden;
    transition: opacity var(--dur-ui) var(--ease) var(--dur-press), visibility 0s calc(var(--dur-press) + var(--dur-ui));
  }
  .nav.open .nav-links { opacity: 1; visibility: visible; transition: opacity var(--dur-ui) var(--ease), visibility 0s; }
  .nav-links ul {
    flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;   /* a phone on its side: the rows scroll under the foot, the page behind stays put */
    flex-direction: column; gap: 0; padding: var(--sp-8) var(--gutter) var(--sp-7);
  }
  .nav-links li { opacity: 0; transform: translateY(16px); transition: opacity var(--dur-press) var(--ease), transform 0s var(--dur-press); }   /* closing: fade in place, then reset the rise unseen */
  .nav.open .nav-links li { opacity: 1; transform: none; transition: opacity var(--dur-hover) var(--ease) var(--wait), transform var(--dur-hover) var(--ease-settle) var(--wait); }   /* the rise settles with a small bounce */
  .nav-links li:nth-child(1) { --wait: 0ms; }
  .nav-links li:nth-child(2) { --wait: 20ms; }
  .nav-links li:nth-child(3) { --wait: 40ms; }
  .nav-links li:nth-child(4) { --wait: 60ms; }
  .nav-links li:nth-child(5) { --wait: 80ms; }
  .nav-links li:nth-child(6) { --wait: 100ms; }
  .nav-links li:nth-child(7) { --wait: 120ms; }
  .nav-links li.nav-resume { display: none; }   /* Resume is a button at the foot here */
  /* A menu row takes the title role (the phone rules are allowed to move a
     thing between roles); ink, as every row is a place to go, not a state. */
  .nav-links ul a, .nav-links ul a.nav-icon, .nav-links ul .nav-kbd {
    display: block; padding: var(--sp-4) 0; text-align: left;   /* a row's pitch is twice its type size */
    font-family: var(--font-display); font-weight: 400; font-size: var(--t-title); line-height: var(--lh-title); letter-spacing: var(--ls-title); color: var(--ink);
  }
  .nav-links a.nav-icon span { display: inline; }
  .nav-links .nav-kbd kbd { display: none; }   /* no keys on a phone */
  .nav-links a.nav-icon svg { display: none; }   /* the word alone here; the mark is for the bar */
  .nav-foot {
    position: relative; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-4);
    padding: var(--sp-6) var(--gutter) calc(var(--gutter) + env(safe-area-inset-bottom, 0px));
    opacity: 0; transition: opacity var(--dur-press) var(--ease);
  }
  .nav-foot::before {   /* rows that scroll under the foot fade out before they reach the buttons */
    content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: var(--sp-8); pointer-events: none;
    background: linear-gradient(to bottom, color-mix(in srgb, var(--paper) 0%, transparent), var(--paper));
  }
  .nav.open .nav-foot { opacity: 1; transition: opacity var(--dur-ui) var(--ease) var(--dur-press); }
  .nav-foot .btn { justify-content: center; padding-inline: var(--sp-3); }
  /* The page holds its scroll position while the menu is open. Overflow on the
     root stops wheels and keys without moving anything (the body used to go
     position: fixed, which reset the scroll to 0 and put it back on close, and
     the page jumped); a touchmove guard in main.js stops iOS Safari
     rubber-banding the page behind the sheet, which the root rule does not. */
  html.nav-locked { overflow: hidden; }
  .nav-toggle { display: grid; margin-left: auto; }
  /* Two lines become a cross while the menu is open: each turns 45° about its
     own centre as it moves to the middle. */
  .nav-toggle path { transform-box: view-box; transform-origin: 12px 12px; transition: transform var(--dur-ui) var(--ease); }
  .nav.open .nav-toggle path:nth-child(1) { transform: rotate(45deg) translateY(3.5px); }   /* Hugeicons' menu-09 draws its lines at 8.5 and 15.5 */
  .nav.open .nav-toggle path:nth-child(2) { transform: rotate(-45deg) translateY(-3.5px); }
  .nav-actions { display: none; }   /* its one button is at the sheet's foot here, so the toggle sits on the gutter */
}
/* A narrow phone: the foot's two buttons stack rather than wrap their words */
@media (max-width: 360px) { .nav-foot { grid-template-columns: minmax(0, 1fr); } }

/* --------------------------------------------------------------------------
   6. Home
   -------------------------------------------------------------------------- */
/* Hero: a slow wash of warm light behind the statement */
.hero { position: relative; isolation: isolate; overflow: hidden; text-align: center;
  padding-top: calc(var(--nav-h) + clamp(84px, 16vh, 190px)); padding-bottom: clamp(64px, 10vh, 130px); }
.hero > .wrap { position: relative; z-index: 1; }
/* The field: a canvas over the whole block, drawn by main.js ("Fields"
   there); the block names its piece in data-field. In the hero it fades out
   under the nav and into the next section so the dots never end in a hard
   edge. */
.field { position: absolute; inset: 0; z-index: 0; display: block; width: 100%; height: 100%; pointer-events: none; }
.hero .field {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, rgba(0, 0, 0, .4) 12%, #000 30%, #000 88%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, rgba(0, 0, 0, .4) 12%, #000 30%, #000 88%, transparent 100%); }
.hero .art-ctl { right: max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter))); bottom: 20px; z-index: 2; }   /* on the content column's edge, not the screen's */

.hero h1 { margin-inline: auto; }
@media (min-width: 900px) { .hero h1 { max-width: 34ch; } .hero h1 .nowrap-lg { white-space: nowrap; } }
/* The lede sits over the color wash. Ink-2 fell to 4.1:1 where a blob passes
   under it (3.3:1 where two overlap), so the hero sets it in ink. */
.hero .t-lede { margin-inline: auto; color: var(--ink); text-wrap: pretty; }
.hero-ctas { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: clamp(28px, 3vw, 38px); }

/* Sections */
.section { padding-block: var(--section); position: relative; }
.section.tight { padding-top: 0; }
.section.alt { border-block: 1px solid var(--hair); background: var(--paper-2); }
.section.alt + .section.alt { border-top: 0; }   /* back to back, they share the one rule */

/* Selected work: an editorial index, each row led by the company's wordmark */
.cases { display: flex; flex-direction: column; border-top: 1px solid var(--hair); }
.case-row {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(28px, 4vw, 64px); align-items: center;
  padding: clamp(32px, 4.5vw, 60px) 0; border-bottom: 1px solid var(--hair);
  text-decoration: none; color: inherit;
}
.case-copy { display: flex; flex-direction: column; align-items: flex-start; }
.case-logo { width: auto; height: 28px; margin-bottom: clamp(18px, 2.2vw, 26px); }
.case-logo[alt="Best Egg"] { height: 24px; }   /* its heavy wordmark reads a size up at the shared height (the experience track sizes it 25 against 29) */
.case-copy .t-body { max-width: 46ch; }
.case-copy .chips { margin-top: var(--sp-4); }
.case-copy .work-cta { margin-top: var(--sp-5); }
.case-media { aspect-ratio: 16 / 10; padding: clamp(20px, 3vw, 40px); }
.case-media img { max-width: 46%; transition: transform .3s var(--ease); }
.case-media.wide img { max-width: 100%; border-radius: 10px; }
/* Cutout: the one piece of UI the case study is about, keyed out of its screen
   and floating alone on the ground at reading size, not the whole screen shrunk to fit.
   .bleed blows a taller piece up until it runs off the bottom. */
.case-media.cutout img { width: 100%; max-width: 100%; height: auto; border-radius: 0; box-shadow: none; filter: var(--shadow-cutout); }
.case-media.cutout.bleed img { position: absolute; top: 5%; left: 17%; width: 66%; max-width: none; max-height: none; }
/* Emerge: one screen, large, coming out of a bottom corner of the ground so the
   part that matters reads at size and the ground clips the rest. --x/--y set the
   screen's visible corner, --w its zoom; .right shows its top-right corner instead
   of its top-left. A phone screen carries its own keyed corners, so it drops the
   CSS radius and ring and takes the cutout's drop-shadow. */
.case-media.emerge img { position: absolute; top: var(--y, 10%); left: var(--x, 14%); width: var(--w, 88%); max-width: none; max-height: none; }
.case-media.emerge.right img { left: auto; right: var(--x, 14%); }
.case-media.emerge:not(.wide) img { border-radius: 0; box-shadow: none; filter: var(--shadow-cutout); }
.case-cross-sell .case-media.emerge { --x: 12%; --y: 10%; --w: 92%; }
.case-no-code .case-media.emerge { --x: 14%; --y: 12%; --w: 150%; }
.case-staking .case-media.emerge { --x: 14%; --y: 12%; --w: 140%; }
.case-audit .case-media.emerge { --x: 10%; --y: 12%; --w: 124%; }
.case-system .case-media.emerge { --x: 6%; --y: 8%; --w: 230%; }   /* the sheet's top-left corner at reading size, still running off the ground */
@media (hover: hover) and (pointer: fine) {
  .case-row:hover .case-media img { transform: translateY(-5px); }
}
.work-all { display: flex; justify-content: center; margin-top: var(--sp-5); }   /* close under the deck, so it reads as its foot */
/* The list as a pile (after harvey.ai's Get more done with agents): from
   821px up each row is a card, copy beside its screen, and it sticks under
   the bar while the page keeps scrolling; the next card rises over it after
   a hold. Nothing stops the scroll: the hold is only the stretch between
   two cards. On a short screen (under 700px tall) a card would not fit
   under the bar, so the cards stay a plain column there. */
@media (min-width: 821px) {
  .cases { display: grid; grid-auto-rows: 1fr; row-gap: clamp(20px, 3vw, 32px); border-top: 0; }   /* every card as tall as the tallest, so each one covers the one under it exactly */
  .cases > .case-row {
    gap: clamp(20px, 3vw, 40px); align-items: stretch;
    padding: clamp(14px, 1.6vw, 20px); border: 1px solid var(--hair); border-radius: calc(var(--radius) + 6px);
    background: var(--surface); box-shadow: var(--shadow-lift);
  }
  .cases > .case-row > .case-copy { justify-content: center; padding: clamp(12px, 2vw, 28px); }
  .cases > .case-row > .case-media { border-radius: var(--radius); aspect-ratio: auto; min-height: 380px; }   /* fills its cell; a ratio here would take its width from the card's height and run past the column */
}
@media (min-width: 821px) and (min-height: 700px) {
  .cases { row-gap: 32vh; }   /* the hold: each card sits alone under the bar for this stretch, then the next rises over it */
  .cases > .case-row { position: sticky; top: calc(var(--nav-h) + var(--sp-5)); box-shadow: none; }   /* no lift: under a pile the shadows would add up, card on card */
}
@media (max-width: 820px) {
  .case-row { grid-template-columns: minmax(0, 1fr); gap: 24px; }
  .case-media { order: -1; aspect-ratio: 4 / 3; }
}

/* The featured three on the home page (after uselayouts.com's stack scroll
   reveal): each case study is a card, copy beside its screen on the
   project's ground. main.js (initStack) sets .on, pins the deck in the
   viewport and peels each card but the last up and back over its own
   stretch of the scroll, the cards behind stepping forward as it goes.
   Without the script, or under reduced motion, the cards sit in a column. */
.stack { --peek: 18px; --card-h: min(72vh, 540px); }
.stack-deck { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(20px, 3vw, 32px); }   /* never wider than the page, whatever a card holds */
.stack-card {
  display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: clamp(20px, 3vw, 40px);
  padding: clamp(14px, 1.6vw, 20px); color: inherit; text-decoration: none;
  background: var(--surface); border: 1px solid var(--hair); border-radius: calc(var(--radius) + 6px); box-shadow: var(--shadow-lift);
}
.stack-copy { display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start; gap: 20px; min-height: 0; padding: clamp(12px, 2vw, 28px); }
.stack-n:empty { display: none; }
.stack-copy .t-body { max-width: 46ch; }
.stack-copy .chips { margin-top: var(--sp-4); }
.stack-card .case-media { border-radius: var(--radius); }
/* A wide screen always runs off the ground's foot, however tall the card
   makes the ground: below that height it grows, anchored at its top corner,
   instead of leaving a band of empty ground under it. */
.stack-card .case-media.emerge img { min-height: calc(100% - var(--y, 10%) + 12%); object-fit: cover; object-position: left top; }
.stack-card .case-media.emerge.right img { object-position: right top; }
/* The pinned deck: the cards lie on each other in a deck that sticks in the
   middle of the screen below the bar for as long as the track lasts. */
.stack.on .stack-track { height: calc(var(--card-h) + var(--peek) * 2 + 170vh); }
.stack.on .stack-deck {
  display: block; position: sticky; height: calc(var(--card-h) + var(--peek) * 2);
  top: calc((100vh + var(--nav-h) - var(--card-h) - var(--peek) * 2) / 2);
}
.stack.on .stack-card { position: absolute; inset: 0 0 auto 0; height: var(--card-h); box-sizing: border-box; overflow: hidden; transform-origin: 50% 100%; will-change: transform; }
.stack.on .stack-card .case-media { aspect-ratio: auto; height: 100%; }
/* A jump to a section passes the deck as one block (main.js, initStack): for
   the length of the scroll the track keeps only the deck and whatever of its
   empty stretch was on screen, and the deck stops sticking. */
.stack.on.whole .stack-track { height: auto; padding-block: var(--whole-above, 0px) var(--whole-below, 0px); }
.stack.on.whole .stack-deck { position: relative; top: auto; }
@media (max-width: 820px) {
  .stack { --card-h: min(80vh, 640px); }
  .stack-card { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  .stack-card .case-media { order: -1; aspect-ratio: 4 / 3; }
  .stack-copy { padding: 12px 8px 8px; }
  .stack.on .stack-card { grid-template-rows: minmax(0, 1fr) auto; }   /* the copy takes its own height and the screen fills the rest, so a short summary leaves no empty card under it */
  .stack.on .stack-copy { justify-content: flex-start; gap: 12px; padding-bottom: 28px; }   /* with the card's own padding the last 40px stay empty, so a card behind never shows through the 18px peek */
  .stack.on .stack-copy :is(.chips, .stack-n) { display: none; }
  .stack.on .stack-copy .t-body { display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
}

/* Leadership panels */
.card { position: relative; background: var(--surface); border: 1px solid var(--hair); border-radius: var(--radius); padding: 28px; }
.lead-solo + .lead-solo { margin-top: var(--row); }
.lead-solo { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: clamp(28px, 5vw, 72px); padding: var(--card-pad); align-items: start; }
.lead-solo-copy .t-body { max-width: 58ch; }
.point-icon { width: 22px; height: 22px; display: block; margin: 0 0 14px; color: var(--accent); }
/* Coaching, after granola.ai's "How it works": the heading, then four cards
   in a 2x2 grid. Each card is a clay model of its point in a well on the
   panel ground (which stays paper in both registers, like the AI card's art),
   then the point and its paragraph. The renders were color-matched to the
   panel ground, and their edges fade into it so no seam shows. */
.lead-coach > .t-heading { margin: 0 auto clamp(20px, 2.5vw, 32px); text-align: center; }   /* on the axis of the centred section opener above, like a case study's row openers */
.lead-coach + .lead-solo { margin-top: var(--row); }
.lead-solo + .lead-coach { margin-top: var(--row); }
.lead-coach-head { margin: 0 0 clamp(20px, 2.5vw, 32px); text-align: center; }
.lead-coach-head > * { margin-inline: auto; }
.lead-coach-head .t-body { max-width: 62ch; }
/* Three layers: three across, as granola.ai lays out its cards. The copy
   column fills the card so each "It shows up as" line sits on the same floor. */
.coach-grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.coach-grid.three .coach-art { aspect-ratio: 16 / 10; }   /* narrower wells: a little more height so the flag clears the top edge */
.coach-copy { flex: 1; display: flex; flex-direction: column; }
.coach-copy .coach-shows { margin-top: auto; padding-top: var(--sp-4); }
.coach-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(16px, 2vw, 24px); }
.coach-card { padding: 0; overflow: hidden; display: flex; flex-direction: column; transition: border-color var(--dur-hover); }
.coach-art { position: relative; overflow: hidden; aspect-ratio: 16 / 9; background: var(--panel); border-bottom: 1px solid var(--hair); }
.coach-art :is(img, video) {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  -webkit-mask-image: radial-gradient(75% 80% at 50% 50%, #000 62%, transparent 100%);
  mask-image: radial-gradient(75% 80% at 50% 50%, #000 62%, transparent 100%);
  transform: translate(var(--px, 0px), var(--py, 0px)) scale(var(--ps, 1));
  transition: transform .9s var(--ease-soft);
}
/* A build clip (main.js, from data-build): the clay model assembling itself.
   It starts on the empty well and ends on the still, so the still hides only
   while the clip is ready to take its place; any failure leaves the still. */
.coach-art video { opacity: 0; }
.coach-art.has-build video { opacity: 1; }
.coach-art.has-build img { visibility: hidden; }
.coach-copy { padding: clamp(22px, 2.6vw, 32px) clamp(22px, 2.8vw, 36px) clamp(28px, 3.4vw, 40px); }
.coach-copy .t-body { max-width: 52ch; }
/* Hover, where there is a pointer: the card's rule darkens and the model leans
   away from the cursor (main.js sets --px/--py), a parallax on the render. */
@media (hover: hover) and (pointer: fine) {
  .coach-card:hover { border-color: var(--hair-2); }
  .coach-card:hover .coach-art :is(img, video) { --ps: 1.05; }
}
.tools { list-style: none; display: flex; flex-wrap: wrap; gap: clamp(18px, 2.4vw, 32px); padding: 0; margin: 26px 0 0; }
.tools li { display: flex; align-items: center; gap: 10px; }
.tools img { width: 28px; height: 28px; object-fit: contain; }
.tools img[src*="figma"] { width: auto; }
@media (max-width: 560px) {
  .tools { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4); }
  .tools li { flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
}   /* a tall glyph: its box is its own width, so the label sits as close as the others' */
/* AI in the process: the copy leads and an abstract on the right fills the card's
   height beside it; the tools row closes the column under a hairline. At one column
   the art moves above the copy as a short cover so the copy stays near the top. */
.lead-art { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); align-items: stretch; }
.lead-art-panel { position: relative; overflow: hidden; margin: 0; border-radius: var(--radius-sm); min-height: 300px; background: linear-gradient(135deg, #F4EFE5, #E4DAC6); }
.lead-art-panel img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* the still SVG is the ground truth; the canvas takes over once main.js is running the strands */
.lead-art-panel.live img { visibility: hidden; }
.art-ctl { position: absolute; right: 12px; bottom: 12px; width: 36px; height: 36px; padding: 0; border: 1px solid var(--hair-2); border-radius: var(--radius-btn); background: color-mix(in srgb, var(--surface) 55%, transparent); color: var(--ink); cursor: pointer; display: grid; place-items: center; transition: border-color var(--dur-hover), color var(--dur-hover); }
.art-ctl:hover { border-color: var(--accent); color: var(--accent-ink); }
.panel .art-ctl, .lead-art-panel .art-ctl { background: color-mix(in srgb, var(--plate-surface) 55%, transparent); border-color: var(--plate-hair-2); color: var(--plate-ink-0); }   /* on a plate, which stays paper in both registers */
.art-ctl svg { width: 14px; height: 14px; }
.art-ctl .i-pause, .art-ctl[aria-pressed="true"] .i-play { display: block; }
.art-ctl .i-play, .art-ctl[aria-pressed="true"] .i-pause { display: none; }
/* ---- AI in the process: the card's copy as a process, after ramp.com's feature cards.
   One step on screen at a time: the brief the agent picks up, the agent's work
   on a real object, the team's review. Between steps the view travels along a
   dotted line that stays behind, so the steps read as one flow. The scene is
   a fixed size, scaled to fit the room the pause button leaves. Plate colors
   only: the panel stays sand in both registers. ---- */
.story { position: absolute; inset: 0; overflow: hidden; font-family: var(--font-body); color: #14100C; }
.st-scene { position: absolute; transform-origin: 0 0; }
.story[data-layout="tall"] .st-scene { width: 296px; height: 272px; }
.story[data-layout="wide"] .st-scene { width: 296px; height: 272px; }
.st-ex { position: absolute; inset: 0; opacity: 0; transition: opacity 600ms cubic-bezier(.16, 1, .3, 1); perspective: 1200px; }
.st-ex.on { opacity: 1; }
.st-track { position: absolute; left: 0; top: 0; transition: transform 800ms cubic-bezier(.65, 0, .25, 1); }

/* the dotted line: round dots, drawn in as the view moves on */
.st-line { position: absolute; transform-origin: 0 0; transition: transform 800ms cubic-bezier(.65, 0, .25, 1); }
.story[data-layout="tall"] .st-line { width: 4px; margin-left: -2px; background: radial-gradient(circle, rgba(20, 16, 12, .38) 1.3px, transparent 1.8px) center top / 4px 10px repeat-y; transform: scaleY(0); }
.story[data-layout="wide"] .st-line { height: 4px; margin-top: -2px; background: radial-gradient(circle, rgba(20, 16, 12, .38) 1.3px, transparent 1.8px) left center / 10px 4px repeat-x; transform: scaleX(0); }
.story[data-layout="tall"] .st-line.on { transform: scaleY(1); }
.story[data-layout="wide"] .st-line.on { transform: scaleX(1); }
.st-line.stub { transform: none !important; }
.story[data-layout="tall"] .st-line.stub { -webkit-mask: linear-gradient(var(--dir, 180deg), transparent, #000); mask: linear-gradient(var(--dir, 180deg), transparent, #000); }
.story[data-layout="wide"] .st-line.stub { -webkit-mask: linear-gradient(var(--dir, 90deg), transparent, #000); mask: linear-gradient(var(--dir, 90deg), transparent, #000); }
/* the work passing along the line */
.st-pkt { position: absolute; width: 8px; height: 8px; margin: -4px 0 0 -4px; border-radius: 50%; background: #14100C; opacity: 0; box-shadow: 0 0 0 4px rgba(20, 16, 12, .08); }
.st-pkt.go { animation: st-pkt 800ms cubic-bezier(.65, 0, .25, 1) both; }
.story[data-layout="tall"] .st-pkt.go { --dx: 0px; --dy: var(--gap); }
.story[data-layout="wide"] .st-pkt.go { --dx: var(--gap); --dy: 0px; }
@keyframes st-pkt { 0% { opacity: 0; transform: none; } 15%, 80% { opacity: 1; } 100% { opacity: 0; transform: translate(var(--dx), var(--dy)); } }

/* a step: alone in the frame, arriving out of a slight tilt and blur */
.st-step { position: absolute; width: max-content; transform: translate(-50%, -50%); }   /* its own width, so the -50% centers it in every browser */
.st-card { opacity: 0; transform: translate3d(0, 18px, 0) rotateX(14deg) scale(.95); filter: blur(6px);
  transition: opacity 600ms cubic-bezier(.16, 1, .3, 1), transform 750ms cubic-bezier(.16, 1, .3, 1), filter 500ms cubic-bezier(.16, 1, .3, 1); }
.st-step.on .st-card { opacity: 1; transform: none; filter: blur(0); transition-delay: 250ms; }
.st-step.past .st-card { opacity: 0; transform: translate3d(0, -14px, 0) scale(.97); filter: blur(4px); transition-duration: 400ms; transition-delay: 0ms; }
.story[data-layout="wide"] .st-step.past .st-card { transform: translate3d(-14px, 0, 0) scale(.97); }

.st-card { background: #FFFFFF; border-radius: 10px; border: 1px solid rgba(20, 16, 12, .1);
  box-shadow: 0 1px 1px rgba(20, 16, 12, .04), 0 2px 4px rgba(20, 16, 12, .04), 0 10px 20px -12px rgba(20, 16, 12, .22); }   /* crisp, not puffy: a tight contact shadow and a short drop */
/* ---- Realistic app windows, after Harvey's product UI (title bar, breadcrumb, an agent run as a
   step list with spinners and checks), Mercury's white space and Robinhood's crisp states. ---- */
.st-app { width: 288px; background: #FFFFFF; border-radius: 10px; border: 1px solid rgba(20, 16, 12, .1); overflow: hidden; font-size: 12.5px; line-height: 1.4; color: #14100C;
  box-shadow: 0 1px 1px rgba(20, 16, 12, .04), 0 2px 4px rgba(20, 16, 12, .04), 0 12px 24px -14px rgba(20, 16, 12, .24); }
.st-tb { display: flex; align-items: center; gap: 8px; height: 34px; padding: 0 12px; border-bottom: 1px solid #EFEBE4; background: #FBFAF8; color: #6F675D; font-size: 11.5px; white-space: nowrap; }
.st-tb .lg { width: 16px; height: 16px; border-radius: 4px; background: #14100C; display: grid; place-items: center; flex: none; }
.st-tb .lg svg { width: 10px; height: 10px; color: #F6F4F0; }
.st-tb b { color: #14100C; font-weight: 500; }
.st-tb .sep { color: #CFC9BF; }
.st-tb .rt { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; }
.st-bd { padding: 14px 14px 12px; }
.st-lbl { font-size: 11px; color: #6F675D; margin-bottom: 4px; }
.st-txt { font-size: 13.5px; font-weight: 500; letter-spacing: -.005em; line-height: 1.35; }
.st-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
.st-chip { display: inline-flex; align-items: center; gap: 5px; height: 22px; padding: 0 8px; border-radius: 6px; border: 1px solid #E6E1D8; background: #FFFFFF; font-size: 11.5px; color: #3E3831; }
.st-chip svg { width: 11px; height: 11px; color: #6F675D; }
.st-ft { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-top: 1px solid #EFEBE4; font-size: 12px; color: #5C564E; }
.st-av { width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center; font-size: 9.5px; font-weight: 600; flex: none; background: #14100C; color: #F6F4F0; }
.st-av.lt { background: #E9E4DB; color: #14100C; }
.st-btn { margin-left: auto; white-space: nowrap; display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; border-radius: 6px; font-size: 12px; font-weight: 500; background: #14100C; color: #F6F4F0; }
.st-btn.gh { margin-left: auto; background: #FFFFFF; color: #14100C; border: 1px solid #E0DAD0; }
.st-btn.gh + .st-btn { margin-left: 0; }
.st-btn.ok { background: #3B6B44; }
.st-kbd { font-family: var(--font-mono); font-size: 10px; padding: 1px 4px; border-radius: 3px; background: rgba(246, 244, 240, .18); }
/* the run: status in the title bar, then a list of steps */
.st-state { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: #14100C; }
.st-spin { width: 11px; height: 11px; border-radius: 50%; border: 1.5px solid #D9D3C9; border-top-color: #14100C; animation: st-spin 900ms linear infinite; }
@keyframes st-spin { to { transform: rotate(360deg); } }
.st-okdot { width: 11px; height: 11px; border-radius: 50%; background: #3B6B44; display: none; }
.done .st-state .st-spin { display: none; }
.done .st-state .st-okdot { display: inline-block; }
.st-steps { list-style: none; margin: 0; padding: 6px 0; }
.st-steps li { display: flex; align-items: center; gap: 10px; padding: 6px 14px; color: #A39B90; transition: color 300ms; }
.st-steps li .ic { width: 14px; height: 14px; flex: none; position: relative; }
.st-steps li .ic::before { content: ""; position: absolute; inset: 1px; border-radius: 50%; border: 1.5px solid #D9D3C9; }
.st-steps li.act { color: #14100C; }
.st-steps li.act .ic::before { border-color: #D9D3C9; border-top-color: #14100C; animation: st-spin 900ms linear infinite; }
.st-steps li.ok { color: #14100C; }
.st-steps li.ok .ic::before { border: 0; background: #3B6B44 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 6.3l2 2 4-4.3'/%3E%3C/svg%3E") center / 10px no-repeat; }
.st-steps li .m { margin-left: auto; font-family: var(--font-mono); font-size: 11px; color: #A39B90; }
.st-steps li.ok .m.bad { color: #9A4430; }
/* what the run produced: arrives when it is done */
.st-out { margin: 0 14px 12px; border-radius: 8px; border: 1px solid #EFEBE4; overflow: hidden; opacity: .35; filter: saturate(0); transition: opacity 600ms, filter 600ms; }
.done .st-out { opacity: 1; filter: none; }
.st-diff { font-family: var(--font-mono); font-size: 11px; line-height: 1.7; }
.st-diff div { padding: 1px 10px; white-space: nowrap; }
.st-diff .dl { background: #FBEFEA; color: #8A3B28; }
.st-diff .ad { background: #EAF2EA; color: #2B5233; }
.st-meta { display: flex; align-items: center; gap: 8px; padding: 7px 10px; background: #FBFAF8; border-top: 1px solid #EFEBE4; font-size: 11.5px; color: #5C564E; }
.st-tag2 { margin-left: auto; font-size: 11px; font-weight: 500; padding: 2px 8px; border-radius: 999px; background: #E3EDE3; color: #2B5233; }
.st-theme2 { padding: 9px 10px; font-size: 12.5px; font-weight: 500; }
.st-quote { margin-top: 10px; padding: 9px 11px; border-left: 2px solid #3B6B44; background: #F8F6F1; border-radius: 0 6px 6px 0; font-size: 12px; color: #3E3831; line-height: 1.45; }
.st-quote span { display: block; margin-top: 4px; font-size: 11px; color: #6F675D; }
/* a pause, or scrolling past, stops the spinners where they are */
.lead-art-panel:not(.run) .story * { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .story * { transition: none !important; animation: none !important; } }
/* Wordmarks are drawn in their brands' inks for paper; in the dark register
   they go to one light tone so they stay legible on the ground. The tool
   icons keep their own colours (Cursor's is a tile, Figma's is in colour),
   all but ChatGPT's, which is a black mark. */
:root[data-theme="dark"] :is(.case-logo, .exp-label img, .kudo-mark, .tools img[src*="openai"]) { filter: brightness(0) invert(.9); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) :is(.case-logo, .exp-label img, .kudo-mark, .tools img[src*="openai"]) { filter: brightness(0) invert(.9); } }
/* The theme switch (main.js initTheme puts it in the footer): System, Light, Dark. */
.theme { display: inline-flex; align-items: center; gap: var(--sp-3); }
.theme .seg-tab { padding: 5px 11px; }
.lead-art .tools { margin-top: var(--sp-6); padding-top: var(--sp-5); border-top: 1px solid var(--hair); }
@media (max-width: 900px) {
  .lead-solo { grid-template-columns: minmax(0, 1fr); }
  .lead-art-panel { order: -1; min-height: 360px; }   /* tall enough on a phone for the card to read (an aspect ratio with it would widen the page) */
}
@media (max-width: 900px) { .coach-grid.three { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 760px) { .coach-grid { grid-template-columns: minmax(0, 1fr); } }

/* Three layers of design tracking: one card like the AI card, mirrored. Figure A
   on the left, three nested rings on the panel ground; on the right the copy,
   then a tab strip that picks which layer is showing and lights its ring. The
   rings share a bottom point rather than a centre so each has a tall band at
   the top for its label. Panels are stacked in one grid cell so the block
   keeps the height of the tallest and never jumps. Each layer closes with its
   signal as the last line of the same paragraph flow ("It shows up as a design
   decision"), the value itself lit like marker ink instead of set apart in its
   own band or tagged with a pill. */
.lead-fig { align-items: stretch; }
.lead-fig { grid-template-columns: minmax(0, 9fr) minmax(0, 14fr); }   /* the strip needs the width */
.lead-fig .layer-tabs { margin-top: var(--sp-6); }
/* each tab carries its layer's icon, lit in the accent when selected */
.lead-fig .layer-tab { display: inline-flex; align-items: center; gap: 6px; padding-inline: 12px; }
.lead-fig .layer-tab:first-child { padding-left: 2px; }
.lead-fig .layer-tab svg { width: 16px; height: 16px; flex: none; }
.lead-fig .layer-tab[aria-selected="true"] svg { color: var(--accent); }
.lead-fig .layer-stage { margin-top: 0; }
.lead-fig .layer { background: none; border: 0; border-radius: 0; box-shadow: none; }
.lead-fig .layer-head { padding: clamp(24px, 3vw, 32px) 0 0; }
/* The panel is a size container, so the figure sizes to whichever of the
   panel's width and height is tighter, and the copy column sets the height.
   The caption keeps a band at the bottom (the extra padding). */
.fig-panel { --fig-pad: clamp(20px, 2.6vw, 32px); container-type: size; display: grid; place-items: center; padding: var(--fig-pad) var(--fig-pad) calc(var(--fig-pad) + 60px); border-radius: var(--radius-sm); }
.fig-nest { position: relative; width: min(100%, 460px); width: min(100cqw, 100cqh, 460px); aspect-ratio: 1; container-type: inline-size; }   /* its own container, so the ring names can size to it */
.nest { position: absolute; border-radius: 50%; border: 1px solid color-mix(in srgb, var(--plate-ink-0) 34%, transparent); transition: border-color var(--dur-ui), box-shadow var(--dur-ui), background var(--dur-ui); }
.nest[data-ring="2"] { inset: 0; }
.nest[data-ring="1"] { left: 13%; top: 26%; width: 74%; height: 74%; }
.nest[data-ring="0"] { left: 30%; top: 60%; width: 40%; height: 40%; }
/* each label sits in the lower half of its ring's band, where the chord is
   wide, with a clear 6% of the figure between it and the next ring below;
   the innermost is centred in its disc. The names are sized off the figure
   (6cqw, never above their role or under 12px), so a figure that shrinks to
   fit a shorter copy column still keeps them inside their rings with room
   around them: a 267px figure sets them at 16px, a full 460px one at 20px. */
.nlbl { position: absolute; left: 50%; transform: translate(-50%, -100%); text-align: center; white-space: nowrap;
  font-size: clamp(12px, 6cqw, var(--t-subhead)); }
.nest[data-ring="2"] .nlbl { top: 22%; }
.nest[data-ring="1"] .nlbl { top: 35.8%; }
.nest[data-ring="0"] .nlbl { top: 50%; transform: translate(-50%, -50%); white-space: normal; width: 8ch; }
.nest .t-subhead { font-size: 1em; color: var(--plate-ink); transition: color var(--dur-ui); }   /* the figure sits on a plate, so its inks are the plate's in both registers */
.nest.hover, .nest.hover .t-subhead { border-color: var(--plate-ink-0); color: var(--plate-ink-0); }   /* the ring under the pointer; main.js hit-tests the circle */
.nest.on { border-color: var(--accent); box-shadow: 0 0 0 8px var(--glow);
  background: color-mix(in srgb, var(--plate-surface) 72%, transparent); }   /* the picked ring lifts to a lighter disc so it reads off the ground and the two it shares */
.nest.on .t-subhead { color: var(--plate-ink-0); }
.fig-cap { position: absolute; left: var(--fig-pad); bottom: var(--fig-pad); display: flex; flex-direction: column; gap: 2px; }
.fig-cap .eyebrow { color: var(--plate-ink-2); }
.fig-cap .t-small { color: var(--plate-ink); }
@media (max-width: 360px) {
  .fig-panel { padding-bottom: var(--fig-pad); }
  .fig-cap { position: static; margin-top: var(--sp-4); justify-self: start; }
}
/* Figure A in motion is one wave: influence travelling the chain. Every ring
   is the outer one scaled about the point they share at the bottom, so main.js
   can send a single circle (.nest-wave, a non-scaling stroke) from one ring's
   shape to the next. As the card arrives the Design quality disc springs in,
   the wave leaves it, each ring appears and flexes as the wave reaches it, and
   the wave spills past Business outcome and dissolves. Picking a layer sends
   the same wave out (cause to effect) or back in (tracing a result to its
   decision), and the ring lights as it lands. Staged hidden until the card
   is in view; under reduced motion or without JS the figure is still. */
.fig-nest.is-staged .nest { opacity: 0; }
.nest.pass { border-color: var(--accent); }   /* a ring the wave crosses on its way to another */
.nest-wave { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.nest-wave circle { fill: none; stroke: var(--accent); stroke-width: 1.5; opacity: 0; transform-box: view-box; transform-origin: 50% 100%; }
/* The layer's signal draws on like a marker stroke, landing with the wave */
.lead-fig.is-played .layer.active .layer-mark { background: linear-gradient(var(--glow), var(--glow)) no-repeat 0 0 / 100% 100%;
  animation: mark-ink .45s var(--ease-soft) var(--mark-delay, 0ms) both; }
.lead-fig.instant .layer-mark { animation: none; }
@keyframes mark-ink { from { background-size: 0% 100%; } }
/* Each tab carries its own slice of the track, and there is no gap between
   them, so the track reads as one line and still works if the strip wraps. */
.layer-tabs { position: relative; display: flex; flex-wrap: wrap; box-shadow: inset 0 -1px var(--hair-2); }   /* the rule runs the column's width; each tab's own border draws over it */
.layer-tab {
  appearance: none; background: none; border: 0; cursor: pointer;
  padding: 8px clamp(12px, 1.6vw, 18px) 14px; white-space: nowrap;
  transition: color var(--dur-hover), transform var(--dur-press) var(--ease);
  border-bottom: 1px solid var(--hair-2);
}
.layer-tab:first-child { padding-left: 2px; }
.layer-tab:hover, .layer-tab[aria-selected="true"] { color: var(--ink); }
.layer-tab:focus-visible { outline-offset: -2px; }
.layer-tabs::after {
  content: ""; position: absolute; left: 0; top: 0; height: 2px;
  width: 1px; transform-origin: left;
  transform: translate(var(--tab-x, 0px), var(--tab-y, 0px)) scaleX(var(--tab-w, 0));
  background: var(--ink); border-radius: 2px;
  transition: transform .3s var(--ease-in-out);   /* width is drawn with scaleX, so the slide never touches layout */
}
.layer-stage { display: grid; margin-top: 22px; }
.layer {
  grid-area: 1 / 1;
  background: var(--surface); border: 1px solid var(--hair); border-radius: var(--radius);
  box-shadow: 0 10px 26px -22px rgba(20, 16, 12, .5);
  opacity: 0; visibility: hidden; transform: translateY(6px);
  transition: opacity .15s var(--ease), transform .15s var(--ease), visibility 0s .15s;
}
/* the outgoing card leaves fast, the incoming one arrives just behind it */
.layer.active { opacity: 1; visibility: visible; transform: none; transition: opacity var(--dur-ui) var(--ease) 60ms, transform var(--dur-ui) var(--ease) 60ms, visibility 0s; }
/* arrow keys are repeated; a keyboard move swaps instantly and the underline just lands */
[data-tabs].instant .layer, [data-tabs].instant .layer-tabs::after { transition-duration: 0s; transition-delay: 0s; }
.layer-head { padding: clamp(24px, 3.2vw, 40px); }
/* the signal's value sits inside its closing sentence, lit like a line of
   marker ink rather than pulled out into its own tag or band */
.layer-mark { background: var(--glow); color: var(--accent-ink); padding: .05em .35em; border-radius: 4px; box-decoration-break: clone; -webkit-box-decoration-break: clone; }
/* Under 1100px the card goes to one column early (the tab strip needs the
   width): the figure becomes a cover above the copy, sized by its width alone. */
@media (max-width: 1100px) {
  .lead-fig { grid-template-columns: minmax(0, 1fr); }
  .fig-panel { container-type: inline-size; }
  .fig-nest { width: min(100cqw, 460px); }
}

/* About: portrait beside the heading and the name. The portrait takes 5 of 24
   parts at least; main.js measures the text and sets --portrait-w at the photo's
   own ratio, so a taller text widens it to stand as tall. Since the heading became
   one word the text is shorter than the photo, which then runs past it. Items align
   to the top so the photo's edge meets the heading. */
.about-grid { display: grid; grid-template-columns: minmax(var(--portrait-w, 0px), 5fr) minmax(0, 19fr); gap: clamp(32px, 6vw, 88px); align-items: start; }
.portrait { margin: 0; border-radius: 14px; overflow: hidden; background: #DFD9CD; }
.portrait img { width: 100%; height: auto; display: block; }
/* name and title are one lockup, closer than a subhead and its text */
.about-who { margin-top: clamp(28px, 3vw, 40px); }
.about-who .t-body { margin-top: var(--sp-1); }
/* The bio sits under the name and title */
.about-bio { margin-top: clamp(20px, 3vw, 28px); max-width: 60ch; }
.about-bio .t-body + .t-body { margin-top: var(--sp-3); }
.about-bio .nowrap { white-space: nowrap; }   /* pre-Series A stays one term */
/* Experience: four roles on one track, oldest first, under one eyebrow. Each item draws the
   run of track from its own marker to the next (the last runs to the edge),
   and the roles already held draw theirs again in ink on top, so the ink
   ends at the current role. Once the list has faded in, the ink sweeps
   along one segment after another and each marker lands as it arrives,
   all within a second of the list's arrival.
   Without JS or under reduced motion the track is simply drawn. */
.exp { --exp-gap: clamp(24px, 4vw, 56px); --exp-seg: .18s; --exp-wait: 250ms; --sx: 0; --sy: 1;
  list-style: none; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--exp-gap);
  padding: 0; margin: var(--sp-5) 0 0; }
.exp-head { margin-top: clamp(44px, 6vw, 72px); }
.exp-item { position: relative; min-width: 0; display: flex; flex-direction: column; padding-top: 32px; }
.exp-item::before, .exp-item::after { content: ""; position: absolute; top: 4px; left: 0; height: 1px; width: calc(100% + var(--exp-gap)); }
.exp-item::before { background: var(--hair); }
.exp-item:last-child::before { width: 100%; }
.exp-item::after { background: var(--ink); transform-origin: 0 0; }
.exp-item:last-child::after { display: none; }
.exp-mark { position: absolute; top: 0; left: 0; width: 9px; height: 9px; border-radius: 2px; background: var(--ink); z-index: 1; }
.exp-item:last-child .exp-mark { background: var(--accent); }   /* the role held now */
.anim .exp .exp-item::after { transform: scale(var(--sx), var(--sy)); }
.anim .exp .exp-mark { transform: scale(0); }
.anim .exp.in .exp-item::after { animation: exp-sweep var(--exp-seg) var(--ease-in-out) var(--exp-wait) both; }
.anim .exp.in .exp-item:nth-child(2)::after { animation-delay: calc(var(--exp-wait) + var(--exp-seg)); }
.anim .exp.in .exp-item:nth-child(3)::after { animation-delay: calc(var(--exp-wait) + 2 * var(--exp-seg)); }
.anim .exp.in .exp-mark { animation: exp-land var(--dur-hover) var(--ease) var(--exp-wait) both; }
.anim .exp.in .exp-item:nth-child(2) .exp-mark { animation-delay: calc(var(--exp-wait) + var(--exp-seg)); }
.anim .exp.in .exp-item:nth-child(3) .exp-mark { animation-delay: calc(var(--exp-wait) + 2 * var(--exp-seg)); }
.anim .exp.in .exp-item:nth-child(4) .exp-mark { animation-delay: calc(var(--exp-wait) + 3 * var(--exp-seg)); }
@keyframes exp-sweep { from { transform: scale(var(--sx), var(--sy)); } to { transform: none; } }
@keyframes exp-land { from { transform: scale(0); } to { transform: none; } }
/* the label is the company's own lockup, standing on the ground; the row is
   the tallest lockup's height so the titles below line up across all four */
.exp-label { display: flex; align-items: flex-end; height: 29px; margin: 0 0 var(--sp-5); }
.exp-label img { width: auto; max-width: 100%; object-fit: contain; object-position: left bottom; }   /* a narrow column shrinks a lockup, never squeezes it */
/* official vector lockups, sized so the wordmark text matches across the row;
   the Charm lockup is the widest, so it gives way to its column before the
   others do rather than run under the next one */
.exp-label img[alt="Best Egg"] { height: 25px; margin-bottom: -3px; }   /* its g descends, so the baseline lands with the others' */
.exp-label img[alt="Chainlink Labs"] { height: 29px; }
.exp-label img[alt="Auth0"] { height: 29px; }
.exp-label img[alt="Charm Solutions"] { width: min(226px, 100%); height: auto; }
.exp-item .t-body { margin-top: var(--sp-2); }
@media (min-width: 701px) { .exp-item .t-subhead { min-height: calc(2em * var(--lh-subhead)); } }
@media (min-width: 701px) and (max-width: 900px) { .exp-item .t-subhead { min-height: calc(3em * var(--lh-subhead)); } }   /* a title that wraps doesn't push its column's lines below the others' */
.exp-dates { margin-top: auto; padding-top: var(--sp-5); }
.exp-dates time { white-space: nowrap; }
/* Senior-leader and peer quotes under the experience track: the case-study
   testimonial carousel (one quote at a time, cross-fading, the same control
   strip) with the company mark centred above the quote and the source line
   under it in place of a person, who is left out by John's choice. */
.kudos { margin-top: clamp(48px, 6vw, 80px); }
.kudo-mark { height: 22px; width: auto; margin-bottom: clamp(24px, 3vw, 40px); }
.kudos .testimonial .quote { flex: none; padding-block: 0; border-bottom: 0; }   /* no rule and no stretch: the source sits right under the quote, like a byline */
.kudos cite { display: block; margin-top: clamp(20px, 2.5vw, 32px); font-style: normal; }
@media (max-width: 700px) {
  /* the track turns down the left and the markers sit level with each label */
  .exp { --exp-gap: var(--sp-6); --sx: 1; --sy: 0; grid-template-columns: minmax(0, 1fr); }
  .exp-item { padding: 0 0 0 28px; }
  .exp-item::before, .exp-item::after { top: 15px; left: 4px; width: 1px; height: calc(100% + var(--exp-gap)); }
  /* the last item's track is 1px here too: the desktop rule above it is the same
     specificity, so without the width the hairline fills the item as a block */
  .exp-item:last-child::before { width: 1px; height: calc(100% - 15px); }
  .exp-mark { top: 10px; }
  .exp-dates { padding-top: var(--sp-4); }
}
@media (max-width: 900px) { .about-grid { grid-template-columns: 1fr; gap: 32px; } .portrait { max-width: 210px; } }

/* Contact: the one dark block, closing the page */
.contact {
  --ink-2: color-mix(in srgb, var(--paper) 70%, transparent);
  --ink-3: color-mix(in srgb, var(--paper) 56%, transparent);
  --hair-2: color-mix(in srgb, var(--paper) 36%, transparent);
  --contact-origin: 100% 50%;
  position: relative; overflow: hidden; color: var(--paper);
  /* the panel ground in its dark register: a lifted corner fading to ink,
     and rings in paper rippling out from behind the buttons */
  background-color: var(--ink);
  background-image:
    repeating-radial-gradient(circle at var(--contact-origin), var(--contact-rings) 0 1px, transparent 1px 42px),
    var(--contact-ground);
  border-radius: var(--radius); padding: clamp(44px, 7vw, 88px);
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 36px; align-items: center;
}
.contact .t-title { color: var(--paper); max-width: 20ch; }
.contact .t-lede { max-width: 50ch; }
.contact-actions { display: flex; flex-direction: column; gap: 10px; align-items: stretch; }
.contact-actions .btn { justify-content: center; text-align: center; }
.contact .btn-primary { background: var(--paper); color: var(--ink); }
.contact .btn-primary:hover { background: var(--surface); color: var(--ink); box-shadow: none; }
.contact .btn-ghost { border-color: var(--hair-2); color: var(--paper); background: transparent; }
.contact .btn-ghost:hover { border-color: var(--paper); color: var(--paper); }
.contact :focus-visible { outline-color: var(--paper); }   /* the accent ring is only 3.2:1 on ink */
/* live, the rings are drawn on the canvas (same pitch, same tone) and the stylesheet keeps only the ground */
.contact.live { background-image: var(--contact-ground); }
.contact > :not(.field) { position: relative; z-index: 1; }
@media (max-width: 720px) { .contact { --contact-origin: 100% 100%; grid-template-columns: 1fr; } }   /* stacked, the buttons sit at the bottom */
/* The LinkedIn button (after uselayouts.com's get in touch; main.js initTalk
   sets data-phase). On hover or keyboard focus the label lifts away, John's
   portrait and a You circle turn in from either side and meet (meet), then
   the two overlap and "Let’s talk" writes in beside them (talk). It is the
   one control that runs past 300ms, about 700ms end to end, by John's
   choice. Without the script it is the plain button. */
.talk { isolation: isolate; overflow: hidden; --talk-face: 34px; --talk-ring: var(--paper); }
.contact .talk:hover { --talk-ring: var(--surface); }
.talk-label { display: inline-flex; align-items: center; gap: 8px; transition: opacity var(--dur-ui) var(--ease), transform var(--dur-ui) var(--ease), filter var(--dur-ui) var(--ease); }
.talk:is([data-phase="meet"], [data-phase="talk"]) .talk-label { opacity: 0; transform: translateY(-28px); filter: blur(4px); }
.talk-meet { position: absolute; inset: 0; display: flex; align-items: center; padding-left: 5px; opacity: 0; pointer-events: none; transition: opacity var(--dur-ui) var(--ease); }
.talk:is([data-phase="meet"], [data-phase="talk"]) .talk-meet { opacity: 1; }
.talk-pair, .talk-words { flex: none; transform: translateX(var(--talk-x, 0px)); transition: transform 440ms var(--ease-settle); }
.talk-pair { display: flex; align-items: center; }
.talk-face {
  width: var(--talk-face); height: var(--talk-face); flex: none; border-radius: 50%; overflow: hidden; display: grid; place-items: center; opacity: 0;
  transition: transform 380ms var(--ease-settle), opacity var(--dur-ui) var(--ease), margin-left 300ms var(--ease);
}
.talk-me { transform: translateX(-40px) rotate(-180deg); }
.talk-me img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 18%; }
.talk-you {
  position: relative; z-index: 1; transform: translateX(40px) rotate(180deg);
  background: var(--ink); color: var(--paper); box-shadow: 0 0 0 2px var(--talk-ring);
  font-family: var(--font-mono); font-size: 10px; font-weight: 500; letter-spacing: var(--ls-micro); text-transform: uppercase;
}
.talk:is([data-phase="meet"], [data-phase="talk"]) .talk-face { opacity: 1; transform: none; }
.talk[data-phase="meet"] .talk-me { transition-delay: 40ms; }
.talk[data-phase="meet"] .talk-you { transition-delay: 100ms; }
.talk[data-phase="talk"] .talk-you { margin-left: -10px; }
.talk-plus { width: 0; margin: 0; overflow: hidden; text-align: center; opacity: 0; transform: scale(.6); transition: width 260ms var(--ease), margin 260ms var(--ease), transform 260ms var(--ease), opacity var(--dur-ui) var(--ease); }
.talk[data-phase="meet"] .talk-plus { width: 18px; margin: 0 8px; opacity: 1; transform: none; transition-delay: 140ms; }
.talk-words { max-width: 0; margin-left: 0; overflow: hidden; white-space: nowrap; opacity: 0; transition: max-width var(--dur-ui) var(--ease), margin-left var(--dur-ui) var(--ease), opacity var(--dur-ui) var(--ease), transform 440ms var(--ease-settle); }
.talk[data-phase="talk"] .talk-words { max-width: 10em; margin-left: 12px; opacity: 1; transition-delay: 200ms, 200ms, 200ms, 0s; }
footer { padding: var(--sp-7) 0 calc(var(--chat-edge) + 64px); display: flex; justify-content: flex-start; align-items: center; gap: var(--sp-4) var(--sp-5); flex-wrap: wrap; }   /* the bottom clears the chat launcher, which floats over the page's last corner */
footer .theme { margin-left: auto; }
main > .section:last-child { padding-bottom: 0; }   /* the footer inside it ends the page */
@media (max-width: 640px) { footer { padding-bottom: var(--sp-7); } }   /* the launcher sits in the bar here */
footer a { color: var(--ink-2); }
footer a:hover { color: var(--ink); }

/* Arcade: the bonus level under Contact. One button holds the whole game so
   Space, Enter and a tap all mean the same thing; the canvas inside it is a
   pixel grid drawn at a whole-number scale (main.js sets the width) so every
   pixel stays square. Sand ground and hairline as the panels, ink and accent
   for the sprites. */
.arcade { margin-top: clamp(40px, 5vw, 64px); }
.arcade-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 24px; margin-bottom: var(--sp-5); }
.arcade-head .t-small { max-width: 44ch; }
.arcade-score { flex: 0 0 auto; font-variant-numeric: tabular-nums; color: var(--ink); }
.arcade-screen {
  display: block; width: 100%; padding: 0; margin: 0; cursor: pointer;
  border: 1px solid var(--hair); border-radius: var(--radius); overflow: hidden;
  background-color: #EFE8DB;
  background-image: linear-gradient(155deg, #F4EFE5 0%, #E4DAC6 100%);
  touch-action: manipulation;   /* no double-tap zoom while jumping */
  -webkit-tap-highlight-color: transparent;
}
.arcade-screen:focus-visible { outline-offset: 2px; border-radius: var(--radius); }
.arcade-screen canvas { display: block; margin-inline: auto; image-rendering: pixelated; image-rendering: crisp-edges; }
/* After a round: the name for the leaderboard, in the gate's input */
.arcade-post { display: flex; flex-direction: column; gap: 12px; margin-top: var(--sp-5); max-width: 520px; }
.arcade-post-row { display: flex; gap: 12px; }
.arcade-post input { flex: 1 1 auto; min-width: 0; padding: 12px 16px; border-radius: 12px; border: 1px solid var(--hair-2); background: var(--surface-2); color: var(--ink); text-transform: uppercase; }
.arcade-post input::placeholder { text-transform: none; }
.arcade-post input:focus { outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent); }
.arcade-post .btn { flex: 0 0 auto; justify-content: center; }
.arcade-post-note:empty { display: none; }

/* --------------------------------------------------------------------------
   7. Case study
   One skeleton for every project: hero → facts → my role → results → story
   rows → (what I'd do differently) → next. Each part uses only the roles
   above, so every case study reads as a chapter of the same document.
   -------------------------------------------------------------------------- */
.cs-hero { padding-top: calc(var(--nav-h) + clamp(40px, 7vw, 88px)); }
.cs-hero.solo { padding-bottom: clamp(48px, 7vw, 96px); }
.cs-hero-copy { display: flex; flex-direction: column; align-items: center; text-align: center; margin-inline: auto; }
.cs-hero-copy h1 { max-width: 22ch; }
.cs-hero.solo .cs-hero-copy > .t-display:first-child { margin-top: calc(var(--t-micro) * var(--lh-micro) + var(--stack-label)); }   /* no eyebrow above: its room is kept, so the title lands where the blog's does */

/* Breadcrumb: where the page sits, Home / Work / this study. The links run
   in ink-2 and darken to the accent on hover; the current page is plain ink
   and not a link; the slash between items is hidden from screen readers.
   The trail keeps to one line and the current page's name clips with an
   ellipsis, since the full title sits right under it. */
.cs-hero .crumbs { margin-bottom: clamp(28px, 4vw, 44px); }
.crumbs ol { display: flex; list-style: none; margin: 0; padding: 0; }
.crumbs li { flex: none; white-space: nowrap; }
.crumbs li:last-child { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.crumbs a { transition: color var(--dur-hover); }
.crumbs a:hover { color: var(--accent-ink); }
.crumb-sep { margin-inline: 8px; }

/* Hero panel: the project's screens on its own ground, full content width */
.hero-panel { margin-top: clamp(40px, 5vw, 64px); min-height: clamp(320px, 40vw, 600px); padding: clamp(28px, 5vw, 64px); }
.hero-panel img { max-height: min(620px, 62vh); }
.hero-panel.two img { max-width: 40%; }
.hero-panel.three img { max-width: 30%; }
.case-no-code .hero-panel { min-height: 0; padding: clamp(14px, 2.6vw, 32px); }   /* the window is the subject: thin ground, full width */
.case-no-code .hero-panel img { width: 100%; max-height: none; }
@media (max-width: 600px) {
  .hero-panel { min-height: 0; }
  .hero-panel.two img { max-width: 46%; max-height: 420px; }
}

/* Hero lead: the outcome sits beside the title as a stat (or a one-line statement
   where a project has no published number), and the facts run as a stat strip below the screens. */
.hero-lead { display: grid; grid-template-columns: minmax(0, 8fr) minmax(0, 4fr); gap: clamp(32px, 5vw, 80px); align-items: end; }
.hero-lead .cs-hero-copy { align-items: flex-start; text-align: left; margin-inline: 0; }
.hero-lead .cs-hero-copy h1 { max-width: 16ch; }
.outcome-stat { padding-left: var(--col-pad); border-left: 1px solid var(--hair-2); }
.outcome-stat .t-heading { max-width: 18ch; }
.outcome-stat .t-small { max-width: 28ch; }
.outcome-stat > div + div { margin-top: var(--sp-5); padding-top: var(--sp-5); border-top: 1px solid var(--hair); }   /* two products, one stat each (Cross-Sell) */
/* Facts: a stat strip under the screens. A label above, the value beneath at
   heading size, cells divided by hairlines, after the Mercury stat band. */
.facts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: clamp(28px, 4vw, 48px) 0 0; padding: 0; border-top: 1px solid var(--hair); border-left: 1px solid var(--hair); }
.facts > div { min-width: 0; padding: clamp(22px, 3vw, 32px) clamp(20px, 3vw, 36px); border-right: 1px solid var(--hair); border-bottom: 1px solid var(--hair); }
.facts dd { margin-top: var(--sp-3); }
.facts + .proof { margin-top: var(--sp-6); }   /* Cross-Sell Results: the product split above the chart */
@media (max-width: 1100px) { .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }   /* four columns leave a value 170px at 1024 */
@media (max-width: 480px) { .facts { grid-template-columns: 1fr; } }

/* Ledger: My role as hairline rows, heading left and paragraph right */
.ledger { border-top: 1px solid var(--hair); }
.ledger-row { display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 8fr); gap: clamp(20px, 4vw, 64px); padding: clamp(24px, 3.5vw, 40px) 0; border-bottom: 1px solid var(--hair); align-items: baseline; }
.ledger-row .t-heading { max-width: 14ch; }
.ledger-row .t-body { margin-top: 0; max-width: 62ch; }   /* beside the heading, not under it */

/* Note: What I'd do differently as one narrow column, labels run into the sentence */
.note { max-width: 62ch; margin-inline: auto; }
.note .t-body + .t-body { margin-top: var(--sp-5); padding-top: var(--sp-5); border-top: 1px solid var(--hair); }

@media (max-width: 900px) {
  .hero-lead { grid-template-columns: minmax(0, 1fr); }
  .outcome-stat { padding-left: 0; border-left: 0; padding-top: var(--sp-5); border-top: 1px solid var(--hair-2); }
  .ledger-row { grid-template-columns: 1fr; gap: var(--sp-3); }
  .ledger-row .t-heading { max-width: none; }
}

/* Sections and rows */
.cs-section { padding-top: var(--section); scroll-margin-top: calc(var(--nav-h) + 24px); }
.section[id] { scroll-margin-top: calc(var(--nav-h) - var(--section) + var(--sp-5)); }   /* a jump lands with the section's heading clear of the bar, whatever its padding */
.cs-section .row + .row { margin-top: var(--row); }

/* A row: copy beside a visual panel, the panel a fixed height so every row in
   the story has the same rhythm. .flip puts the visual on the left. */
.row { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: clamp(32px, 5vw, 80px); align-items: center; }
.row.flip { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
.row.flip > .row-copy { order: 2; }
.row-copy { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
.row-copy .t-heading { margin-top: var(--sp-3); }
.row-copy .t-title { margin-top: var(--stack-label); }
.row-copy .t-body { margin-top: var(--sp-4); }
.row-copy > :first-child { margin-top: 0; }   /* those margins are the gap under an eyebrow or heading; whatever opens the block owns no gap. Last so it wins on equal specificity: the problem row opens on its body. */
.row .panel { height: var(--panel-h); }
.row .panel img { max-height: 100%; }
.row .panel.crop { padding: var(--panel-pad) 0 0 var(--panel-pad); align-items: flex-start; justify-content: flex-start; }
.row .panel.crop img { width: 100%; height: 100%; max-width: none; object-fit: cover; object-position: var(--crop-pos, 0 0); border-radius: 10px 0 0 0; }
/* .panel.dock: a tall phone screen whose top sits inside the ground and whose
   rest runs off the bottom edge, so it reads as the phone continuing past the
   panel. The screen carries its own keyed corners, so it takes the cutout shadow. */
.row .panel.dock { align-items: flex-start; padding-bottom: 0; }
.row .panel.dock img { width: var(--dock-w, 78%); max-width: none; max-height: none; height: auto; border-radius: 0; box-shadow: none; filter: var(--shadow-cutout); }
/* .panel.cutout holds one UI card cut out with transparent corners, floating on the ground */
.row .panel.cutout img { border-radius: 0; box-shadow: none; filter: var(--shadow-cutout); }

/* A full row: copy centred above a visual that needs the whole width */
.row.full { grid-template-columns: minmax(0, 1fr); gap: clamp(28px, 4vw, 44px); }
.row.full > .row-copy { align-items: center; text-align: center; margin-inline: auto; }
.row.full .row-copy > * { margin-inline: auto; }
.row.full .panel { height: auto; aspect-ratio: auto; } /* the visual sets the height, not --panel-h */
.row.full .cols { margin-top: clamp(12px, 2vw, 24px); text-align: left; }
@media (max-width: 820px) {
  .row, .row.flip { grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); }   /* minmax(0): a swipe strip must not widen the column */
  .row.flip > .row-copy { order: 0; }
  .row.media-first > .panel { order: -1; }   /* stacked, the visual leads: breaks up copy that follows a text-only section */
  .row .panel { height: auto; aspect-ratio: 4 / 3; }
}

/* Tensions: the three forces in a problem, a label and a line each */
.tensions { list-style: none; margin: var(--sp-6) 0 0; padding: 0; border-top: 1px solid var(--hair); width: 100%; }
.tensions li { padding: var(--sp-4) 0; border-bottom: 1px solid var(--hair); display: grid; grid-template-columns: minmax(0, 7.5rem) minmax(0, 1fr); gap: var(--sp-4); align-items: baseline; }
@media (max-width: 560px) { .tensions li, .proof .tensions li { grid-template-columns: minmax(0, 1fr); gap: 4px; } }
/* .stack: heading over text in one column, for items whose heading is a phrase rather than a word */
.tensions.stack li { grid-template-columns: minmax(0, 1fr); gap: 0; padding: var(--sp-5) 0; }

/* Quote: a pull quote, the text alone; the testimonial below adds the person */
.quote { margin: 0; }

/* Testimonial carousel: one piece of feedback at a time, set large, with the
   person underneath. Slides share a grid cell so the block is as tall as the
   longest quote and nothing below it jumps as they cross-fade. */
.testimonials { width: 100%; margin-top: var(--sp-6); }
.row.full > .testimonials { margin-top: 0; }   /* the row's own gap is the space */
/* Feedback sits on the same white card as the AI block, so a quote reads as a
   thing someone handed over rather than more page. */
.testimonials, .kudos { background: var(--surface); border: 1px solid var(--hair); border-radius: var(--radius); padding: var(--panel-pad); }
.testimonial-track { display: grid; }
.testimonial { grid-area: 1 / 1; display: flex; flex-direction: column; align-items: center; text-align: center; opacity: 0; visibility: hidden; filter: blur(3px); transition: opacity .3s var(--ease), filter .3s var(--ease), visibility 0s .3s; }
/* a touch of blur while the quotes cross so the eye reads one thing changing, not two overlapping */
.testimonial.active { opacity: 1; visibility: visible; filter: none; transition: opacity .4s var(--ease), filter .4s var(--ease), visibility 0s; }
.testimonial .quote { flex: 1; width: 100%; padding-block: var(--sp-5) var(--sp-7); border-bottom: 1px solid var(--hair-2); }   /* the quote sits off its heading and off its own rule, not hard against either */
.testimonial .quote p { margin-inline: auto; }
.testimonial-foot { display: flex; align-items: center; justify-content: center; gap: var(--sp-4); padding-top: var(--sp-5); text-align: left; }
.testimonial-foot .avatar { flex: none; width: 48px; height: 48px; border-radius: 12px; display: grid; place-items: center; background: var(--paper-2); }
.testimonial-foot .avatar.glyph { padding-top: .35em; }   /* the quote mark hangs high in its em box; this centres it optically */
.testimonial-foot cite { display: block; min-width: 0; font-style: normal; }
.testimonial-foot cite b { display: block; }
.testimonial-mark { margin-left: var(--sp-3); padding-left: var(--sp-4); border-left: 1px solid var(--hair-2); white-space: nowrap; }
.testimonial-controls { display: flex; align-items: center; justify-content: center; gap: var(--sp-3); margin-top: var(--sp-5); }
.testimonial-controls .dots { display: flex; margin-inline: var(--sp-1); }
/* each dot is a 24px button (WCAG 2.2 target size) drawing an 8px mark; the current one stretches */
.testimonial-controls .dot { position: relative; width: 24px; height: 24px; padding: 0; border: 0; background: none; cursor: pointer; transition: width var(--dur-ui) var(--ease), transform var(--dur-press) var(--ease); }
.testimonial-controls .dot::before { content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 8px; height: 8px; border-radius: 2px; background: var(--hair-2); transition: width var(--dur-ui) var(--ease), background var(--dur-hover); }   /* a square mark, a bar when current: half of --radius-btn, which would round an 8px mark into a dot */
.testimonial-controls .dot[aria-current="true"] { width: 40px; }
.testimonial-controls .dot[aria-current="true"]::before { width: 24px; background: var(--ink); }
.testimonial-controls .ctl { width: 40px; height: 40px; padding: 0; border: 1px solid var(--hair-2); border-radius: var(--radius-btn); background: transparent; color: var(--ink); cursor: pointer; display: grid; place-items: center; transition: transform var(--dur-press) var(--ease), border-color var(--dur-hover), color var(--dur-hover); }
.testimonial-controls .ctl:hover { border-color: var(--accent); color: var(--accent-ink); }
.testimonial-controls .ctl svg { width: 16px; height: 16px; }
.testimonial-controls .pause { margin-left: var(--sp-2); }
.testimonial-controls:has(> .pause)::before { content: ""; flex: none; width: 40px; margin-right: var(--sp-2); }   /* the pause's twin, empty, so the arrows and dots sit on the quote's axis */
.testimonial-controls .ctl { flex: none; }   /* square at any width */
@media (max-width: 560px) {   /* flush left here; the row tightens so it fits from 375px, and below that the pause wraps under it */
  .testimonial-controls:has(> .pause)::before { content: none; }
  .testimonial-controls { flex-wrap: wrap; gap: var(--sp-3) var(--sp-2); }
  .testimonial-controls .dots { margin-inline: 0; }
  .testimonial-controls .pause { margin-left: var(--sp-1); }
}
.testimonial-controls .pause .i-pause, .testimonial-controls .pause[aria-pressed="true"] .i-play { display: block; }
.testimonial-controls .pause .i-play, .testimonial-controls .pause[aria-pressed="true"] .i-pause { display: none; }
/* Rotation keeps running for reduced-motion users too (the CSS above already drops the
   cross-fade); the pause button is the one stop, so it shouldn't itself be inert. */
@media (prefers-reduced-motion: reduce) { .testimonial-controls .dot, .testimonial-controls .dot::before { transition: none; } }

/* Shots: the exact piece of UI a decision refers to, cut from the real screen
   and set on the case study's panel ground so the three read as one set.
    The pieces are cut from the source screens in assets/BE (cs-piece-*.webp). */
/* Three across, icon, heading, copy and shot share four grid rows so the
   shots line up, and the edge columns' shots give up the outer pad they do
   not have, so all three frames are one size. Two across, the third column
   is both odd and last, so it alone gives up a pad. Stacked, nothing to do. */
@media (min-width: 901px) {
  .cols:has(.shot) { grid-template-rows: repeat(4, auto); }
  .cols .col:has(.shot) { display: grid; grid-template-rows: subgrid; grid-row: span 4; align-content: start; }
  .cols .col:has(.shot) .shot { align-self: start; }
  .cols .col:first-child .shot, .cols .col:last-child .shot { width: calc(100% - var(--col-pad)); }
}
@media (max-width: 900px) and (min-width: 561px) { .cols .col:nth-child(odd):last-child .shot { width: calc(100% - var(--col-pad)); } }
.shot { position: relative; margin: var(--sp-5) 0 0; aspect-ratio: 1.7; border-radius: 12px; overflow: hidden; border: 1px solid var(--hair);
  background-color: var(--panel-base); background-image: var(--panel); }
/* The screen sits in from the top-left and runs off the right and bottom, so the frame breaks it. No shadow: the hairline is the edge. */
.shot-frame { position: absolute; left: 8%; top: 10%; width: 100%; height: 100%; overflow: hidden; border-radius: 10px 0 0 0; background: var(--plate-surface); border: 1px solid var(--plate-hair); border-right: 0; border-bottom: 0; }   /* a screen on the plate: the plate's inks in both registers */
.shot-frame img { display: block; width: 100%; height: auto; max-width: none; }

/* Gallery: several screens on one ground */
.gallery { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
.gallery figure { margin: 0; position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; min-width: 0; }
.panel .gallery img { max-height: 720px; } /* outranks .row .panel img */
.gallery figcaption { margin-top: auto; padding-top: var(--sp-4); text-align: center; }   /* pushed to the figure's foot, so a row of captions shares one line */

/* Walkthrough player: poster first, frames on demand */
.walk { position: relative; border-radius: 16px; overflow: hidden; border: 1px solid var(--hair); background: var(--ink); max-width: 932px; margin-inline: auto; width: 100%; }
.walk img { width: 100%; height: auto; display: block; }
/* A scrim marks the poster as a video still, so the control reads as "play" */
.walk::after { content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 1; background: linear-gradient(180deg, rgba(20, 16, 12, .12), rgba(20, 16, 12, .34)); transition: opacity var(--dur-ui) var(--ease); }
.walk .play { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 2; display: inline-flex; align-items: center; gap: 9px; padding: 12px 20px 12px 17px; border: 0; border-radius: var(--radius-btn); white-space: nowrap; background: var(--plate-surface); color: var(--plate-ink-0); box-shadow: 0 10px 34px rgba(20, 16, 12, .34); transition: transform var(--dur-press) var(--ease), background var(--dur-hover), box-shadow var(--dur-hover), opacity var(--dur-ui) var(--ease); }
.walk .play:hover { background: var(--plate-surface); box-shadow: 0 14px 40px rgba(20, 16, 12, .42); }
.walk .play:focus-visible { outline-color: var(--surface); }
.walk.loading .play { cursor: progress; }
.walk.loading .play svg { animation: walk-spin .7s linear infinite; transform-origin: 50% 50%; }
@keyframes walk-spin { to { transform: rotate(360deg); } }
.walk .play svg { width: 13px; height: 13px; flex: none; }
.walk .walk-stop { z-index: 3; }   /* the pause while the recording plays (main.js initWalkthroughs) */
.walk.playing::after, .walk.playing .play { opacity: 0; }
.walk.playing .play { visibility: hidden; transition: opacity var(--dur-ui) var(--ease), visibility 0s var(--dur-ui); }
.walk-note { max-width: 932px; margin: var(--sp-3) auto 0; text-align: center; }
@media (prefers-reduced-motion: reduce) { .walk::after, .walk .play { transition: none; } }

/* Proof blocks: the chart and the stake row share one frame */
.proof { background: var(--surface); border: 1px solid var(--hair); border-radius: 16px; padding: clamp(22px, 3vw, 32px); position: relative; }
/* Figures (after vercel.com): a row of headline numbers, each with a mono
   label and one sentence, no frame. The audit page's projected outcomes sit
   under its button proof. Each stat rolls in (data-flow) a beat after the one
   before it (data-flow-delay), as its column rises. */
.proof + .figures { margin-top: clamp(48px, 6vw, 80px); }
.figures-row { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-7) clamp(24px, 3vw, 48px); }
.figure { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }
.figure .eyebrow { margin-top: var(--sp-4); color: var(--ink); }
.figure .t-body { margin-top: var(--sp-4); color: var(--ink-2); }
.figures details.data { margin-top: var(--sp-7); }
@media (max-width: 900px) { .figures-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .figures-row { grid-template-columns: minmax(0, 1fr); } }
.proof-head { display: flex; justify-content: space-between; align-items: end; gap: 16px; flex-wrap: wrap; margin-bottom: var(--sp-5); }
.proof-head small { display: block; max-width: 40ch; }
/* The frame also hosts a comparison table (Verifications) or, where there is no
   number to chart, a hairline list of what shipped (No-code tools). */
.proof .table-wrap { border: 0; border-top: 1px solid var(--hair); border-radius: 0; background: transparent; }
.proof .tensions { margin-top: 0; }
.proof .tensions li { grid-template-columns: minmax(0, 11rem) minmax(0, 1fr); }
.proof .table-wrap ~ .tensions li { padding-inline: 16px; }   /* the table's cell inset, so the labels line up with its rows */
@media (max-width: 560px) { .proof .tensions li { grid-template-columns: minmax(0, 1fr); gap: 4px; } }   /* after the rule above, which it has to outlast */
/* Before and after (No-code tools): two lanes of four steps, drawn in the AI
   card's language (Sep 28, 2026): each step a small app card with a numbered
   title bar, a spinner while the work is on it and a check once it has moved
   on, dotted wires between them and a dot carrying the work. main.js stages
   it: the before lane plays alone in the middle of the frame, its steps waking
   out of a slight tilt as the dot reaches them, stalls at the engineering
   queue, and Deploy ticks; then it rises and steps back while the after lane
   comes in and runs straight to Publish. Without the script, or under reduced
   motion, both lanes sit finished. */
.flow-ba { display: grid; gap: var(--sp-5); margin: 0 0 var(--sp-6); }
.ba-lane { position: relative; display: grid; grid-template-columns: minmax(0, 10rem) minmax(0, 1fr); align-items: center; gap: var(--sp-5);
  padding: clamp(20px, 3vw, 32px); border-radius: 16px; background: var(--paper-2); border: 1px solid var(--hair);
  transition: transform .8s var(--ease-soft), opacity .6s linear; }
.ba-label { display: flex; flex-direction: column; gap: var(--sp-1); }
.ba-name { font-family: var(--font-display); font-size: var(--t-heading); line-height: var(--lh-heading); letter-spacing: var(--ls-heading); color: var(--ink); }
.ba-after .ba-label .eyebrow { color: var(--accent-ink); }
.ba-steps { list-style: none; margin: 0; padding: 0; position: relative; z-index: 1; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: clamp(20px, 3vw, 40px); perspective: 1200px; }   /* a layer of its own over the dot: the perspective would otherwise paint the dot over the cards */
/* the wire: round dots, behind the cards, so only the gaps show it */
.ba-steps::before { content: ""; position: absolute; left: 12.5%; right: 12.5%; top: 50%; height: 4px; margin-top: -2px;
  background: radial-gradient(circle, color-mix(in srgb, var(--ink) 38%, transparent) 1.3px, transparent 1.8px) left center / 10px 4px repeat-x; }
.ba-after .ba-steps::before { background-image: radial-gradient(circle, color-mix(in srgb, var(--accent) 60%, transparent) 1.3px, transparent 1.8px); }
.ba-steps li { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 6px; min-height: 5.25rem; padding: 0 14px 14px;
  background: var(--surface); border: 1px solid var(--hair); border-radius: 10px; color: var(--ink);
  box-shadow: 0 1px 1px rgba(20, 16, 12, .04), 0 2px 4px rgba(20, 16, 12, .04), 0 12px 24px -14px rgba(20, 16, 12, .24);
  transition: opacity .6s cubic-bezier(.16, 1, .3, 1), transform .75s cubic-bezier(.16, 1, .3, 1), filter .5s cubic-bezier(.16, 1, .3, 1),
    background-color .4s var(--ease), border-color .4s var(--ease), box-shadow .4s var(--ease), color .4s var(--ease); }
.ba-head { display: flex; align-items: center; justify-content: space-between; height: 32px; margin: 0 -14px 4px; padding: 0 10px 0 14px;
  border-bottom: 1px solid var(--hair); border-radius: 10px 10px 0 0; background: color-mix(in srgb, var(--paper-2) 45%, var(--surface));
  font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing: var(--ls-micro); font-weight: 500; color: var(--ink-3); }
.ba-title { font-family: var(--font-display); font-size: var(--t-subhead); line-height: var(--lh-subhead); color: var(--ink); text-wrap: balance; }
/* the step's state: nothing yet, a spinner while the work is on it, a check once it has moved on */
.ba-state { width: 14px; height: 14px; border-radius: 50%; flex: none; }
.ba-steps li.is-reached .ba-state { border: 1.5px solid var(--hair-2); border-top-color: var(--ink); animation: st-spin 900ms linear infinite; }
.ba-steps li.is-reached:has(~ li.is-reached) .ba-state, .ba-steps li.is-done .ba-state { border: 0; animation: none;
  background: var(--ink-3) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 6.3l2 2 4-4.3'/%3E%3C/svg%3E") center / 10px no-repeat; }
.ba-after .ba-steps li.is-reached:has(~ li.is-reached) .ba-state, .ba-after .ba-steps li.is-done .ba-state { background-color: var(--accent); }
.ba-steps li.is-reached:not(:has(~ li.is-reached)):not(.is-done) { border-color: color-mix(in srgb, var(--ink) 30%, transparent); }
.ba-after .ba-steps li.is-reached:not(:has(~ li.is-reached)):not(.is-done) { border-color: var(--accent); }
.ba-after .ba-steps li.is-done { border-color: var(--accent); box-shadow: 0 0 0 4px var(--glow), 0 12px 24px -14px rgba(20, 16, 12, .24); }
/* the stall: the queue card goes amber and says why, its spinner slowed to a crawl */
.ba-steps li.is-waiting { background: var(--wait); border-color: var(--wait-edge); }
.ba-steps li.is-waiting .ba-head { background: transparent; border-color: color-mix(in srgb, var(--wait-edge) 40%, transparent); color: var(--wait-ink); }
.ba-steps li.is-waiting .ba-title { color: var(--wait-text, var(--ink)); }
.ba-steps li.is-waiting .ba-state { border-color: color-mix(in srgb, var(--wait-edge) 35%, transparent); border-top-color: var(--wait-edge); animation-duration: 2.4s; }
.ba-wait { display: block; max-height: 0; opacity: 0; overflow: hidden; font-size: var(--t-small); line-height: var(--lh-small); color: var(--wait-ink);
  transition: max-height .4s var(--ease), opacity .3s var(--ease); }
.ba-steps li.is-waiting .ba-wait { max-height: 9em; opacity: 1; }
/* a stall that is a rejection, not a wait (Cross-Sell's decline): the card goes red,
   flinches once and carries what an applicant said about that moment */
.ba-wait q { display: block; font-style: italic; quotes: "“" "”"; }
.ba-cite { display: block; margin-top: 4px; font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; opacity: .85; }
.ba-steps li.ba-reject.is-waiting { background: color-mix(in srgb, var(--danger) 9%, var(--surface)); border-color: var(--danger); box-shadow: 0 0 0 4px color-mix(in srgb, var(--danger) 14%, transparent); animation: ba-flinch .45s cubic-bezier(.36, .07, .19, .97) both; }
.ba-steps li.ba-reject.is-waiting .ba-head { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 30%, transparent); }
.ba-steps li.ba-reject.is-waiting .ba-title { color: var(--ink); }
.ba-steps li.ba-reject.is-waiting .ba-wait { color: var(--ink-2); }
.ba-steps li.ba-reject.is-waiting .ba-state { animation: none; border: 0; background: var(--danger) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 4l4 4M8 4l-4 4'/%3E%3C/svg%3E") center / 10px no-repeat; }
.ba-steps li.ba-reject.is-reached:has(~ li.is-reached) .ba-state { background: var(--danger) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 4l4 4M8 4l-4 4'/%3E%3C/svg%3E") center / 10px no-repeat; }   /* a decline stays a cross, never a check */
.flow-ba.is-two .ba-before .ba-steps li.ba-reject .ba-state { background-color: var(--hair-2); }
@keyframes ba-flinch { 20% { transform: translateX(-5px); } 40% { transform: translateX(4px); } 60% { transform: translateX(-3px); } 80% { transform: translateX(2px); } }
/* the end of a lane can say where it landed: a note that opens once the last step is done.
   A lane that ends nowhere (data-end="lost", Cross-Sell's old decline) crosses its last step out. */
.ba-end { display: block; max-height: 0; opacity: 0; overflow: hidden; font-size: var(--t-small); line-height: var(--lh-small); color: var(--accent-ink);
  transition: max-height .4s var(--ease), opacity .3s var(--ease); }
.ba-steps li.is-done .ba-end { max-height: 3em; opacity: 1; }
[data-end="lost"] .ba-end { color: var(--ink-2); }
[data-end="lost"] .ba-steps li.is-done .ba-state { background: var(--danger) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 4l4 4M8 4l-4 4'/%3E%3C/svg%3E") center / 10px no-repeat; }
.flow-ba.is-two [data-end="lost"] .ba-steps li.is-done .ba-state { background-color: var(--hair-2); }
.flow-ba.is-two .ba-before .ba-end { color: var(--ink-3); }
/* The dot: main.js moves it along the wire; across it, it holds the lane's middle, where
   the wire runs, so it stays on the wire when the waiting note makes the queue card taller.
   A hop is HOP in main.js, on the plain in-out: only the gap between two cards shows the dot. */
.ba-dot { position: absolute; left: 0; top: 50%; z-index: 0; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%;
  background: var(--ink); box-shadow: 0 0 0 4px color-mix(in srgb, var(--ink) 10%, transparent); opacity: 0; pointer-events: none; transform: translateX(var(--x, 0));
  transition: transform .9s ease-in-out, opacity .3s var(--ease); }
.ba-after .ba-dot { background: var(--accent); box-shadow: 0 0 0 4px var(--glow); }
.ba-dot.is-on { opacity: 1; }
/* Staged: a step the dot has not reached waits dimmed and tilted back; the before lane
   sits in the middle of the frame, the after lane below and out of sight */
.flow-ba.is-staged .ba-steps li:not(.is-reached) { transform: translate3d(0, 8px, 0) rotateX(12deg) scale(.96); box-shadow: none; background: color-mix(in srgb, var(--paper-2) 55%, var(--surface)); }
.flow-ba.is-staged .ba-steps li:not(.is-reached) > * { opacity: .4; filter: saturate(0) blur(1.5px); }   /* the card stays solid, so the wire never shows through it; only what is on it waits */
.ba-head, .ba-title { transition: opacity .6s cubic-bezier(.16, 1, .3, 1), filter .5s cubic-bezier(.16, 1, .3, 1), color .4s var(--ease), background-color .4s var(--ease); }
.flow-ba.is-staged .ba-before { transform: translateY(calc(50% + var(--sp-5) / 2)); }
.flow-ba.is-staged .ba-after { opacity: 0; transform: translateY(var(--sp-5)); }
/* Both on screen: the before lane rises and steps back. The cards melt into the
   lane's ground and stop spinning; the words drop to --ink-3, which still clears
   4.5:1 on paper-2, so the fade is in the chrome, not the reading. */
.flow-ba.is-two .ba-before { transform: none; }
.flow-ba.is-two .ba-before .ba-steps::before { opacity: .45; transition: opacity .6s linear; }
.flow-ba.is-two .ba-before .ba-steps li { background: var(--paper-2); border-color: var(--hair-2); box-shadow: none; }
.flow-ba.is-two .ba-before :is(.ba-title, .ba-head, .ba-name, .eyebrow) { color: var(--ink-3); background: transparent; transition: color .6s var(--ease); }
.flow-ba.is-two .ba-before .ba-state { animation: none; border: 0; background-color: var(--hair-2); }
.flow-ba.is-two .ba-after { opacity: 1; transform: none; }
@media (max-width: 640px) {
  .ba-lane { grid-template-columns: minmax(0, 1fr); }
  .ba-steps { grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); }   /* room for the dot to wait between steps */
  .ba-steps::before { left: 50%; right: auto; top: 12.5%; bottom: 12.5%; width: 4px; height: auto; margin: 0 0 0 -2px; background-position: center top; background-size: 4px 10px; background-repeat: repeat-y; }
  .ba-steps li { min-height: 0; }
  .ba-dot { left: 50%; top: 0; transform: translateY(var(--y, 0)); }   /* the wire runs down the lane's middle */
  .flow-ba.is-staged .ba-before { transform: none; }   /* a stacked lane is too tall to centre: it waits at the top, under the lede */
}
@media (prefers-reduced-motion: reduce) { .ba-lane, .ba-steps li, .ba-wait, .ba-dot { transition: none; } .ba-state, .ba-steps li { animation: none !important; } }
/* What shipped, under the lanes: the two phases and what was kept, as chips */
.ba-phases { list-style: none; margin: 0; padding: 0; gap: var(--sp-3); }
.ba-phases .chip { display: inline-flex; align-items: baseline; gap: var(--sp-2); padding: 10px 18px; border-radius: 999px; background: var(--surface); color: var(--ink); white-space: normal; }
.ba-phases .eyebrow { color: var(--accent-ink); }
/* Chart: a line over a soft area, drawn by main.js in the pixels of its box.
   The grid sits under a clipped layer that main.js sweeps open; the pill
   holds the closing value at the right edge; a marker, a dot and a card
   follow the pointer or the arrow keys. Text in the SVG is in true pixels. */
.chart-shell { position: relative; height: 320px; touch-action: pan-y; }
.chart-shell svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; display: block; }
.chart-lines { position: absolute; inset: 0; z-index: 1; will-change: clip-path; pointer-events: none; }
.chart .grid { stroke: var(--hair-2); stroke-width: 1; stroke-dasharray: 4 6; }
.chart .axis { font-size: 13px; fill: var(--ink-3); }
.chart .line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.chart .anno { font-size: 12px; fill: var(--ink-2); }
.chart .anno-line { stroke: var(--hair-2); stroke-dasharray: 3 3; }
.chart-pill { position: absolute; right: 0; z-index: 3; transform: translateY(-50%); padding: 3px 9px 4px; border-radius: 28px; border: 2px solid var(--surface); background: var(--accent); white-space: nowrap; pointer-events: none; line-height: 1.2; }
.chart-marker { position: absolute; z-index: 1; width: 0; border-left: 1.5px solid color-mix(in srgb, var(--ink) 9%, transparent); transform: translateX(-50%); opacity: 0; transition: opacity var(--dur-hover); pointer-events: none; }
.chart-node { position: absolute; z-index: 2; width: 13px; height: 13px; border-radius: 50%; border: 2px solid var(--surface); background: var(--accent); box-shadow: 0 1px 2px rgba(20, 16, 12, .18); transform: translate(-50%, -50%); opacity: 0; transition: opacity var(--dur-hover); pointer-events: none; }
.chart-marker.show, .chart-node.show { opacity: 1; }
.chart-hit { position: absolute; z-index: 5; border-radius: 8px; }
.chart-hit:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chart-legend { display: inline-flex; align-items: center; gap: 8px; }
.chart-legend .dot, .matrix-legend .dot, .tooltip .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--accent); flex: 0 0 auto; }
.tooltip { position: absolute; top: 0; z-index: 4; width: 240px; padding: 14px 16px; display: flex; flex-direction: column; gap: 8px; border-radius: 14px; background: var(--surface); box-shadow: 0 3px 5px rgba(20, 16, 12, .05), 0 0 0 1px rgba(20, 16, 12, .08); transform: translate(-50%, 6px) scale(.94); transform-origin: 50% 100%; opacity: 0; transition: opacity var(--dur-hover) var(--ease), transform var(--dur-hover) var(--ease); pointer-events: none; }
.tooltip.show { opacity: 1; transform: translate(-50%, 0); }
.tooltip b { display: block; }
.tip-row { display: flex; align-items: center; gap: 8px; }
.tip-val { margin-left: auto; font-variant-numeric: lining-nums tabular-nums; }
.tip-note { color: var(--ink-3); padding-top: 8px; border-top: 1px solid var(--hair); }
/* Matrix: one dot per variant. main.js draws the wall on a canvas as the block
   reveals, then thins it to the set that remains, tinted by component. */
.matrix-plot { position: relative; margin-top: var(--sp-2); }
.matrix-plot canvas { display: block; width: 100%; height: auto; }
.proof-foot { position: relative; }
.proof-foot .sheets-replay { position: absolute; top: -6px; right: 0; }   /* on the summary's line; the table opens beneath it */
.matrix-legend { display: inline-flex; flex-wrap: wrap; gap: 6px 16px; }
.matrix-legend > span { display: inline-flex; align-items: center; gap: 8px; }
.matrix-legend .m2 { background: var(--accent-2); }
.matrix-legend .m3 { background: var(--ink-2); }
/* Configurator: the old .Button and the new Button side by side, every
   option a chip. Native radios and checkboxes sit under the chips, so the
   keyboard and a screen reader get a plain form, and :has(:checked) lights
   the chosen chip. The specimen is drawn by main.js in Best Egg's own
   colors, the way a screenshot would be, on a navy ground for the dark
   theme. */
.config { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(24px, 3vw, 44px); }
.config-side { display: grid; gap: var(--sp-4); align-content: start; min-width: 0; }
.specimen { display: grid; place-items: center; min-height: 150px; padding: 24px; border-radius: var(--radius-sm); border: 1px solid var(--plate-hair); background: var(--plate-paper); transition: background var(--dur-ui) var(--ease), border-color var(--dur-ui) var(--ease); }
.specimen.navy { background: #0B1F3A; border-color: #0B1F3A; }
.spec { display: inline-flex; align-items: center; justify-content: center; gap: .5em; border: 1.5px solid transparent; border-radius: 8px; font-family: var(--font-body); font-weight: 500; line-height: 1; cursor: default; pointer-events: none; transition: background var(--dur-hover), color var(--dur-hover), border-color var(--dur-hover), padding var(--dur-hover), font-size var(--dur-hover); }
.spec svg { width: 1.05em; height: 1.05em; flex: none; }
.spec.s-small { font-size: 13px; padding: 7px 12px; }
.spec.s-medium { font-size: 14px; padding: 10px 16px; }
.spec.s-large { font-size: 16px; padding: 13px 22px; }
.spec.fixed { min-width: 176px; }
.spec.icon-only { padding-inline: 0; aspect-ratio: 1; }
.spec.icon-only.fixed { min-width: 0; }
.spec.st-link { text-decoration: underline; text-underline-offset: .18em; }
.spec.focused { box-shadow: 0 0 0 2px var(--specimen-bg, var(--plate-paper)), 0 0 0 4px currentColor; }
.config-form { display: grid; gap: var(--sp-4); }
.config-group { border: 0; margin: 0; padding: 0; min-width: 0; }
.config-group legend { padding: 0; margin-bottom: 8px; }
.config-group .chips { gap: 6px; }
.config .chip { position: relative; padding: 6px 11px; cursor: pointer; user-select: none; transition: background var(--dur-hover) var(--ease), border-color var(--dur-hover) var(--ease), color var(--dur-hover) var(--ease); }
.config .chip:hover { border-color: color-mix(in srgb, var(--ink) 28%, transparent); }
.config .chip:has(:checked) { background: var(--ink); border-color: var(--ink); color: var(--surface); }
.config .chip:has(:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.config-sum { border-top: 1px solid var(--hair); padding-top: var(--sp-3); }
.config-sum b { font-variant-numeric: lining-nums tabular-nums; }
@media (max-width: 760px) { .config { grid-template-columns: minmax(0, 1fr); } }
details.data { margin-top: 14px; }
details.data summary { cursor: pointer; }
details.data table { min-width: 0; margin-top: 10px; }
details.data th, details.data td { white-space: normal; padding-inline: 10px; }   /* wraps rather than clips at 320px */
@media (max-width: 400px) { main table { width: 100%; table-layout: fixed; } main table :is(th, td) { white-space: normal; overflow-wrap: anywhere; } }   /* the columns share the width and wrap, so no table widens the page */
/* Stake row: the pool as a row of LINK tokens, one per million (John picked
   it Sep 30 2026 over an arch, an hourglass and a coil). The 45 slots are the
   v0.1 pool; the first 25 are early access, which filled in three hours, so
   those tokens drop in left to right as the block reveals while the other 20
   stay pale. main.js draws it in WebGL (initStakeRow) and places the labels by
   projection; without WebGL the flat .stake-bar below is the figure. */
.stake-row { position: relative; aspect-ratio: 4 / 1; max-width: 100%; margin: var(--sp-7) 0 var(--sp-6); touch-action: pan-y; }
.stake-row canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.stake-row.is-3d { cursor: grab; user-select: none; -webkit-user-select: none; }
.stake-row.is-3d:active { cursor: grabbing; }
.stake-bar { position: absolute; left: 0; right: 0; top: 50%; height: 26%; transform: translateY(-50%); border-radius: 6px; overflow: hidden; background: color-mix(in srgb, var(--accent-2) 20%, var(--surface)); }
.stake-bar .fill { position: absolute; inset: 0 auto 0 0; width: calc(25 / 45 * 100%); background: linear-gradient(90deg, var(--accent), var(--accent-2)); transform-origin: left; animation: stake-fill 2.4s var(--ease-in-out) both; }
.anim [data-reveal]:not(.in) .stake-bar .fill { animation-play-state: paused; }
@keyframes stake-fill { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.stake-row .lbl { position: absolute; left: 0; white-space: nowrap; color: var(--ink-2); pointer-events: none; }
.stake-row [data-x="8.333"] { left: calc(100% / 45 * 25 / 3); }   /* data-x is the slot, of 45 */
.stake-row [data-x="16.667"] { left: calc(100% / 45 * 50 / 3); }
.stake-row [data-x="25"] { left: calc(100% / 45 * 25); }
.stake-row [data-x="45"] { left: 100%; }
.stake-row .below { top: 63%; transform: translate(-50%, 8px); }
.stake-row .below.start { transform: translate(0, 8px); }
.stake-row .below.after { transform: translate(0, 8px); }
.stake-row .above { bottom: 63%; transform: translate(-100%, -8px); }
.stake-row .above.start { transform: translate(0, -8px); }
.stake-row.is-3d .stake-bar { display: none; }
.stake-row.is-3d .lbl { left: 0; top: 0; bottom: auto; will-change: transform; }
@media (max-width: 640px) { .stake-row { aspect-ratio: 5 / 2; } .stake-row .below.start { display: none; } }   /* the row's own start says 0 hr; the label would sit on 1 hr */

/* Next: the closing band */
.cs-next { margin-top: var(--section); padding-block: var(--section); background: var(--paper-2); border-top: 1px solid var(--hair); text-align: center; }
.cs-next .wrap { display: flex; flex-direction: column; align-items: center; }
/* The next case study's title is the link. The two ways out of the sequence are
   text links told apart by what they do (after Coinbase's docs pager and
   Stripe's breadcrumb): the previous case study is a direction, an arrow
   straight under the next one's title; All work is the index, a grid beside
   Contact me. Contact me stays the band's one button. */
.cs-next-title a { transition: color var(--dur-hover); }
.cs-next-title a:hover { color: var(--accent-ink); }
.cs-prev-link, .cs-all-link { display: inline-flex; align-items: center; gap: 8px; padding-block: var(--sp-2); color: var(--ink); transition: color var(--dur-hover); }   /* the padding lifts the tap target past 24px (2.5.8) */
.cs-prev-link svg, .cs-all-link svg { width: 16px; height: 16px; flex: none; }
.cs-prev-link { margin-top: var(--sp-4); }
.cs-prev-link svg { transition: transform var(--dur-hover) var(--ease); }
@media (hover: hover) and (pointer: fine) {
  .cs-prev-link:hover, .cs-all-link:hover { color: var(--accent-ink); }
  .cs-prev-link:hover svg { transform: translateX(-3px); }   /* the arrow leans the way it goes */
}
.cs-next .actions { display: flex; align-items: center; gap: var(--sp-4) var(--sp-5); flex-wrap: wrap; justify-content: center; margin-top: clamp(28px, 3vw, 40px); }
.cs-next footer { width: 100%; justify-content: center; align-items: center; padding-bottom: 0; margin-top: clamp(40px, 5vw, 64px); }
.cs-next footer .theme { margin: 0; }

/* Steps (after diabrowser.com): a decisions block marked data-steps, whose
   columns each carry a shot, becomes from 761px up a numbered list beside one
   panel that sticks and shows the step under the reader. main.js (initSteps)
   moves the shots into the panel, numbers the heads and marks the current
   step; the others fall back to ink-3. Below 761px the columns stack with
   their own shots, as they always did. */
@media (min-width: 761px) {
  .steps.on .cols { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); grid-template-rows: none; column-gap: clamp(28px, 4vw, 64px); row-gap: 0; align-items: start; }
  .steps.on .col { grid-column: 1; grid-row: auto; display: flex; flex-direction: column; justify-content: center; min-height: 36vh; padding: var(--sp-6) 0 var(--sp-6) var(--sp-5); border-left: 1px solid var(--hair); transition: border-color var(--dur-ui); }   /* each step centred in its stretch of scroll, level with the panel */
  .steps.on .col:first-child { padding-top: 0; }
  .steps.on .col .t-subhead::before { display: none; }
  .steps.on .col[aria-current="step"] { border-left-color: var(--accent); }
  .anim .steps.on .cols > .col.col { transition: border-color var(--dur-ui); transition-delay: 0s; }   /* outranks the column entrance (section 9), which a vertical list of steps doesn't use */
  .steps.on .col :is(.t-subhead, .t-body, .col-icon) { transition: color var(--dur-ui); }
  .steps.on .col:not([aria-current="step"]) :is(.t-subhead, .t-body, .col-icon) { color: var(--ink-3); }
  .steps.on .col .shot { display: none; }
  .steps-stage { grid-column: 2; position: sticky; top: calc(var(--nav-h) + var(--sp-5)); aspect-ratio: 1.7; }
  .steps-stage .shot { position: absolute; inset: 0; margin: 0; opacity: 0; transition: opacity var(--dur-ui) var(--ease); }
  .steps-stage .shot.is-on { opacity: 1; }
}
.step-no { min-width: 2.2ch; }

/* Segmented control: a row of options in one frame, the chosen one in ink.
   The lens above the work list and the reading control on a case study
   both use it. The frame's corner is the buttons' plus its 3px inset, so
   the two corners stay concentric. */
.seg { display: inline-flex; max-width: 100%; padding: 3px; border: 1px solid var(--hair-2); border-radius: calc(var(--radius-btn) + 3px); background: var(--surface); overflow-x: auto; scrollbar-width: none; }
.seg-tab { border: 0; background: none; color: var(--ink-2); padding: 7px 14px; border-radius: var(--radius-btn); cursor: pointer; white-space: nowrap; transition: color var(--dur-hover), background var(--dur-hover); }
.seg-tab:hover { color: var(--ink); }
.seg-tab[aria-pressed="true"] { background: var(--ink); color: var(--paper); }
/* The lens (after the audience prompts on axoworks.com): the control above
   the work list re-sorts the rows for the reader's role. main.js (initLens) keeps the choice for the next visit
   and, where the browser can, moves the rows in a view transition. */
.lens { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); margin-bottom: clamp(24px, 3vw, 36px); }   /* the label over the tabs, both on the axis of the centred heading above */
/* The phone's dropdown: a button face showing the role (cycling until the
   reader picks) with a native <select> laid over it, so the tap opens the
   phone's own picker and a screen reader meets a plain select. */
.lens-pick { display: none; position: relative; min-width: 12.5rem; }
.lens-pick-face { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); width: 100%; padding: 9px 14px 9px 18px; border: 1px solid var(--hair-2); border-radius: var(--radius-btn); background: var(--surface); color: var(--ink); overflow: hidden; }
.lens-pick-word { display: block; font-family: var(--font-body); font-size: var(--t-small); line-height: var(--lh-small); font-weight: 500; white-space: nowrap; }
.lens-pick-word.roll { animation: lens-roll .42s var(--ease) both; }
.lens-pick:not(.picked) .lens-pick-word { color: var(--ink-3); font-weight: 400; }   /* a hint of the options, set like a placeholder; it filters nothing */
@keyframes lens-roll { from { opacity: 0; transform: translateY(70%); } to { opacity: 1; transform: none; } }
.lens-pick-face svg { width: 16px; height: 16px; flex: none; color: var(--ink-2); }
.lens-select { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; font-size: 16px; }   /* 16px: iOS doesn't zoom the page on tap */
.lens-select:focus-visible + .lens-pick-face, .lens-pick:has(.lens-select:focus-visible) .lens-pick-face { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .lens-pick-word.roll { animation: none; } }
@media (max-width: 640px) {
  .lens { align-items: flex-start; }
  .lens.has-pick { flex-direction: column; align-items: stretch; gap: var(--sp-3); }   /* the label on the left edge over a full-width dropdown, in line with the heading above */
  .lens.has-pick .seg { display: none; }   /* the dropdown stands in for the tabs */
  .lens-pick { display: flex; width: 100%; }
}

/* The agent window (after granola.ai and replicate.com): the audit case
   study's hero re-enacts the agent's run in live HTML, from a script the
   page carries as JSON (data-demo-script) that main.js (initDemo) plays and
   loops: a title bar with the run's status, a log the lines arrive in, and
   one figure that counts. It pauses off screen and behind its own button;
   under reduced motion it stands at the run's last step. */
/* The window is a screen on the panel, so it takes the plate's inks and
   stays paper in both registers, as the screenshots do. main.js holds it
   at its tallest step's height for the current width. */
.hero-panel.demo { flex-direction: column; align-items: stretch; padding: clamp(20px, 3.5vw, 40px); }
@media (max-width: 1100px) { .hero-panel.demo { padding-bottom: 60px; } }   /* where the window fills the panel, its pause sits under it, not on its corner */
.agent-win {
  position: relative; width: 100%; max-width: 1120px; margin-inline: auto;
  display: grid; grid-template-rows: auto 1fr; overflow: hidden;
  background: var(--plate-surface); border: 1px solid var(--plate-hair); border-radius: 12px; box-shadow: var(--shadow-screen);
  font-family: var(--font-mono); font-size: 13px; line-height: 1.6; color: var(--plate-ink);
}
.agent-bar { display: flex; align-items: center; gap: var(--sp-3); padding: 10px 14px; border-bottom: 1px solid var(--plate-hair); background: var(--plate-paper); }
.agent-dots { display: inline-flex; gap: 6px; }
.agent-dots i { width: 10px; height: 10px; border-radius: 50%; background: var(--plate-hair-2); }
.agent-title, .agent-status { font-size: var(--t-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; }
.agent-title { color: var(--plate-ink-2); }
.agent-status { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; color: var(--plate-ink-0); text-align: right; }
.agent-status::before { content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--plate-accent); }
.agent-win.busy .agent-status::before { animation: agent-pulse 1s ease-in-out infinite; }
@keyframes agent-pulse { 50% { opacity: .25; } }
.agent-split { display: grid; grid-template-columns: minmax(0, 1.08fr) minmax(0, 1fr); }

/* Left: the terminal the agent runs in. A dark pane in both registers, on
   the plate's inks: green for a pass, a warm amber (its one extra color,
   the terminal's warning) for a flag, and dim for what it skipped. */
.agent-term {
  --term-ink: var(--plate-paper-2); --term-dim: #9C958B; --term-ok: var(--plate-accent-2); --term-flag: #D9A066;
  list-style: none; margin: 0; padding: var(--sp-5); display: flex; flex-direction: column; gap: 2px;
  background: var(--plate-ink-0); color: var(--term-ink);
}
.agent-term li { white-space: pre-wrap; padding-left: 2.4ch; text-indent: -2.4ch; }   /* a wrapped line hangs under its text, clear of its mark */
.agent-term li > i { font-style: normal; display: inline-block; width: 1.4ch; text-indent: 0; }
.agent-term .t-prompt { color: var(--term-ok); }
.agent-term .t-cmd { margin-bottom: 6px; }
.agent-term .t-ok > i { color: var(--term-ok); }
.agent-term .t-ok b { font-weight: 500; color: var(--term-ok); }
.agent-term .t-dim { color: var(--term-dim); }
.agent-term .t-flag { color: var(--term-flag); }
.agent-term .t-bar { color: var(--term-dim); }
.t-track { display: inline-block; width: 9ch; height: 7px; vertical-align: 1px; border-radius: 2px; background: rgba(239, 236, 230, .14); overflow: hidden; }
.t-track b { display: block; height: 100%; background: var(--term-ok); transform-origin: left; transform: scaleX(var(--p, 1)); }
.agent-term .t-caret { margin-top: auto; padding-top: 6px; }
.caret { display: inline-block; width: .6em; height: 1.1em; vertical-align: -3px; background: var(--term-ink); animation: caret 1.1s steps(1) infinite; }
@keyframes caret { 50% { opacity: 0; } }

/* Right: the remediation guide the agent writes as it flags. Two charts of
   numbers the page states (the .Button variants cut to what was kept; the
   component picker split into what stays, what moves and what goes), then
   one row per fix with its link into Figma. A row's mark says who acts. */
.agent-guide { --g-pad: var(--sp-5); position: relative; display: flex; flex-direction: column; background: var(--plate-paper); border-left: 1px solid var(--plate-hair); }
.g-head { position: sticky; top: 0; z-index: 1; display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-3); padding: 16px var(--g-pad) 12px; background: var(--plate-paper); border-bottom: 1px solid var(--plate-hair); font-size: var(--t-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; color: var(--plate-ink-2); }
.g-total b { font-weight: 500; color: var(--plate-ink-0); font-variant-numeric: tabular-nums; }
.g-body { display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-4) var(--g-pad) var(--g-pad); flex: 1; }

.g-chart { display: grid; gap: 8px; animation: g-in .45s var(--ease) both; }
@keyframes g-in { from { opacity: 0; transform: translateY(8px); } }
.gc-top { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-3); }
.gc-label { font-size: var(--t-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; color: var(--plate-ink-2); }
.gc-val { display: inline-flex; align-items: baseline; gap: 8px; color: var(--plate-ink-2); }
.gc-val b { font-family: var(--font-display); font-weight: 400; font-size: 28px; line-height: 1; letter-spacing: -.5px; font-variant-numeric: lining-nums tabular-nums; color: var(--plate-ink-0); }
.g-chart.decided .gc-val b { color: var(--plate-accent-ink); }
.gc-val small { font-size: 12px; }
.gc-bar { --gap: 2px; display: flex; height: 12px; border-radius: 6px; overflow: hidden; background: var(--plate-paper-2); }
.gc-bar i { display: block; height: 100%; }
.gc-key { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 12px; line-height: 1.4; color: var(--plate-ink-2); }
.gc-key li { display: inline-flex; align-items: center; gap: 6px; }
.gc-key li::before { content: ""; flex: none; width: 8px; height: 8px; border-radius: 2px; background: var(--sw); }
.gc-key b { font-weight: 500; color: var(--plate-ink-0); font-variant-numeric: tabular-nums; }

/* .Button variants: the whole 2,304 as a sand bar; at the decision it
   shrinks to the 321 kept, split Button, Icon Button and Link. */
.g-var { --sand: var(--plate-hair-2); --v-btn: #2B5233; --v-icon: #4E7D4C; --v-link: #9DBF98; }
.g-var .gc-track { background: repeating-linear-gradient(135deg, var(--plate-hair) 0 2px, var(--plate-paper-2) 2px 6px); }
.gc-kept { display: flex; gap: 0; height: 100%; width: 100%; transition: width .8s var(--ease), gap .5s var(--ease) .3s; }   /* one component until the split */
.gc-kept i { transition: background-color .5s var(--ease) .15s; background: var(--sand); }
.gc-kept .v-btn { flex: 141; } .gc-kept .v-icon { flex: 120; } .gc-kept .v-link { flex: 60; }
.g-var.decided .gc-kept { width: 13.93%; gap: 2px; }   /* 321 of 2,304 */
.g-var.decided .gc-kept .v-btn { background: var(--v-btn); }
.g-var.decided .gc-kept .v-icon { background: var(--v-icon); }
.g-var.decided .gc-kept .v-link { background: var(--v-link); }
.gc-key .v-btn { --sw: var(--v-btn); } .gc-key .v-icon { --sw: var(--v-icon); } .gc-key .v-link { --sw: var(--v-link); }
.g-var .gc-key { opacity: 0; transition: opacity .4s var(--ease) .5s; }
.g-var.decided .gc-key { opacity: 1; }
.g-chart .g-after { display: none; }
.g-chart.decided .g-after { display: inline; }
.g-chart.decided .g-before { display: none; }

/* Component picker: 491 standalones. What is flagged peels off the end of
   the bar: illustrations move to a library of their own, the unused go. */
.g-picker { --stay: var(--plate-accent-ink); --ill: var(--plate-hair-2); --dep: #C98A48; --w-ill: 0%; --w-dep: 0%; }
.g-picker.set-ill { --w-ill: 54.16%; }   /* 266 of 491 */
.g-picker.set-dep { --w-dep: 12.02%; }   /* 59 of 491 */
.g-picker .gc-bar .k-stay { flex: 1 1 0; background: var(--stay); }
.g-picker .gc-bar .k-ill { flex: none; width: var(--w-ill); background: repeating-linear-gradient(135deg, var(--ill) 0 3px, #E3DFD7 3px 6px); transition: width .7s var(--ease); }
.g-picker .gc-bar .k-dep { flex: none; width: var(--w-dep); background: var(--dep); transition: width .7s var(--ease); }
.g-picker .gc-bar i + i { box-shadow: -2px 0 0 0 var(--plate-paper); }
.g-picker .gc-key li { opacity: .35; transition: opacity .4s var(--ease); }
.g-picker .gc-key .k-stay { opacity: 1; }
.g-picker.set-ill .gc-key .k-ill, .g-picker.set-dep .gc-key .k-dep { opacity: 1; }
.gc-key .k-stay { --sw: var(--plate-accent-ink); }
.gc-key .k-ill { --sw: var(--plate-hair-2); }
.gc-key .k-dep { --sw: #C98A48; }

.g-rows { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--plate-hair); }
.g-row {
  display: grid; grid-template-columns: 16px minmax(0, 1fr) auto; column-gap: 12px; row-gap: 1px; align-items: baseline;
  padding: 9px 0; border-bottom: 1px solid var(--plate-hair); animation: g-in .45s var(--ease) both;
}
.g-mark { grid-row: 1 / span 2; align-self: start; position: relative; width: 16px; height: 16px; margin-top: 2px; border-radius: 50%; border: 1.5px solid var(--plate-hair-2); background: var(--plate-surface); transition: background-color .3s var(--ease), border-color .3s var(--ease); }
.g-mark::after { content: ""; position: absolute; left: 4.5px; top: 1.5px; width: 4px; height: 8px; border: solid var(--plate-surface); border-width: 0 1.5px 1.5px 0; transform: rotate(45deg) scale(0); transition: transform .3s var(--ease) .1s; }
.g-agent .g-mark { background: var(--plate-accent-2); border-color: var(--plate-accent-2); }
.g-row.decided .g-mark { background: var(--plate-accent-ink); border-color: var(--plate-accent-ink); }
.g-row.decided .g-mark::after { transform: rotate(45deg) scale(1); }
.g-title { grid-column: 2 / -1; font-family: var(--font-body); font-size: 15px; font-weight: 500; line-height: 1.35; color: var(--plate-ink-0); }
.g-meta { grid-column: 2; font-size: 12px; color: var(--plate-ink-2); }
.g-link { grid-column: 3; font-size: 12px; color: var(--plate-accent-ink); white-space: nowrap; }
.g-legend { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 0; font-size: 11px; letter-spacing: .02em; color: var(--plate-ink-2); }
.g-legend span { display: inline-flex; align-items: center; gap: 6px; }
.g-legend span::before { content: ""; width: 10px; height: 10px; border-radius: 50%; border: 1.5px solid var(--plate-hair-2); background: var(--plate-surface); }
.g-legend .g-agentkey::before { background: var(--plate-accent-2); border-color: var(--plate-accent-2); }
.g-legend .g-donekey::before { background: var(--plate-accent-ink); border-color: var(--plate-accent-ink); }

/* The run in progress: main.js marks what has not printed yet. It keeps
   its room (the window is held at the finished run's height), so nothing
   under the window moves. */
.agent-win:not(.measure) .pending { display: none; }
.agent-win.paused .caret, .agent-win.paused .g-chart, .agent-win.paused .g-row { animation-play-state: paused; }

@media (max-width: 860px) {
  .agent-split { grid-template-columns: minmax(0, 1fr); }
  .agent-guide { border-left: 0; border-top: 1px solid var(--plate-hair); }
}
@media (max-width: 640px) {
  .agent-win { font-size: 12px; }
  .agent-term { padding: var(--sp-4); }
  .agent-guide { --g-pad: var(--sp-4); }
  .agent-title { display: none; }   /* the status alone fits the bar */
  .gc-top { flex-wrap: wrap; row-gap: 2px; }
  .g-meta { grid-column: 2 / -1; }
  .g-link { grid-column: 2; margin-top: 2px; }
  /* Both panes keep a fixed height and scroll on their own; while the run
     plays they follow the newest output, as a terminal and a document being
     written do (main.js keeps each at its end), and the terminal fades at
     the top. The guide keeps its head. Not playing (reduced motion), a
     reader can scroll them. */
  .agent-term { height: 17.5rem; overflow: auto; }
  .agent-term .t-caret { margin-top: 0; }
  .agent-guide { height: 30rem; overflow: auto; }
  .agent-term:focus-visible { outline: 2px solid var(--plate-paper); outline-offset: -2px; }   /* a pane that scrolls is a tab stop (main.js); paper on the dark ground */
  .agent-guide:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
  .agent-win.playing .agent-term, .agent-win.playing .agent-guide { overflow: hidden; }
  /* The fade at the top of the terminal is a strip of its own ground laid
     over the scrolled lines (a mask would show the paper bar through it). */
  .agent-term::before { content: ""; display: block; flex: none; position: sticky; top: 0; z-index: 1; height: 2.5rem; margin: calc(-1 * var(--sp-4)) calc(-1 * var(--sp-4)) calc(var(--sp-4) - 2.5rem - 2px); pointer-events: none; background: linear-gradient(to bottom, var(--plate-ink-0) 10%, rgba(20, 16, 12, 0)); }
}
@media (prefers-reduced-motion: reduce) {
  .g-chart, .g-row, .caret { animation: none; }
  .gc-kept, .gc-kept i, .g-picker .k-ill, .g-picker .k-dep, .g-mark, .g-mark::after, .g-picker .gc-key li, .g-var .gc-key { transition: none; }
  .agent-win.busy .agent-status::before { animation: none; }
}

/* The brief as a run (Sep 28, 2026; replaced a sheet of seven clauses): the
   six things the brief asks for, drawn as the flow the agent works through,
   each step a small app card with its instruction and what the run found.
   Three across, then back along the second row, so the monthly rerun closes
   the loop up the left. main.js (initRun) draws the dotted wires between the
   cards and plays the run the way the home page's AI card does: a step wakes,
   works and ticks, the work travels the wire to the next. Without the script,
   or under reduced motion, every card shows done. Plate colors only: the
   panel stays paper in both registers. */
.panel.run-panel { display: block; padding: clamp(20px, 3vw, 36px); }
.run-eyebrow { margin: 0 0 var(--sp-5); padding-right: 48px; color: var(--plate-ink-2); }
.rn-flow { position: relative; z-index: 1; list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 72px 56px; perspective: 1400px; }
.rn-node:nth-child(4) { grid-area: 2 / 3; }
.rn-node:nth-child(5) { grid-area: 2 / 2; }
.rn-node:nth-child(6) { grid-area: 2 / 1; }
.rn-node { display: flex; flex-direction: column; gap: 10px; padding: 0 16px 16px; background: var(--plate-surface); border: 1px solid rgba(20, 16, 12, .1); border-radius: 10px; color: var(--plate-ink); font-size: 13px; line-height: 1.45;
  box-shadow: 0 1px 1px rgba(20, 16, 12, .04), 0 2px 4px rgba(20, 16, 12, .04), 0 12px 24px -14px rgba(20, 16, 12, .24);
  transition: opacity 600ms cubic-bezier(.16, 1, .3, 1), transform 750ms cubic-bezier(.16, 1, .3, 1), filter 500ms cubic-bezier(.16, 1, .3, 1), border-color 400ms; }
.rn-head { display: flex; align-items: center; gap: 10px; height: 36px; margin: 0 -16px; padding: 0 12px 0 16px; border-bottom: 1px solid #EFEBE4; background: #FBFAF8; border-radius: 10px 10px 0 0;
  font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; font-weight: 500; color: var(--plate-ink-0); }
.rn-no { color: var(--plate-ink-2); }
.rn-title { margin: 4px 0 0; font-family: var(--font-display); font-size: var(--t-subhead); line-height: var(--lh-subhead); color: var(--plate-ink-0); }
.rn-sub { margin: 0; color: var(--plate-ink); text-wrap: pretty; }
.rn-rows { list-style: none; margin: 0; padding: 0; display: grid; }
.rn-rows li { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 7px 0; border-top: 1px solid #EFEBE4; transition: opacity 400ms, transform 500ms cubic-bezier(.16, 1, .3, 1); }
.rn-rows li:first-child { border-top: 0; padding-top: 2px; }
.rn-skip > span:first-child { text-decoration: line-through; text-decoration-color: rgba(20, 16, 12, .45); color: var(--plate-ink-2); }
.rn-tag { flex: none; display: inline-flex; align-items: center; height: 20px; padding: 0 7px; border-radius: 999px; background: #F1EEE8; color: var(--plate-ink-2); font-size: 11px; font-weight: 500; white-space: nowrap; }
.rn-tag.alt { background: #E3EDE3; color: var(--plate-accent-ink); }
.rn-count { flex: none; font-family: var(--font-mono); font-size: 12px; color: #9A4430; font-variant-numeric: tabular-nums; }
.rn-meter { margin-top: auto; display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; padding-top: 4px; }
.rn-num { font-family: var(--font-display); font-size: 30px; line-height: 1; color: var(--plate-accent-ink); font-variant-numeric: lining-nums tabular-nums; }
.rn-unit { font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; color: var(--plate-ink-2); }
.rn-bar { flex: 1 0 100%; height: 4px; margin-top: 8px; border-radius: 2px; background: #EFEBE4; overflow: hidden; }
.rn-bar i { display: block; height: 100%; width: calc(var(--w, 1) * 100%); border-radius: inherit; background: var(--plate-accent); transform-origin: 0 50%; transform: scaleX(var(--p, 1)); }
.rn-bar .rn-adopt { --w: .907; }   /* 90.7% library adoption */
.rn-fix { padding: 10px 12px; border: 1px solid #EFEBE4; border-radius: 8px; background: #FBFAF8; }
.rn-fix p { margin: 0; }
.rn-link { color: var(--plate-ink-0); font-weight: 500; text-decoration: underline; text-underline-offset: .18em; text-decoration-color: rgba(20, 16, 12, .3); }
.rn-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px !important; }
.rn-hand { margin: auto 0 0; display: flex; align-items: center; gap: 8px; padding-top: 4px; color: var(--plate-ink-0); font-weight: 500; }
.rn-av { width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center; flex: none; background: #E9E4DB; color: var(--plate-ink-0); font-size: 9.5px; font-weight: 600; }

/* the step's state in its title bar: a spinner while it works, a green check once it has */
.rn-state { margin-left: auto; width: 14px; height: 14px; border-radius: 50%; flex: none; background: var(--plate-accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 6.3l2 2 4-4.3'/%3E%3C/svg%3E") center / 10px no-repeat; }

/* the wires: round dots, a faint track that is always there and a darker run drawn over it */
.rn-wires { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.rn-wires .trk, .rn-wires .lit { fill: none; stroke-width: 2.6; stroke-linecap: round; stroke-dasharray: 0 8; }
.rn-wires .trk { stroke: rgba(20, 16, 12, .16); }
.rn-wires .lit { stroke: rgba(20, 16, 12, .5); }
.rn-wires .arw { fill: none; stroke: rgba(20, 16, 12, .5); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.rn-wires .pkt { fill: #14100C; stroke: rgba(20, 16, 12, .08); stroke-width: 8; paint-order: stroke; }
.rn-loop { position: absolute; z-index: 1; transform: translateY(-50%); font-family: var(--font-mono); font-size: var(--t-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; color: var(--plate-ink-2); white-space: nowrap; }
.run-panel .art-ctl { top: 12px; bottom: auto; }
.run-panel:not(.playing) .rn-state { animation-play-state: paused; }

/* live: a step waits dimmed, wakes out of a slight tilt, works, then settles done */
.run-panel.live .rn-node { opacity: .4; filter: saturate(0) blur(1.5px); transform: translate3d(0, 10px, 0) rotateX(10deg) scale(.97); }
.run-panel.live .rn-node:is(.on, .ok) { opacity: 1; filter: none; transform: none; }
.run-panel.live .rn-node.on { border-color: rgba(20, 16, 12, .28); }
.run-panel.live .rn-node:not(.ok) .rn-state { background: none; border: 1.5px solid #D9D3C9; }
.run-panel.live .rn-node.on:not(.ok) .rn-state { border-top-color: #14100C; animation: st-spin 900ms linear infinite; }
.run-panel.live .rn-node:not(.ok) .rn-rows li:not(.in) { opacity: 0; transform: translateY(6px); }
.run-panel.live .rn-node:not(.ok) .rn-fix, .run-panel.live .rn-node:not(.ok) .rn-hand { opacity: 0; transform: translateY(6px); }
.run-panel.live .rn-node.on .rn-fix.in, .run-panel.live .rn-node.on .rn-hand.in { opacity: 1; transform: none; }
.rn-fix, .rn-hand { transition: opacity 400ms, transform 500ms cubic-bezier(.16, 1, .3, 1); }

@media (max-width: 860px) {   /* one column, in order; the wires run straight down and the rerun is said in step six */
  .rn-flow { grid-template-columns: minmax(0, 1fr); gap: 40px; }
  .rn-node:nth-child(n) { grid-area: auto; }
  .rn-loop { display: none; }
  .run-panel.live .rn-node { filter: saturate(0); opacity: .55; }   /* a reader scrolls ahead of the run here: a waiting step stays legible */
}
@media (max-width: 560px) {
  .row.full .panel.run-panel { padding: 16px 12px; }
  .run-eyebrow { margin-bottom: var(--sp-4); }
}

/* Cut and kept (after emilkowal.ski's wrong-and-right pairs): the two ways
   a button could carry its icon, each shown in a real capture (the old sheet,
   the new Button's properties), with the verdict as a label and the reason in a line. */
.pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-5); margin-top: var(--sp-5); }
.pair + .panel { margin-top: var(--sp-5); }   /* as far below the pair as the pair is below the columns */
.pair-item { display: flex; flex-direction: column; padding: var(--sp-5); border: 1px solid var(--hair); border-radius: var(--radius); background: var(--surface); }
.pair-item.right { border-color: color-mix(in srgb, var(--accent) 45%, var(--hair)); }
.pair-demo { position: relative; margin-bottom: var(--sp-5); border-radius: 12px; overflow: hidden; }
.pair-demo img { display: block; width: 100%; height: auto; }
.pair-demo::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px var(--hair); pointer-events: none; }   /* a hairline over the capture's own edge */
.pair-verdict { color: var(--ink-3); }
.pair-item.right .pair-verdict { color: var(--accent-ink); }
@media (max-width: 640px) { .pair { grid-template-columns: minmax(0, 1fr); } }


/* --------------------------------------------------------------------------
   8. Password gate
   -------------------------------------------------------------------------- */
.gate-wrap { min-height: 100vh; display: grid; place-items: center; padding: 40px var(--gutter); position: relative; }
.gate { position: relative; width: 100%; max-width: 470px; background: var(--surface); border: 1px solid var(--hair); border-radius: var(--radius); padding: clamp(28px, 5vw, 42px); box-shadow: var(--shadow-lift); }
.gate .brand { margin-bottom: var(--sp-5); }
.gate-form { display: flex; flex-direction: column; gap: 18px; margin-top: var(--sp-5); }
.gate-form input { padding: 14px 16px; border-radius: 12px; border: 1px solid var(--hair-2); background: var(--surface-2); color: var(--ink); }
.gate-form input:focus { outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent); }
.gate-form input[aria-invalid="true"] { border-color: var(--danger); }
.gate-form input[aria-invalid="true"]:focus { outline-color: var(--danger); }
.gate-form .btn { justify-content: center; padding: 15px 20px; }
.gate-error { margin-top: -8px; color: var(--danger); }   /* pulls up under the field it describes, inside the form's gap */
.back { display: block; width: fit-content; margin-top: var(--sp-4); transition: color var(--dur-hover); }
.back:hover { color: var(--accent-ink); }

/* --------------------------------------------------------------------------
   8b. Unlock opener
   The moment after the password. The case study opens behind a sheet of its
   own ground split by one green seam: the seam draws, the title rises on the
   sheet, then the seam becomes two edges and the halves part like doors with
   the hero rising in behind them. main.js builds it (initUnlock) when the
   gate sends the reader here with ?unlocked, and the one-line script in each
   case study's head sets html.unlock before first paint so the page is never
   seen before the sheet is. The hold is an animation so it can never outlast
   a script that fails: after 3s the page shows regardless.

   Every class here is opener-, never a bare .open: the phone menu marks its
   own bar .nav.open, and a plain .open rule this far down the sheet outranks
   .nav on order and clipped the menu away behind the bar.
   -------------------------------------------------------------------------- */
html.unlock { background: var(--panel-base); }
html.unlock:not(.unlock-on) body { animation: unlock-hold 3s step-end both; }
@keyframes unlock-hold { from { visibility: hidden; } to { visibility: visible; } }
html.unlock [data-rise] { animation-play-state: paused; }   /* the hero's cascade waits for the doors */
.opener { position: fixed; inset: 0; z-index: 80; overflow: hidden; display: grid; place-items: center; padding: var(--gutter); visibility: visible; cursor: pointer; }
/* Each half shows its half of one viewport-wide gradient, so the ground reads as one sheet across the seam. */
.opener-half { position: absolute; inset: 0; background: var(--panel) 0 0 / 200% 100% var(--panel-base); }
.opener-half.l { right: 50%; }
.opener-half.r { left: 50%; background-position: 100% 0; }
.opener.part .opener-half.l { border-right: 1px solid var(--accent); }
.opener.part .opener-half.r { border-left: 1px solid var(--accent); }
.opener-seam { position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; margin-left: -.5px; background: var(--accent); transform: scaleY(0); }
.opener-body { position: relative; display: flex; flex-direction: column; align-items: center; text-align: center; width: min(100%, 60ch); }
.opener-body > * { opacity: 0; }                            /* main.js rises each line in */
.opener-body .t-display { margin-top: var(--stack-label); max-width: 16ch; }
.opener-note { margin-top: var(--sp-6); color: var(--ink-2); max-width: 44ch; text-wrap: balance; }

/* --------------------------------------------------------------------------
   9. Motion
   -------------------------------------------------------------------------- */
/* Between pages (after maggieappleton.com and cursor.com): the document
   crossfades instead of flashing to white, the bar, the launcher and the
   footer hold still, and the image in a work row grows into that case
   study's hero (the row's ground and the hero panel share a name per
   project, so the browser pairs them). A
   browser without the feature, or a reader who asked for less motion, gets
   a plain page load. */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: .32s; }
::view-transition-group(*) { animation-duration: .42s; animation-timing-function: var(--ease); }
.nav { view-transition-name: site-nav; }
.chat-launch { view-transition-name: site-launch; }
footer { view-transition-name: site-foot; }   /* one a page, at the foot of its last section */
.case-system .case-media, .case-system .hero-panel { view-transition-name: case-system; }
.case-audit .case-media, .case-audit .hero-panel { view-transition-name: case-audit; }
.case-cross-sell .case-media, .case-cross-sell .hero-panel { view-transition-name: case-cross-sell; }
.case-verifications .case-media, .case-verifications .hero-panel { view-transition-name: case-verifications; }
.case-refi .case-media, .case-refi .hero-panel { view-transition-name: case-refi; }
.case-staking .case-media, .case-staking .hero-panel { view-transition-name: case-staking; }
.case-no-code .case-media, .case-no-code .hero-panel { view-transition-name: case-no-code; }
/* The rows themselves, for the lens's reorder (initLens): each row keeps its
   place in the eye as the list re-sorts. */
.case-row.case-system { view-transition-name: row-system; }
.case-row.case-audit { view-transition-name: row-audit; }
.case-row.case-cross-sell { view-transition-name: row-cross-sell; }
.case-row.case-verifications { view-transition-name: row-verifications; }
.case-row.case-refi { view-transition-name: row-refi; }
.case-row.case-staking { view-transition-name: row-staking; }
.case-row.case-no-code { view-transition-name: row-no-code; }
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }
/* A block waits below its place and rises into it once the reader reaches
   it (main.js sets .in). The fade runs straight so the block is still
   translucent while it travels and lands opaque; blocks that arrive in the
   same frame follow each other 60ms apart (--reveal-i). */
.anim [data-reveal] {
  opacity: 0; transform: translateY(var(--reveal-y));
  transition:
    opacity var(--dur-reveal-in) linear calc(var(--reveal-i, 0) * 60ms),
    transform var(--dur-reveal-in) var(--ease-soft) calc(var(--reveal-i, 0) * 60ms);
}
.anim [data-reveal].in { opacity: 1; transform: none; }
/* Columns in a revealed block enter one after another, left to right, while
   the block is still settling (a column row reads as a sequence, not a slab). */
.anim [data-reveal] .cols > .col {
  opacity: 0; transform: translateX(-32px);
  transition: opacity var(--reveal-col-dur) linear, transform var(--reveal-col-dur) var(--ease-soft);
}
.anim [data-reveal] .cols > .col:nth-child(2) { --col-i: 1; }
.anim [data-reveal] .cols > .col:nth-child(3) { --col-i: 2; }
.anim [data-reveal] .cols > .col:nth-child(4) { --col-i: 3; }
.anim [data-reveal].in .cols > .col { opacity: 1; transform: none; transition-delay: calc(var(--reveal-col-wait) + var(--col-i, 0) * var(--reveal-col-step)); }   /* the wait only on the way in, so hiding at load is instant; a fourth column lands by 920ms (590ms on a phone) */
.anim [data-reveal] .figures-row > .figure { opacity: 0; transform: translateY(24px); transition: opacity var(--reveal-col-dur) linear, transform var(--reveal-fig-dur) var(--ease-soft); }
.anim [data-reveal] .figures-row > .figure:nth-child(2) { --col-i: 1; }
.anim [data-reveal] .figures-row > .figure:nth-child(3) { --col-i: 2; }
.anim [data-reveal] .figures-row > .figure:nth-child(4) { --col-i: 3; }
.anim [data-reveal].in .figures-row > .figure { opacity: 1; transform: none; transition-delay: calc(var(--reveal-col-wait) + var(--col-i, 0) * var(--reveal-fig-step)); }   /* the figures follow each other, like the columns */
/* The coaching cards enter one at a time as each one's well comes on screen
   (main.js adds .card-in, left to right within a row): the card rises, then
   its build clip plays in the well. The well stays empty until the clip or
   the still (.show-still: no clip, or it failed) takes it; a still drops the
   last few pixels and settles from a slight zoom. */
.anim .coach-card.waits { transition: opacity .5s linear, transform .7s var(--ease-soft), border-color var(--dur-hover); }
.anim .coach-card.waits:not(.card-in) { opacity: 0; transform: translateY(24px); }
.anim .coach-card.waits .coach-art img { transition: opacity .6s linear, transform 1.1s var(--ease-soft); }
.anim .coach-card.waits .coach-art:not(.show-still) img { opacity: 0; --py: -18px; --ps: 1.1; }
.anim .coach-card.waits.settled .coach-art img { transition: transform .6s var(--ease-soft); }   /* landed: the lean follows the pointer at once */
[data-rise] { animation: rise .6s var(--ease) both; animation-delay: calc(var(--i, 0) * 70ms); }
@keyframes rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
/* The hero hands off (after granola.ai): where the browser can drive an
   animation from scroll, the dot field fades over the first screen of
   scroll, so the work arrives on plain paper. Content blocks rise on the
   timer above rather than with the scroll: a rise scrubbed by scrolling
   leaves whatever rests at the foot of the screen half faded. */
@supports (animation-timeline: scroll()) {
  .hero .field { animation: hero-field linear both; animation-timeline: scroll(root); animation-range: 0 80vh; }
  @keyframes hero-field { to { opacity: 0; } }
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .hero .field { animation: none; }
  .anim [data-reveal], [data-rise], .anim [data-reveal] .cols > .col, .anim .coach-card.waits { opacity: 1; transform: none; transition: none; animation: none; }
  .coach-art img, .anim .coach-card.waits .coach-art img { opacity: 1; transform: none; transition: none; }
  .opener-body > * { opacity: 1; }                          /* the opener shows still, then goes */
  .opener-seam { transform: none; }
  .stake-bar .fill { animation: none !important; }
  .layer, .layer.active, .layer-tabs::after, .nest, .nest .t-subhead, .steps-stage .shot, .testimonial, .testimonial.active { transition: none; }
  .testimonial { filter: none; }
  .post-card:hover, .btn:hover:active { transform: none; }
  .case-media img, .tooltip, .chart-marker, .chart-node { transition: none; transform: none; }
  .tooltip { transform: translate(-50%, 0); }
  .btn:hover, .btn:active, .icon-btn:active, .layer-tab:active, .testimonial-controls button:active, .cs-prev-link:hover svg { transform: none; }
  .talk *, .talk *::before { transition: none !important; }   /* the LinkedIn button goes straight to its last frame (main.js skips the meet) */
  .icon-swap svg { transition: none; }
  .walk .play:active, .walk .play:hover:active { transform: translate(-50%, -50%); }
  .nav-links, .nav.open .nav-links, .nav-links li, .nav.open .nav-links li, .nav-foot, .nav.open .nav-foot { transition: none; }
  .nav-toggle path { transition: none; }
}

/* --------------------------------------------------------------------------
   10. Per-project grounds
   Each case study gets its own ground, drawn in CSS from the project's own
   subject matter, and carried from the thumbnail through every panel inside
   the case study. Kept near-invisible so the screenshots stay the subject.
   -------------------------------------------------------------------------- */
.case-cross-sell {
  /* the site's own sand, with a grid of registration marks: every decline checked against a second product */
  --panel-art: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='44'%3E%3Cpath d='M19 22.5h7M22.5 19v7' stroke='rgba(20,16,12,.11)'/%3E%3C/svg%3E");
  --panel-size: 44px 44px;
}
.case-verifications {
  --panel-base: #E9E7D3;
  --panel: linear-gradient(155deg, #F1F0E1 0%, #DDDABF 100%);
  /* ruled lines: a stack of documents waiting to be checked off */
  --panel-art: repeating-linear-gradient(180deg, rgba(20,16,12,.055) 0 1px, transparent 1px 24px);
}
.case-refi {
  --panel-base: #EEE2CD;
  --panel: linear-gradient(155deg, #F4EAD9 0%, #E4D5BB 100%);
  /* a shallow downhill ruling: a rate stepping down */
  --panel-art: repeating-linear-gradient(192deg, rgba(20,16,12,.05) 0 1px, transparent 1px 28px);
}
.case-staking {
  --panel-base: #EBE0D8;
  --panel: linear-gradient(155deg, #F2EAE5 0%, #E1CFC4 100%);
  /* concentric rings rising from below: a pool filling to capacity */
  --panel-art: repeating-radial-gradient(circle at 50% 116%, rgba(20,16,12,.05) 0 1px, transparent 1px 42px);
}
.case-no-code {
  --panel-base: #E2E9DC;
  --panel: linear-gradient(155deg, #EBF0E6 0%, #D5E0CE 100%);
  /* a lattice of nodes: the flow builder's canvas */
  --panel-art: radial-gradient(rgba(20,16,12,.10) 1.2px, transparent 1.4px);
  --panel-size: 28px 28px;
}
.case-audit {
  --panel-base: #E6DFD0;
  --panel: linear-gradient(155deg, #EEE8DB 0%, #DCD3C1 100%);
  /* a small pill outline per tile: the cells of a variant sheet */
  --panel-art: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='28'%3E%3Crect x='13.5' y='11.5' width='9' height='5' rx='2.5' fill='none' stroke='rgba(20,16,12,.075)'/%3E%3C/svg%3E");
  --panel-size: 36px 28px;
}
.case-system {
  --panel-base: #E3DCD3;
  --panel: linear-gradient(155deg, #EBE5DD 0%, #D8CFC4 100%);
  /* a short tally stroke per tile: every instance counted */
  --panel-art: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28'%3E%3Cpath d='M14.5 10v8' stroke='rgba(20,16,12,.09)'/%3E%3C/svg%3E");
  --panel-size: 28px 28px;
}
/* This page skips the accent bar before non-first column headings; the icon
   already marks each one, and the bar read as a stray line beside it. */
.case-system .col:not(:first-child) .t-subhead::before { display: none; }
/* My role is nested inside #story, between two rows rather than as its own
   top-level section, so it gets no margin from the row+row rule below it;
   give it the section gap on both sides to match the rest of the page. */

/* --------------------------------------------------------------------------
   11. Phone
   Everything below 560px (and a few things below 760px). The rules above
   already collapse the grids; this section fixes what a collapse alone gets
   wrong on a phone: dead space, screens too small to read, sequences that
   lose their order, tables that hide the result column, and centred
   paragraphs that are hard to read at seven lines.
   -------------------------------------------------------------------------- */
@media (max-width: 760px) {
  :root { --nav-h: 68px; }
  .btn { min-height: 44px; }
  .icon-btn { width: 42px; height: 42px; }
}

@media (max-width: 640px) {
  /* Sequences stay in order: the gallery and the three-up hero become
     swipe strips, one screen at a time with the next one peeking in. */
  .gallery, .hero-panel.three {
    display: flex; flex-wrap: nowrap; justify-content: flex-start; align-items: flex-start;
    overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .gallery::-webkit-scrollbar, .hero-panel.three::-webkit-scrollbar { display: none; }
  .gallery { gap: 16px; }
  .gallery figure { flex: 0 0 76%; scroll-snap-align: start; scroll-snap-stop: always; }
  .panel .gallery img { max-height: 520px; }
  .hero-panel.three { gap: 16px; scroll-padding-inline: clamp(28px, 5vw, 64px); }
  .hero-panel.three img { flex: 0 0 64%; width: 64%; min-width: 0; max-width: none; max-height: none; scroll-snap-align: center; scroll-snap-stop: always; }

  /* The A/B table stacks: one card per metric, each cell labelled by its column */
  .table-wrap table { min-width: 0; }
  .table-wrap thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .table-wrap tr, .table-wrap th, .table-wrap td { display: block; }
  .table-wrap tbody tr { padding: 18px 16px; border-bottom: 1px solid var(--hair); }
  .table-wrap tbody tr:last-child { border-bottom: 0; }
  .table-wrap tbody th, .table-wrap tbody td { padding: 0; border: 0; white-space: normal; }
  .table-wrap tbody th { font-size: var(--t-body); line-height: var(--lh-body); margin-bottom: var(--sp-3); }   /* the row's label steps up to body, as the card's heading */
  .table-wrap tbody td + td { margin-top: var(--sp-3); }
  .table-wrap td[data-label]::before { content: attr(data-label); display: block; margin-bottom: 3px; }
  .table-wrap td.hl { padding: 10px 12px; border-radius: 8px; }
  .table-wrap td.hl::before { color: var(--accent-ink); }

  /* In a proof table the two states are a comparison, not a list: side by
     side reads at a glance, stacked makes you scroll to see both. */
  .proof .table-wrap tbody tr { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-4); }
  .proof .table-wrap tbody th { flex: 1 1 100%; margin-bottom: 0; }
  .proof .table-wrap tbody td { flex: 1 1 0; min-width: 0; margin-top: 0; }
  .proof .table-wrap tbody td:not(.hl) { padding-block: 10px; }   /* the tinted cell's inset, so the two values start level */
}

@media (max-width: 560px) {
  :root { --section: 56px; --row: 48px; }

  /* Hero: the statement sits a screen's breath under the bar, not a third of the way down */
  .hero { padding-top: calc(var(--nav-h) + 56px); padding-bottom: calc(var(--sp-5) + 36px + var(--sp-4)); }   /* room for the pause under the buttons */
  .hero .art-ctl { bottom: var(--sp-4); }
  .hero-ctas .btn { flex: 1 1 auto; justify-content: center; }

  /* Section openers read left to right on a phone; a seven-line centred paragraph does not */
  .section-intro, .cs-hero-copy, .row.full > .row-copy { align-items: flex-start; text-align: left; margin-inline: 0; }
  .section-intro > *, .row.full .row-copy > * { margin-inline: 0; }
  .lead-coach > .t-heading, .lead-coach-head { text-align: left; }
  .lead-coach > .t-heading, .lead-coach-head > * { margin-inline: 0; }
  .cs-hero-copy h1 { max-width: none; }
  .testimonial { align-items: flex-start; text-align: left; }
  .testimonial .quote p { margin-inline: 0; }
  .testimonial-foot { justify-content: flex-start; flex-wrap: wrap; }
  .testimonial-mark { flex-basis: 100%; margin-left: 0; border-left: 0; }
  .testimonial-controls { justify-content: flex-start; }

  /* Work index: every screen on the same 4:3 ground (the home cards' ratio
     at this width), phone screens and desktop screens alike, so the list
     reads as one set; each screen's own crop (emerge, bleed) fills it */
  .case-row { gap: 20px; }
  .case-media, .case-media.wide, .case-media.cutout { aspect-ratio: 4 / 3; }
  .case-media.cutout.bleed img { left: 5%; width: 90%; max-width: none; }
  .case-media img { max-width: 44%; }
  .case-logo { height: 24px; }
  .case-logo[alt="Best Egg"] { height: 21px; }

  /* Leadership */
  .lead-solo { padding: 24px 20px; }
  .coach-copy { padding: 20px 20px 28px; }
  /* the tab strip scrolls instead of wrapping onto a broken second line */
  .layer-tabs { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 40px), transparent); }
  .layer-tabs::-webkit-scrollbar { display: none; }
  .layer-tab { flex: 0 0 auto; }
  .layer-head { padding: 22px 20px; }
  .lead-fig .layer-head { padding: 20px 0 0; }
  /* the ring names step down a role so they clear the rings on a narrow figure */
  .nest .nlbl { font-size: clamp(12px, 6cqw, var(--t-small)); }
  .nest .t-subhead { line-height: var(--lh-small); }
  .nest[data-ring="0"] .nlbl { width: 7ch; }

  /* About */
  .about-grid { gap: 24px; }
  .portrait { max-width: 150px; }
  .exp-head { margin-top: 40px; }
  .kudos { margin-top: 40px; }
  .kudo-mark { height: 19px; }

  /* Contact */
  .contact { padding: 32px 24px; gap: 28px; }
  .arcade { margin-top: 40px; }
  .arcade-head { flex-direction: column; align-items: flex-start; gap: 12px; margin-bottom: var(--sp-4); }
  footer { padding-top: var(--sp-6); flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
  footer .theme { margin: var(--sp-2) 0 0; }

  /* Case study hero and facts */
  .cs-hero { padding-top: calc(var(--nav-h) + 28px); }
  .cs-hero .crumbs { margin-bottom: 24px; }
  .hero-lead { gap: 24px; }
  .outcome-stat { padding-top: 20px; }
  .outcome-stat .t-stat { margin-top: var(--sp-2); }
  .hero-panel { margin-top: 32px; padding: 24px 20px; }
  .facts { margin-top: 24px; border-left: 0; }
  .facts > div { padding: 14px 0; border-right: 0; }

  /* Story rows: the visual sets its own height, so a phone screen is not
     squeezed into a landscape box. Cropped desktop screens keep the box. */
  .row .panel { height: auto; aspect-ratio: auto; padding: 24px 20px; }
  .row .panel img { max-height: min(520px, 68vh); }
  .row .panel.crop { aspect-ratio: 4 / 3; padding: 20px 0 0 20px; }
  .row .panel.dock { aspect-ratio: 1; padding: 24px 0 0; justify-content: center; }
  .row .panel.dock img { --dock-w: 82%; }
  .row.full .panel { padding: 20px; }
  .tensions { margin-top: 24px; }

  /* Testimonials: the mark drops under the name */
  .testimonial .t-quote { max-width: none; }
  .testimonial .quote { padding-bottom: 24px; }
  .testimonial-foot { gap: 12px 14px; padding-top: 20px; }
  .testimonial-foot .avatar { width: 44px; height: 44px; }
  .testimonial-foot cite { flex: 1; }
  .testimonial-mark { padding-left: 58px; }

  /* Proof, note, next */
  .proof { padding: 20px 18px; }
  .proof-head { margin-bottom: 18px; }
  .chart-shell { height: 250px; }
  .tooltip { width: 200px; padding: 12px 14px; }
  .note .t-body + .t-body { margin-top: 20px; padding-top: 20px; }
  .cs-next .actions { align-self: stretch; flex-direction: column; align-items: stretch; }   /* All work over a full-width Contact me */
  .cs-next .actions .btn { justify-content: center; }
  .cs-all-link { align-self: center; }
  .walk-note { text-align: left; }
}

/* --------------------------------------------------------------------------
   12. Activity dashboard (admin/)
   The owner's view of who is on the site: a stat strip, one period control,
   a visits chart and a session-length histogram, a map beside the where-from
   tally, three more tallies, a table of sessions and a live feed the page
   polls every few seconds. Built from the case-study parts (facts, card,
   chart, table) so it reads as one more page of the site.
   -------------------------------------------------------------------------- */
.dash-nav { border-color: var(--hair); }
.dash-nav-actions { margin-left: auto; display: flex; align-items: center; gap: 20px; }
.dash-live { display: inline-flex; align-items: center; gap: 8px; }
.dash-live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--hair-2); flex: none; }
.dash-live[data-state="on"] .dash-live-dot { background: var(--accent); animation: live-pulse 2.2s var(--ease-soft) infinite; }
.dash-live[data-state="off"] .dash-live-dot { background: var(--danger); }
@keyframes live-pulse { from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 38%, transparent); } to { box-shadow: 0 0 0 10px transparent; } }

/* The three dashboard pages (Activity, Nine holes, Chat): the site's tab strip,
   as links, the current page underlined in ink where the strip's hairline is. */
.dash-tabs { margin-bottom: clamp(28px, 4vw, 44px); }
.dash-tabs ul { list-style: none; padding: 0; margin: 0; }
.dash-tabs li { display: flex; flex: none; }
.dash-tabs .layer-tab { text-decoration: none; color: var(--ink-2); padding-left: clamp(12px, 1.6vw, 18px); }   /* each link is its li's first child, so the strip's own first-tab rule would catch them all */
.dash-tabs .layer-tab:hover { color: var(--ink); }
.dash-tabs .layer-tab[aria-current="page"] { color: var(--ink); border-bottom-color: var(--ink); box-shadow: inset 0 -1px 0 var(--ink); }
.dash-tabs .layer-tabs::after { content: none; }
.dash-tabs li:first-child .layer-tab { padding-left: 2px; }
.dash-head { padding-bottom: clamp(32px, 4vw, 48px); }
.dash-sub { color: var(--ink-2); max-width: 60ch; }
.dash { padding-bottom: var(--section); }
.dash-notice { margin-bottom: var(--sp-6); border-color: var(--accent); color: var(--ink-2); }
.dash-facts { margin-top: 0; grid-template-columns: repeat(5, minmax(0, 1fr)); }

/* The period: one row of tabs, and everything under it follows. */
.dash-range { margin-top: var(--row); }
.dash-notice[hidden] + .dash-range { margin-top: 0; }   /* first on the Chat page, where no stat strip sits above it */
.dash-range .layer-tab[aria-pressed="true"] { color: var(--ink); }

/* What counts as a visit: the switch, the length beside it, and the line saying
   what they hold back. Shares the pill of the feed filters. */
.dash-real { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: var(--sp-4); }
.dash-real :is(select, button) { scroll-margin-top: calc(var(--nav-h) + 16px); }
.dash-real .dash-status { flex: 1 1 24ch; margin: 0; color: var(--ink-3); }

.dash-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-5); margin-top: var(--sp-5); }
.dash-grid-wide { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
.dash-card .eyebrow { margin-bottom: var(--sp-4); }
.dash-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px 20px; flex-wrap: wrap; }
.dash-legend { display: inline-flex; gap: 18px; color: var(--ink-3); }
.dash-legend .chart-legend { gap: 7px; }
.dot-views { background: var(--ink-3) !important; width: 14px !important; height: 2px !important; border-radius: 2px !important; }
.dash-card details.data { margin-top: var(--sp-4); }
.dash-card .table-wrap { margin-top: var(--sp-3); }
.dash-card .dash-data { min-width: 0; }
.dash-card .dash-data th, .dash-card .dash-data td { padding: 9px 12px; }

/* The visits chart: the case-study chart's parts at a dashboard's height,
   visitors in the accent over a soft area, views as a thinner grey line
   behind them. Both are read from the same marker. */
.dash-chart-shell { height: 260px; }
.dash-chart .line-views { stroke: var(--ink-3); stroke-width: 1.5; }
.dash-chart .tooltip { width: 200px; }

/* Session lengths: a column per band, the count on each cap. */
.dash-hist { height: 200px; }
.dash-hist svg { display: block; width: 100%; height: 100%; overflow: visible; }
.dash-hist .col { fill: var(--accent); transition: fill var(--dur-hover); }
.dash-hist g:hover .col { fill: var(--accent-2); }
.dash-hist .cap { font-size: 12px; fill: var(--ink-2); font-variant-numeric: tabular-nums; }
.dash-hist .axis { font-size: 12px; fill: var(--ink-3); }
#hist-note { margin-top: var(--sp-3); }

/* The map: Leaflet on Esri's light tiles, its chrome restyled to the tokens.
   Dots are the accent green, sized by visits; the newest arrival pulses. */
.dash-map-card { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.dash-map-head { display: flex; align-items: center; gap: 10px 18px; flex-wrap: wrap; padding: 22px 28px 16px; }
.dash-map-head .eyebrow { margin: 0; }
.dash-map-head .t-small { flex: 1; min-width: 20ch; margin: 0; color: var(--ink-3); }
.dash-map { height: clamp(260px, 30vw, 360px); background: var(--surface-2); isolation: isolate; }
.dash-map-empty { padding: 0 28px 22px; }
.leaflet-container { background: var(--surface-2); }   /* its type comes from the small role in section 2 */
.leaflet-tile-pane, .leaflet-labels-pane { filter: sepia(.10); }   /* tiles pulled toward the paper */
.leaflet-labels-pane { pointer-events: none; }
.leaflet-bar { border: 1px solid var(--hair-2) !important; border-radius: 10px !important; box-shadow: var(--shadow-screen) !important; overflow: hidden; }
.leaflet-bar a { color: var(--ink) !important; background: var(--surface) !important; border-bottom-color: var(--hair) !important; }
.leaflet-bar a:hover, .leaflet-bar a:focus-visible { background: var(--surface-2) !important; color: var(--accent-ink) !important; }
.leaflet-popup-content-wrapper { background: var(--surface); color: var(--ink); border-radius: 12px; box-shadow: var(--shadow-lift); }
.leaflet-popup-tip { background: var(--surface); box-shadow: none; }
.leaflet-popup-content { margin: 12px 16px; }
.leaflet-container a.leaflet-popup-close-button { color: var(--ink-3); }
.leaflet-control-attribution { background: color-mix(in srgb, var(--surface) 82%, transparent) !important; color: var(--ink-3) !important; font-size: var(--t-micro); }   /* a credit line: the micro size without the micro role's caps */
.leaflet-control-attribution a { color: var(--ink-2) !important; }
.visit-dot { transition: r var(--dur-ui) var(--ease); }
.visit-dot.is-new { animation: dot-pulse 2.2s var(--ease-soft) infinite; }
@keyframes dot-pulse { from { stroke-width: 2; stroke-opacity: .9; } to { stroke-width: 18; stroke-opacity: 0; } }

.dash-list { list-style: none; margin: 0; padding: 0; }
.dash-list li { position: relative; display: flex; align-items: baseline; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--hair); }
.dash-list li:last-child { border-bottom: 0; }
.dash-bar { position: absolute; left: 0; bottom: -1px; height: 2px; width: var(--w, 0%); background: var(--accent-2); border-radius: 2px; transition: width var(--dur-reveal) var(--ease-soft); }
.dash-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-count { font-variant-numeric: tabular-nums; color: var(--ink-2); }
.dash-empty { color: var(--ink-3); }

.dash-feed { margin-top: var(--row); }
.dash-feed-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: var(--sp-5); }
.dash-feed-head .t-small { margin: 0; color: var(--ink-3); }

/* Feed filters: a row of selects in the ghost button's clothes, each with
   its own chevron. A set one turns ink, the way a chosen chip does. */
.dash-feed-head:has(+ .dash-filters) { margin-bottom: var(--sp-4); }
.dash-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: var(--sp-4); }
.dash-filter { position: relative; display: inline-flex; color: var(--ink-2); }
.dash-filters :is(select, button, input) { scroll-margin-top: calc(var(--nav-h) + 16px); }   /* focus never scrolls a control under the nav */
.dash-filter select, .dash-filter-btn {
  appearance: none; -webkit-appearance: none;
  font-family: var(--font-body); font-size: var(--t-small); line-height: 1; font-weight: 500; text-align: left;
  color: var(--ink); background: var(--surface); border: 1px solid var(--hair-2); border-radius: var(--radius-btn);
  padding: 10px 36px 10px 16px; max-width: 30ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer;
  transition: background var(--dur-hover), border-color var(--dur-hover), color var(--dur-hover);
}
.dash-filter::after {   /* the chevron: Hugeicons' arrow-down-01, cut from currentColor */
  content: ""; position: absolute; right: 12px; top: calc(50% - 8px); width: 16px; height: 16px; pointer-events: none; background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 9.00005C18 9.00005 13.5811 15 12 15C10.4188 15 6 9 6 9'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 9.00005C18 9.00005 13.5811 15 12 15C10.4188 15 6 9 6 9'/%3E%3C/svg%3E") center / contain no-repeat;
}
.dash-filter-plain::after { content: none; }                    /* a switch, with no menu behind it: no chevron, and not the room for one */
.dash-filter-plain .dash-filter-btn { padding-right: 16px; }
.dash-filter select:hover, .dash-filter-btn:hover { border-color: var(--accent); color: var(--accent-ink); }
.dash-filter:hover { color: var(--accent-ink); }
.dash-filter select:focus-visible, .dash-filter-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dash-filter.is-on, .dash-filter.is-on:hover { color: var(--paper); }
.dash-filter.is-on select, .dash-filter.is-on .dash-filter-btn { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.dash-filter.is-on select:hover, .dash-filter.is-on .dash-filter-btn:hover { background: var(--accent); border-color: var(--accent); color: var(--surface); }

/* The places menu: a card under its button, one checkbox a place, tallies at
   the right. It closes on a click elsewhere, on Escape, or when focus leaves. */
.dash-menu {
  position: absolute; z-index: 20; top: calc(100% + 8px); left: 0;
  min-width: 100%; width: max-content; max-width: min(380px, calc(100vw - 2 * var(--gutter)));
  background: var(--surface); border: 1px solid var(--hair-2); border-radius: var(--radius-sm); box-shadow: var(--shadow-lift);
  padding: 6px 0; color: var(--ink-2);
}
.dash-menu-list { border: 0; margin: 0; padding: 0; min-width: 0; max-height: 320px; overflow-y: auto; }
.dash-check { display: flex; align-items: center; gap: 12px; padding: 8px 16px; cursor: pointer; color: var(--ink); font-size: var(--t-small); line-height: var(--lh-small); }
.dash-check:hover { background: var(--surface-2); }
.dash-check input { flex: none; width: 16px; height: 16px; margin: 0; accent-color: var(--accent); cursor: pointer; }
.dash-check input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dash-check-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-check .dash-count { flex: none; font-size: var(--t-micro); }
.dash-menu-foot { display: flex; gap: 18px; margin-top: 6px; padding: 10px 16px 4px; border-top: 1px solid var(--hair); }
.dash-menu-foot button, .dash-act { border: 0; padding: 0; background: none; font-size: var(--t-micro); line-height: var(--lh-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; font-weight: 500; color: var(--ink-2); cursor: pointer; transition: color var(--dur-hover); }
.dash-menu-foot button:hover, .dash-act:hover { color: var(--accent-ink); }
.dash-menu-foot button:focus-visible, .dash-act:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
/* Block, beside a referrer in Sent by: quiet until it is armed, then it says what it will drop. */
.dash-list .dash-act { flex: none; min-width: 5ch; margin-left: 4px; color: var(--ink-3); text-align: right; white-space: nowrap; }   /* Block and Sure? take the same room, so the counts hold still */
.dash-list .dash-act:hover { color: var(--accent-ink); }
.dash-act.is-armed, .dash-list .dash-act.is-armed:hover { color: var(--danger); }
.dash-status { margin: var(--sp-3) 0 0; color: var(--ink-3); }
.dash-status:empty { display: none; }
.dash-blocked { margin-top: var(--sp-5); padding-top: var(--sp-4); border-top: 1px solid var(--hair); font-size: var(--t-small); line-height: var(--lh-small); color: var(--ink-2); }
.dash-blocked summary { cursor: pointer; }
.dash-blocked .t-small { margin: var(--sp-3) 0 var(--sp-3); color: var(--ink-3); }
.dash-blocked ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px 20px; }
.dash-blocked li { display: inline-flex; align-items: baseline; gap: 10px; font-size: var(--t-small); line-height: var(--lh-small); color: var(--ink-2); }
.dash-blocked li span { text-decoration: line-through; text-decoration-color: var(--ink-3); }

/* Nine holes: the leaderboard (a dash-list with a place and a date), and the
   side column's two small forms, their inputs in the gate input's clothes at
   the small size. */
.dash-golf-side { display: grid; gap: var(--sp-5); align-content: start; }
.dash-board li { align-items: baseline; }
.dash-board .dash-rank { flex: none; width: 3ch; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.dash-board .dash-when { flex: none; color: var(--ink-3); }
.dash-board .dash-act { flex: none; min-width: 6ch; text-align: right; color: var(--ink-3); }
.dash-board .dash-act:hover { color: var(--accent-ink); }
.dash-card-head .dash-act { margin-bottom: var(--sp-4); }
.dash-form { display: grid; gap: 12px; }
.dash-fields { display: grid; gap: 10px; }
.dash-field { display: flex; align-items: center; justify-content: space-between; gap: 16px; font-size: var(--t-small); line-height: var(--lh-small); color: var(--ink-2); }
.dash-field small { display: block; font-size: var(--t-micro); line-height: var(--lh-micro); color: var(--ink-3); }
.dash-form input:is([type="number"], [type="text"]) { font-family: var(--font-body); font-size: var(--t-small); line-height: var(--lh-small); padding: 7px 10px; border-radius: 10px; border: 1px solid var(--hair-2); background: var(--surface-2); color: var(--ink); }
.dash-form input[type="number"] { flex: none; width: 9ch; font-variant-numeric: tabular-nums; }
.dash-form input[type="text"] { flex: 1 1 auto; min-width: 0; text-transform: uppercase; }
.dash-form input[type="text"]::placeholder { text-transform: none; color: var(--ink-3); }
.dash-form input[type="checkbox"] { flex: none; width: 18px; height: 18px; accent-color: var(--accent); }
.dash-form input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent); }
.dash-form input[aria-invalid="true"] { border-color: var(--danger); }
.dash-form-row { display: flex; gap: 10px; }
.dash-form-foot { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
.dash-form .dash-status:empty, #board-status:empty { display: none; }
.dash-form .dash-status, #board-status { margin: 0; color: var(--ink-2); }
.dash-bans { list-style: none; margin: var(--sp-4) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px 20px; }
.dash-bans:empty { display: none; }
.dash-bans li { display: inline-flex; align-items: baseline; gap: 10px; font-size: var(--t-small); line-height: var(--lh-small); color: var(--ink-2); }
.dash-menu-empty { padding: 8px 16px; color: var(--ink-3); }
.dash-filter-state { margin-left: auto; display: flex; align-items: center; gap: 14px; }
.dash-filter-state .t-small { margin: 0; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.dash-feed .dash-empty { margin-top: var(--sp-4); padding-left: 16px; }   /* under the header row, at the cells' inset */
.dash-table { min-width: 1120px; }
.dash-sessions { min-width: 1040px; }
.dash-table td { padding: 12px 16px; }
.dash-table .c-time, .dash-table .c-where, .dash-table .c-secs, .dash-table .c-length, .dash-sessions .c-device { white-space: nowrap; }
.dash-table .c-time { color: var(--ink-2); }
.dash-table .c-secs, .dash-table .c-length { font-variant-numeric: tabular-nums; }
.dash-table .c-route { max-width: 34ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-table .c-visitor { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--t-micro); color: var(--ink-3); }   /* a hash, so monospace and never the micro role's caps */
/* The hash is a button: press it to follow that visitor through both tables. Pressed, it wears the chosen chip's ink. */
.dash-visitor { font: inherit; color: inherit; background: none; border: 0; padding: 3px 9px; margin: -3px -9px; border-radius: var(--radius-btn); cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; text-decoration-color: var(--hair-2); transition: background var(--dur-hover), color var(--dur-hover); }
.dash-visitor:hover { color: var(--accent-ink); text-decoration-color: currentColor; }
.dash-visitor:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dash-visitor[aria-pressed="true"], .dash-visitor[aria-pressed="true"]:hover { background: var(--ink); color: var(--paper); text-decoration: none; }
.dash-filter-visitor::after { display: none; }   /* a chip, not a menu: no chevron */
.dash-filter-visitor .dash-filter-btn { padding-right: 16px; }
.dash-hash { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.dash-x { width: 12px; height: 12px; margin-left: 6px; vertical-align: -1px; }   /* the chip's cross, after the hash */
.dash-kind, .dash-now { display: inline-block; padding: 3px 9px; border-radius: 20px; background: var(--surface-2); white-space: nowrap; }
.dash-now, .dash-kind[data-kind="unlocked"] { background: var(--glow); color: var(--accent-ink); }
.c-time .dash-now, .dash-tried .dash-kind { margin-left: 8px; }
.dash-kind[data-kind="wrong password"] { background: color-mix(in srgb, var(--danger) 10%, var(--surface)); color: var(--danger); }
.dash-tried { display: block; white-space: nowrap; }   /* one case study per line, its outcome beside it */
.dash-tried + .dash-tried { margin-top: 6px; }
.dash-table tr.is-new { animation: dash-new 4s linear both; }   /* an arrival glows green, then settles */
@keyframes dash-new { from { background: var(--glow); } to { background: transparent; } }

/* Questions: what people asked the chat. The question leads in ink; the
   answer folds under its first line, with a chip when the password came up. */
.dash-questions { min-width: 980px; }
.dash-questions .c-question { max-width: 30ch; color: var(--ink); }
.dash-questions .c-answer { max-width: 54ch; }
.dash-questions .c-answer summary { cursor: pointer; }
.dash-questions .c-answer summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
.dash-questions .c-answer details p { margin-top: var(--sp-2); white-space: pre-line; }
.dash-questions .c-answer .dash-kind { margin-top: var(--sp-2); }
.dash-questions .c-page { white-space: nowrap; }

/* Chat settings: one card of labelled fields, two columns, the save at the
   foot behind a hairline. The fields wear the gate's field; the switch and
   the model choice are native controls in the accent. */
.dash-settings { display: flex; flex-direction: column; gap: var(--sp-5); padding: var(--card-pad); }
.dash-settings-state { color: var(--ink-2); }
.dash-settings-state.is-warn { color: var(--danger); }
.dash-settings-fields { border: 0; margin: 0; padding: 0; min-width: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-6) var(--sp-7); }
.dash-settings-fields[disabled] { opacity: .55; }
.dash-setting { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); min-width: 0; border: 0; margin: 0; padding: 0; }
.dash-setting-wide { grid-column: 1 / -1; }
.dash-setting > label, .dash-setting > legend { color: var(--ink); padding: 0; }
.dash-settings input[type="number"], .dash-settings textarea { width: 100%; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--hair-2); background: var(--surface-2); color: var(--ink); max-width: none; }
.dash-settings input[type="number"] { max-width: 14ch; font-variant-numeric: tabular-nums; }
.dash-settings textarea { resize: vertical; }
.dash-settings :is(input[type="number"], textarea):focus { outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent); }
.dash-help { color: var(--ink-3); max-width: 64ch; }
.dash-switch, .dash-choice { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; color: var(--ink); }
.dash-switch input, .dash-choice input { flex: none; width: 18px; height: 18px; margin: 0; accent-color: var(--accent); cursor: pointer; }
.dash-switch input:focus-visible, .dash-choice input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dash-choice-row { display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-6); margin-top: var(--sp-1); }
.dash-choices > legend { margin-bottom: var(--sp-2); }
.dash-settings-foot { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3) var(--sp-4); padding-top: var(--sp-5); border-top: 1px solid var(--hair); }
.dash-settings-foot .dash-status { flex: 1 1 24ch; margin: 0; }
@media (max-width: 640px) { .dash-settings-fields { grid-template-columns: 1fr; gap: var(--sp-5); } }

@media (max-width: 1000px) { .dash-facts { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 900px) { .dash-grid, .dash-grid-wide { grid-template-columns: minmax(0, 1fr); } }   /* minmax: a long tally label would hold the track open at 320 */
@media (max-width: 640px) {
  .dash-facts { grid-template-columns: 1fr; }
  .dash-chart-shell { height: 220px; }
  .dash-hist { height: 180px; }
  .dash-map-head { padding: 18px 20px 14px; } .dash-map { height: 300px; } .dash-map-empty { padding: 0 20px 18px; }
  .dash-filter { flex: 1 1 calc(50% - 5px); }   /* the filters pair up; the count and Clear take a line of their own */
  .dash-filter select, .dash-filter-btn { width: 100%; max-width: none; }
  .dash-filter-state { flex-basis: 100%; margin-left: 0; justify-content: space-between; }
}
@media (max-width: 640px) {   /* the feed stacks (section 11): time and kind share the first line, the page is the card's heading */
  .dash-table tbody tr { padding: 16px; }
  .dash-table .c-time, .dash-table .c-kind { display: inline; }
  .dash-table .c-kind { margin-left: 10px; }
  .dash-table .c-page, .dash-table .c-route { margin-top: var(--sp-2); font-size: var(--t-body); line-height: var(--lh-body); max-width: none; white-space: normal; }
  .dash-visitor { margin: 0; }   /* stacked under its label, the pill lines up with it instead of hugging the cell edge */
}
@media (max-width: 560px) { .dash-nav-actions { gap: 12px; } .dash-live { font-size: var(--t-micro); } }
@media (prefers-reduced-motion: reduce) {
  .dash-live[data-state="on"] .dash-live-dot, .dash-table tr.is-new, .visit-dot.is-new { animation: none; }
  .dash-bar, .visit-dot, .dash-hist .col { transition: none; }
}

/* --------------------------------------------------------------------------
   13. Blog
   Two documents: the index (a hairline list of posts, newest first) and the
   post itself (one column of running text on a 34rem measure). A post is the
   only place on the site with long-form prose, so it is the only place a
   heading, a list or a link sits inside a paragraph stream; those bare tags
   take their roles back in section 2, so nothing here sets type. Self
   contained like the dashboard above, phone rules included.
   -------------------------------------------------------------------------- */

/* ---- Index: the three arguments, a recent strip, the latest, then all ---- */
/* The way into the blog is the argument, not the date. Two columns of serif
   lines, each one jumping to its own group below. */
.pillar-nav ul { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-3) var(--sp-7); }   /* one column a pillar */
.pillar-nav a { display: flex; flex-direction: column; align-items: flex-start; height: 100%; text-decoration: none; max-width: none; text-wrap: balance; transition: color var(--dur-hover), opacity var(--dur-hover); }
.pillar-nav .pillar-count { margin-top: auto; padding-top: var(--sp-2); }   /* the counts share a baseline whatever the titles wrap to */
.pillar-label { text-wrap: balance; }
@media (max-width: 1100px) { .pillar-nav ul { grid-template-columns: minmax(0, 1fr); } }   /* two-line labels left the counts floating under them */
.pillar-count { display: inline-block; white-space: nowrap; }
@media (hover: hover) and (pointer: fine) {
  .pillar-nav a:hover { color: var(--accent-ink); }
  /* Pointing at one line quiets the others (after craft.do's blog): they step
     down to ink-3, 5.1:1 on paper, where an opacity fade took the counts under 4.5:1 */
  .pillar-nav ul:has(a:hover) a:not(:hover) { color: var(--ink-3); }
}
.pillar-head { scroll-margin-top: calc(var(--nav-h) + var(--sp-5)); margin-bottom: var(--sp-5); }
.pillar-group:not([hidden]) ~ .pillar-group { margin-top: var(--row); }   /* measured from the last group shown, so a filter leaves no gap above the first */
.pillar-empty { padding-block: var(--sp-5); }
/* The feed, beside the eyebrow that names the section */
.feed-link { display: inline-flex; margin-left: 8px; vertical-align: -2px; color: var(--ink-3); transition: color var(--dur-hover); }
.feed-link svg { width: 13px; height: 13px; }
.feed-link:hover { color: var(--accent-ink); }

/* The read-on cue: an underlined label that turns green on hover */
.post-cta { color: var(--ink); transition: color var(--dur-hover); }
@media (hover: hover) and (pointer: fine) {
  .post-cta:hover { color: var(--accent-ink); }
}


/* The latest three, on their own ground */
.featured { background: var(--paper-2); border: 1px solid var(--hair); border-radius: var(--radius); padding: clamp(24px, 3.4vw, 48px); }
.featured-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-5); flex-wrap: wrap; margin-bottom: clamp(24px, 3vw, 36px); }
.featured-title { font-style: italic; max-width: none; }

/* A post as a card: its cover, the month, the title, the blurb.
   Named .post-card, not .card: .card is the site-wide primitive
   (section 4), used bare by the home page's three lead-solo blocks and by
   admin/index.html's dashboard cards. A same-specificity .card rule here,
   later in the cascade, would silently win the fight for `display` and
   `border-radius` on all of them. */
.cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(16px, 2vw, 24px); }
.post-card { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--hair); border-radius: var(--radius-sm); overflow: hidden; text-decoration: none; transition: transform var(--dur-hover) var(--ease), box-shadow var(--dur-hover), border-color var(--dur-hover); }
.card-art { display: block; overflow: hidden; }
.card-body { flex: 1; display: flex; flex-direction: column; gap: var(--sp-3); padding: clamp(16px, 1.8vw, 22px); }
.card-title { display: block; max-width: none; transition: opacity var(--dur-hover); text-box: trim-start cap alphabetic; }   /* the cover's inset reaches the caps, not the line box */
/* In the grid, a row of cards shares its tracks: art, title, description,
   meta, so a title that wraps doesn't push its description below the others'. */
@supports (grid-template-rows: subgrid) {
  .cards:not([data-view="list"]) .post-card { display: grid; grid-row: span 4; grid-template-rows: subgrid; row-gap: 0; }
  .cards:not([data-view="list"]) .card-body { display: grid; grid-row: span 3; grid-template-rows: subgrid; row-gap: var(--sp-3); }
}
.card-desc { display: block; transition: opacity var(--dur-hover); }
/* The foot sits on the card's floor, the chip and the read time a gap apart */
.post-card .post-meta { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-top: auto; padding-top: var(--sp-2); }
@media (hover: hover) and (pointer: fine) {
  .post-card:hover { transform: translateY(-3px); border-color: var(--hair-2); box-shadow: var(--shadow-lift); }
  .post-card:hover :is(.card-title, .card-desc) { opacity: .7; }
  .post-card:hover .card-art .cov-in { transform: scale(1.05); }
}
@media (prefers-reduced-motion: reduce) { .card-art .cov-in { transition: none; } }

/* All posts: a search and a layout choice above the groups */
.posts-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-5); flex-wrap: wrap; margin-bottom: clamp(28px, 3vw, 44px); }
.posts-controls { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.post-search { display: flex; align-items: center; gap: 8px; padding: 9px 14px; border: 1px solid var(--hair-2); border-radius: 999px; background: var(--surface); }
.post-search svg { width: 15px; height: 15px; color: var(--ink-3); flex: none; }
.post-search input { border: 0; background: none; outline: none; min-width: 170px; color: var(--ink); }
.post-search:focus-within { border-color: var(--accent); }
/* iOS Safari zooms the page into any field under 16px when it takes focus, so on touch screens the search is 16 */
@media (pointer: coarse) { .post-search input { font-size: 1rem; } }
.post-search input::-webkit-search-cancel-button {   /* the browser's blue cross, as Hugeicons' cancel-01 in the site's ink */
  -webkit-appearance: none; appearance: none; width: 14px; height: 14px; cursor: pointer; background: var(--ink-3);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6L6.00081 17.9992M17.9992 18L6 6.00085'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6L6.00081 17.9992M17.9992 18L6 6.00085'/%3E%3C/svg%3E") center / contain no-repeat;
}
.view-toggle { display: flex; padding: 3px; gap: 2px; border: 1px solid var(--hair-2); border-radius: calc(var(--radius-btn) + 3px); background: var(--surface); }   /* concentric with its buttons, as .seg is */
.view-toggle button { display: inline-flex; align-items: center; gap: 6px; padding: 7px 13px; border: 0; border-radius: var(--radius-btn); background: none; color: var(--ink-2); cursor: pointer; transition: background var(--dur-hover), color var(--dur-hover); }
.view-toggle svg { width: 14px; height: 14px; }
.view-toggle button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }
.posts-empty { padding-block: var(--sp-6); }
.post-card[hidden] { display: none; }

/* The list layout: the cover shrinks to a stripe beside the copy */
.cards[data-view="list"] { grid-template-columns: minmax(0, 1fr); gap: 0; }
.cards[data-view="list"] .post-card { flex-direction: row; align-items: stretch; border-radius: 0; border-width: 0 0 1px; background: none; }
.cards[data-view="list"] .post-card:first-child { border-top: 1px solid var(--hair); }
.cards[data-view="list"] .card-art { flex: none; width: clamp(96px, 14vw, 168px); align-self: center; border-radius: var(--radius-sm); overflow: hidden; margin: var(--sp-4) 0; }
.cards[data-view="list"] .card-body { padding-block: var(--sp-4); }
.cards[data-view="list"] .card-desc { display: none; }
@media (hover: hover) and (pointer: fine) {
  .cards[data-view="list"] .post-card:hover { transform: none; box-shadow: none; background: var(--surface); }
}

/* ---- The cover: the post's argument in one line ----
   After Harvey's title plates: the claim cut to six to ten words, in the
   serif's italic, centred over the double rule, the pillar named in the
   corner. One tinted disc bleeds off the corner so the pillar grounds still
   read at a glance. blog.py writes it from each post's `cover_line`, as spans,
   since on the index it sits inside a link. Every size is in container units,
   so one set of markup draws the 3:2 card and the list thumbnail. It lives
   on the index only; a post page opens on its title and full claim.
   Like the case-study panels, a cover is a plate of paper: it keeps its light
   palette in the dark theme, so its colors are its own, not the page's. */
.cover {
  --cv-ink: #14100C; --cv-ink-3: #6F675D;
  container-type: inline-size; position: relative; display: block; overflow: hidden;
  aspect-ratio: 3 / 2; color: var(--cv-ink);
  background: linear-gradient(155deg, var(--cv-g0), var(--cv-g1));
}
.cover.p-leadership { --cv-g0: #F0F1EB; --cv-g1: #E7E9E0; --cv-tint: #DCE2D2; --cv-acc: #3B6B44; }
.cover.p-craft      { --cv-g0: #F7F1E5; --cv-g1: #F1E8D6; --cv-tint: #EFE0C2; --cv-acc: #B07A2E; }
.cover.p-fintech    { --cv-g0: #F1F0ED; --cv-g1: #E7E4DE; --cv-tint: #DAD6CD; --cv-acc: #2B2621; }
.cov-in {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3.2cqw; padding: 8cqw 9cqw; text-align: center;
  transition: transform var(--dur-ui) var(--ease);
}
.cov-disc { position: absolute; right: -16cqw; bottom: -49cqw; width: 66cqw; aspect-ratio: 1; border-radius: 50%; background: var(--cv-tint); mix-blend-mode: multiply; }
.cov-eye { position: absolute; left: 5cqw; top: 4.6cqw; font: 500 2.7cqw/1.3 var(--font-body); letter-spacing: .08em; text-transform: uppercase; color: var(--cv-ink-3); }
.cov-line { position: relative; display: block; max-width: 17ch; font: italic 400 7.8cqw/1.1 var(--font-display); letter-spacing: -.012em; text-wrap: balance; }
.cov-rule { position: relative; display: block; width: 14cqw; height: .9cqw; border-block: 1px solid var(--cv-acc); }   /* the double rule: under a total, or under the argument */

/* ---- Post: one column, centred on the measure ---- */
.post-hero { padding-top: calc(var(--nav-h) + clamp(32px, 6vw, 72px)); padding-bottom: clamp(32px, 4vw, 52px); }
.post-hero .wrap > * { max-width: var(--prose); margin-inline: auto; }
.post-hero .crumbs { margin-bottom: clamp(24px, 3vw, 36px); }
.post-hero .crumbs ol { justify-content: center; }   /* the whole head sits on one axis */
.post-hero .eyebrow, .post-hero .t-display, .post-hero .t-lede, .post-hero .post-meta { text-align: center; }
.post-hero h1 { max-width: 26ch; }
.post-hero .post-meta { justify-content: center; }
.post-hero .t-lede { margin-top: var(--sp-5); }
.post-hero .post-meta { margin-top: var(--sp-4); }
.post-meta > span { white-space: nowrap; }

/* The claim: the one sentence the post defends, standing above the body */
.claim { max-width: var(--prose); margin: clamp(32px, 4vw, 48px) auto 0; padding-left: clamp(20px, 3vw, 32px); border-left: 2px solid var(--accent); }

.prose { max-width: var(--prose); margin-inline: auto; }
.claim + .prose { margin-top: clamp(32px, 4vw, 48px); }
/* Every gap inside a post, mapped to craft.do's rhythm: 16 between paragraphs,
   32 above a heading, 16 back down to the text under it. These selectors name
   the tag rather than leaning on `* + *`, because the roles in section 2 set
   `margin: 0` at the same specificity and a universal selector loses to them. */
.prose p + p,
.prose p + ul, .prose p + ol, .prose ul + p, .prose ol + p,
.prose p + figure, .prose figure + p,
.prose h2 + *, .prose h3 + *, .prose blockquote + *, .prose hr + * { margin-top: var(--sp-4); }
.prose h2 { margin-top: clamp(32px, 3.4vw, 44px); }
.prose h3 { margin-top: clamp(26px, 2.6vw, 34px); }
.prose ul, .prose ol { padding-left: 1.35em; }
.prose li + li { margin-top: var(--sp-2); }
.prose li::marker { color: var(--ink-3); }
.prose blockquote { margin: clamp(32px, 4vw, 48px) 0; padding-left: clamp(20px, 3vw, 32px); border-left: 1px solid var(--hair-2); }
.prose blockquote cite { display: block; margin-top: var(--sp-3); font-style: normal; }
.prose hr { margin-block: clamp(40px, 5vw, 64px); border: 0; border-top: 1px solid var(--hair); }
.prose figure { margin: clamp(32px, 4vw, 48px) 0; }
.prose figcaption { margin-top: var(--sp-3); }

/* Sources: the post's receipts, a hairline list under the body */
.sources { max-width: var(--prose); margin: clamp(48px, 6vw, 80px) auto 0; padding-top: var(--sp-5); border-top: 1px solid var(--hair); }
.sources ol { margin: var(--sp-4) 0 0; padding-left: 1.35em; }
.sources li + li { margin-top: var(--sp-3); }

/* Next post, the closing band of a post: .cs-next's type (eyebrow, title
   link, the actions) on the post's own ground. */
.post-next { text-align: center; }
.post-next .t-title { margin-inline: auto; }
.post-next .t-title a { transition: color var(--dur-hover); }
@media (hover: hover) and (pointer: fine) { .post-next .t-title a:hover { color: var(--accent-ink); } }
.post-next .actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; margin-top: clamp(28px, 3vw, 40px); }

@media (max-width: 560px) {
  /* The index row's date and cue drop under the copy and turn left with it;
     the post column is already one column, so only its rails come in. */
  .post-hero .eyebrow, .post-hero .t-display, .post-hero .t-lede, .post-hero .post-meta { text-align: left; }
  .post-hero .post-meta { justify-content: flex-start; }
  .pillar-nav ul { grid-template-columns: minmax(0, 1fr); }
  .cards { grid-template-columns: minmax(0, 1fr); }
  .posts-controls { width: 100%; flex-wrap: nowrap; }
  .post-search { flex: 1; min-width: 0; }
  .post-search input { min-width: 0; width: 100%; }
  .post-hero .wrap > * { margin-inline: 0; }
  .post-hero h1 { max-width: none; }
  .claim, .prose blockquote { padding-left: var(--sp-4); }
  .post-next { text-align: left; }
  .post-next .t-title { margin-inline: 0; }
  .post-next .actions { justify-content: flex-start; }
}

/* --------------------------------------------------------------------------
   14. Chat
   The assistant (main.js initChat, api/chat.js). The primary button waits in
   the bottom corner of every page; it opens a panel built from the site's own
   parts: a surface card with a hairline and the lift shadow, a card head
   (eyebrow, subhead, the round close), the conversation, and a composer that
   wears the gate field's ring. Answers read as plain text on the card, the
   visitor's questions sit to the right on paper-2, and the password field the
   model asks for is a small card of its own. On a phone the panel is a modal
   sheet over the whole screen, and the page behind holds still, as it does
   under the menu. The panel sits under the nav (z 50) and the opener (z 80);
   the modal sheet is in the top layer.
   -------------------------------------------------------------------------- */
:root { --chat-edge: clamp(16px, 2.4vw, 28px); }
.chat-launch {
  position: fixed; z-index: 45;
  right: calc(var(--chat-edge) + env(safe-area-inset-right, 0px)); bottom: calc(var(--chat-edge) + env(safe-area-inset-bottom, 0px));
  box-shadow: var(--shadow-lift);
  opacity: 0; transform: translateY(12px); visibility: hidden;
  transition: opacity var(--dur-ui) var(--ease), transform var(--dur-ui) var(--ease), visibility 0s var(--dur-ui), background var(--dur-hover), box-shadow var(--dur-hover), color var(--dur-hover),
    --sweep-burst var(--dur-ui) var(--ease), --sweep-span var(--dur-ui) var(--ease);
}
.chat-launch.is-in { opacity: 1; transform: none; visibility: visible; transition-delay: 0s; }
.chat-launch.is-in[aria-expanded="true"] { opacity: 0; transform: translateY(8px); visibility: hidden; transition-delay: 0s, 0s, var(--dur-ui), 0s, 0s, 0s; }
.chat-launch.is-in:active { transform: scale(.97); }
@media (max-width: 1023px) { html.nav-locked .chat-launch.is-in { opacity: 0; visibility: hidden; transition-delay: 0s, 0s, var(--dur-ui), 0s, 0s, 0s; } }   /* it goes with the page while the menu sheet is up */
@media (hover: hover) and (pointer: fine) {
  .chat-launch.is-in:hover { transform: translateY(-2px); }
  .chat-launch.is-in:hover:active { transform: translateY(-2px) scale(.97); }
}
.chat-launch svg { width: 18px; height: 18px; }
/* The opener's sweep (after thinkcompany.com's "Ask us anything"): a short
   arc of light runs the pill's edge once every 7.5 s. The light is a hairline
   ring with a soft glow (.chat-launch-sweep), shown through a conic mask
   whose angle is a registered property, so the arc glides rather than jumps.
   On hover the arc laps the pill once inside --dur-ui and opens to three
   times its length, the site's 300ms ceiling for anything a reader
   triggers. The pill stays ink on hover so the green arc has something to
   read against; the lift and the lap are the hover. The ring is accent-2, the lightest green, so it reads on
   ink and on paper alike. */
@property --sweep-rest  { syntax: "<angle>"; inherits: true; initial-value: 110deg; }
@property --sweep-burst { syntax: "<angle>"; inherits: true; initial-value: 0deg; }
@property --sweep-span  { syntax: "<number>"; inherits: true; initial-value: 5; }
.chat-launch {
  --sweep-pad: 40px;     /* room around the pill so the outer glow isn't cut by the mask's box */
  --sweep-fade: 7.5;     /* % of the turn the arc takes to fade in, and again to fade out */
  isolation: isolate;
  animation: chat-sweep 7.5s linear 2;   /* two laps as the page arrives, then it rests: nothing on the site moves for long unasked */
}
@keyframes chat-sweep { from { --sweep-rest: 110deg; } to { --sweep-rest: 470deg; } }
.chat-launch-sweep {
  position: absolute; z-index: -1; inset: calc(var(--sweep-pad) * -1);
  border-radius: inherit; pointer-events: none;
  -webkit-mask-image: var(--sweep-mask); mask-image: var(--sweep-mask);
  --sweep-mask: conic-gradient(from calc(var(--sweep-rest) + var(--sweep-burst) - (var(--sweep-fade) + var(--sweep-span) / 2) * 3.6deg),
    transparent 0%, #000 calc(var(--sweep-fade) * 1%), #000 calc((var(--sweep-fade) + var(--sweep-span)) * 1%),
    transparent calc((var(--sweep-fade) * 2 + var(--sweep-span)) * 1%));
}
.chat-launch-sweep::before {
  content: ""; position: absolute; inset: var(--sweep-pad); border-radius: inherit;
  box-shadow: inset 0 0 0 1px var(--accent-2), inset 0 0 8px color-mix(in srgb, var(--accent-2) 60%, transparent), inset 0 0 20px 2px color-mix(in srgb, var(--accent-2) 20%, transparent),
    0 0 8px color-mix(in srgb, var(--accent-2) 60%, transparent), 0 0 20px 2px color-mix(in srgb, var(--accent-2) 20%, transparent);
}
.chat-launch:hover, .chat-launch:focus-visible { background: var(--ink); color: var(--paper); --sweep-burst: 360deg; --sweep-span: 16; }

.chat {
  position: fixed; z-index: 46; margin: 0; padding: 0;
  inset: auto calc(var(--chat-edge) + env(safe-area-inset-right, 0px)) calc(var(--chat-edge) + env(safe-area-inset-bottom, 0px)) auto;
  width: min(420px, calc(100vw - 2 * var(--chat-edge)));
  height: min(660px, calc(100vh - var(--nav-h) - 2 * var(--chat-edge)));
  height: min(660px, calc(100dvh - var(--nav-h) - 2 * var(--chat-edge)));
  max-width: none; max-height: none;
  border: 1px solid var(--hair); border-radius: var(--radius);
  background: var(--surface); color: var(--ink);
  box-shadow: var(--shadow-lift), 0 2px 8px rgba(20, 16, 12, .06);
  display: none; flex-direction: column; overflow: hidden;
  opacity: 0; transform: translateY(12px) scale(.98); transform-origin: 100% 100%;
  transition: opacity var(--dur-ui) var(--ease), transform var(--dur-ui) var(--ease);
}
.chat[open] { display: flex; }
.chat.is-open { opacity: 1; transform: none; }
.chat::backdrop { background: var(--paper); }

.chat-head { flex: none; display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-4); padding: var(--sp-5) var(--sp-5) var(--sp-4); border-bottom: 1px solid var(--hair); }
.chat-head .t-subhead:focus { outline: none; }        /* focused by script on a touch screen, so the keyboard stays down */
.chat-close { flex: none; margin: -6px -8px 0 0; }

.chat-log {
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-5);
}
.chat-log:focus-visible { outline: 2px solid var(--accent); outline-offset: -4px; border-radius: 8px; }
.chat-msg { max-width: 100%; color: var(--ink); overflow-wrap: anywhere; }
.chat-msg.is-user { align-self: flex-end; max-width: 85%; padding: 9px 14px; border-radius: 14px 14px 4px 14px; background: color-mix(in srgb, var(--ink) 7%, var(--surface)); white-space: pre-wrap; }
.chat-msg p + p, .chat-msg p + ul, .chat-msg ul + p { margin-top: var(--sp-2); }
.chat-msg ul { margin: 0; padding-left: 1.15em; }
.chat-msg li + li { margin-top: 4px; }
.chat-msg li::marker { color: var(--ink-3); }
.chat-msg .chat-sources { padding: 0; }
.chat-msg .chat-sources li { margin: 0; }
.chat-error { color: var(--danger); }
.chat-starters { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.chat-starters .btn { white-space: normal; text-align: left; line-height: 1.3; }   /* a long suggestion wraps inside its button */

/* Waiting for the first word: three dots that rise in turn. */
.chat-wait { display: inline-flex; gap: 5px; padding: 8px 0; }
.chat-wait i { width: 6px; height: 6px; border-radius: 50%; background: var(--ink-3); animation: chat-wait 1.2s var(--ease-soft) infinite; }
.chat-wait i:nth-child(2) { animation-delay: .15s; }
.chat-wait i:nth-child(3) { animation-delay: .3s; }
@keyframes chat-wait { 0%, 60%, 100% { opacity: .3; transform: none; } 30% { opacity: 1; transform: translateY(-3px); } }

/* The password field: the gate's field in a small card of its own. */
.chat-unlock { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4); border: 1px solid var(--hair); border-radius: var(--radius-sm); background: var(--paper); }
.chat-unlock label { color: var(--ink); }
.chat-unlock-row { display: flex; gap: var(--sp-2); }
.chat-unlock input { flex: 1; min-width: 0; padding: 9px 14px; border-radius: 12px; border: 1px solid var(--hair-2); background: var(--surface); color: var(--ink); }
.chat-unlock input:focus { outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent); }
.chat-unlock input[aria-invalid="true"] { border-color: var(--danger); }
.chat-unlock input[aria-invalid="true"]:focus { outline-color: var(--danger); }
.chat-unlock .t-small a { color: var(--accent-ink); }
.chat-unlocked { display: inline-flex; align-items: center; gap: 8px; align-self: flex-start; padding: 3px 10px; border-radius: 20px; background: var(--glow); color: var(--accent-ink); }

/* The walkthrough offer after the third answer: John's own voice, on paper. */
.chat-offer { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-4); border: 1px solid var(--hair); border-radius: var(--radius-sm); background: var(--paper); }
.chat-offer-text { color: var(--ink); }
.chat-offer-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* The composer: a field that takes the gate field's ring while you type. The
   box grows with the question up to six lines (field-sizing), and holds two
   lines where the browser can't grow it. Its corner is the send button's
   plus the 6px inset, so the button sits concentric in it. */
.chat-form {
  flex: none; display: flex; align-items: flex-end; gap: var(--sp-2);
  margin: var(--sp-1) var(--sp-5) 0; padding: 6px 6px 6px 16px;   /* on the head's and the log's inset */
  border: 1px solid var(--hair-2); border-radius: calc(var(--radius-btn) + 6px); background: var(--surface);
  transition: border-color var(--dur-hover);
}
.chat-form:focus-within { border-color: var(--accent); outline: 2px solid var(--accent); outline-offset: 2px; }
.chat-form textarea {
  flex: 1; min-width: 0; max-width: none; margin: 0; padding: 5px 0; border: 0; background: none; color: var(--ink); resize: none; outline: none;
  field-sizing: content; min-block-size: calc(1lh + 10px); max-block-size: calc(6lh + 10px);
}
@supports not (field-sizing: content) { .chat-form textarea { height: calc(2lh + 10px); } }
.chat-send {
  flex: none; width: 36px; height: 36px; padding: 0; border: 0; border-radius: var(--radius-btn); display: grid; place-items: center; cursor: pointer;
  background: var(--ink); color: var(--paper);
  transition: background var(--dur-hover), color var(--dur-hover), transform var(--dur-press) var(--ease);
}
.chat-send:active { transform: scale(.94); }
.chat-send:disabled { background: var(--paper-2); color: var(--ink-3); cursor: default; transform: none; }
@media (hover: hover) and (pointer: fine) { .chat-send:not(:disabled):hover { background: var(--accent); color: var(--surface); } }
.chat-send:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.chat-send svg { width: 18px; height: 18px; }
.chat-foot { flex: none; padding: var(--sp-3) var(--sp-5) var(--sp-4); color: var(--ink-3); text-align: center; text-wrap: balance; max-width: none; }

@media (max-width: 640px) {
  /* On a phone the button is the mark alone, a square one, and it sits in the
     bar beside the menu toggle rather than floating over the page, so it never
     covers a line of text; its name stays for screen readers. It goes with the
     bar while the menu sheet is open. */
  .chat-launch {
    top: calc((var(--nav-h) - 42px) / 2); bottom: auto; right: calc(var(--gutter) + 54px); z-index: 51;   /* past the toggle (42px, on the gutter) and the bar's 12px gap */
    width: 42px; height: 42px; min-height: 0; padding: 0; justify-content: center; box-shadow: none;
  }
  .chat-launch .chat-launch-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .chat-launch svg { width: 18px; height: 18px; }
  /* The sheet: the whole screen, flush, rising in. The close sits where the
     menu's toggle does; main.js keeps its height to the visible area while
     the keyboard is up. */
  .chat:modal { inset: 0; width: 100%; height: 100%; height: 100dvh; border: 0; border-radius: 0; box-shadow: none; transform: translateY(16px); }
  .chat:modal.is-open { transform: none; }
  .chat-head { padding: calc((var(--nav-h) - 42px) / 2 + env(safe-area-inset-top, 0px)) var(--gutter) var(--sp-4); }   /* the close lands where the menu toggle is */
  .chat-close { margin: 0; }
  .chat-log { padding: var(--sp-5) var(--gutter); }
  .chat-form { margin-inline: var(--gutter); }
  .chat-foot { padding: var(--sp-3) var(--gutter) calc(var(--sp-3) + env(safe-area-inset-bottom, 0px)); }
  html.chat-locked { overflow: hidden; }
}
@media (prefers-reduced-motion: reduce) {
  .chat, .chat.is-open, .chat:modal, .chat-launch, .chat-launch.is-in { transform: none; }
  .chat, .chat-launch { transition-property: opacity, visibility, background, color; transition-duration: 0s; }
  .chat-wait i { animation: none; opacity: .6; }
  .chat-launch { animation: none; }
  .chat-launch:hover, .chat-launch:focus-visible { --sweep-burst: 0deg; --sweep-span: 5; }
}

/* Sources: the pages an answer drew on, one small card each under the text
   (after ask.lukew.com). The page's own cover, a serif title and a mono label
   saying what kind of page it is. The answer's text sits in .chat-answer so
   the cards can follow it inside the same bubble. */
.chat-answer + .chat-sources, .chat-answer + .chat-card, .chat-sources + .chat-card, .chat-card + .chat-card { margin-top: var(--sp-3); }
.chat-sources { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: var(--sp-2); }
.chat-sources li { min-width: 0; display: flex; }
.chat-msg .chat-source {
  flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; padding: var(--sp-2);
  border: 1px solid var(--hair); border-radius: var(--radius-sm); background: var(--paper); color: var(--ink); text-decoration: none;
  transition: border-color var(--dur-hover), transform var(--dur-press) var(--ease);
}
.chat-msg .chat-source:hover { border-color: var(--accent); }
.chat-msg .chat-source:active { transform: scale(.98); }
.chat-msg .chat-source:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-color: var(--accent); }
.chat-source-thumb { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: 6px; background-color: var(--panel-base); background-image: var(--grain), var(--panel); background-size: var(--grain-size), auto; background-blend-mode: multiply, normal; }   /* a page without a cover gets the panel ground */
.chat-source-thumb[hidden] { display: none; }
.chat-source-title { font-family: var(--font-display); font-size: 1rem; line-height: 1.25; color: var(--ink); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.chat-source-label { margin-top: auto; font-family: var(--font-mono); font-size: var(--t-micro); line-height: var(--lh-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; font-weight: 500; color: var(--ink-3); }   /* the micro role */

/* A cited section on the page you are on: rung once by a hairline that fades
   (main.js adds the class to the section's column and takes it off after). */
.chat-cited { outline: 1px solid var(--accent); outline-offset: 12px; border-radius: 6px; scroll-margin-top: calc(var(--nav-h) + 24px); animation: chat-cited 1.6s var(--ease) both; }
@keyframes chat-cited { 0%, 30% { outline-color: var(--accent); } 100% { outline-color: transparent; } }

/* Cards the model asks for with its tools: a case study (logo, title, summary,
   the outcome once the page is unlocked, the link) and John's contact buttons,
   both on paper like the offer. Links in them are buttons, not underlined text. */
.chat-card { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); padding: var(--sp-4); border: 1px solid var(--hair); border-radius: var(--radius-sm); background: var(--paper); }
.chat-card .case-logo { height: 18px; margin: 0 0 var(--sp-1); }
.chat-card-title { font-family: var(--font-display); font-size: 1.25rem; line-height: 1.2; letter-spacing: -.01em; color: var(--ink); text-wrap: balance; }
.chat-card-summary { color: var(--ink-2); }
.chat-card-stat { font-family: var(--font-display); font-size: 2.25rem; line-height: 1; letter-spacing: -.02em; color: var(--accent-ink); font-variant-numeric: lining-nums tabular-nums; margin-top: var(--sp-1); }
.chat-card-note { color: var(--ink-3); max-width: 30ch; }
.chat-card .chips { margin-top: var(--sp-1); }
.chat-card .chip { font-size: var(--t-small); padding: 4px 10px; }
.chat-card-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-top: var(--sp-2); }
.chat-card-lock { color: var(--ink-3); }
.chat-msg .chat-card .btn { text-decoration: none; }
.chat-msg .chat-card .btn-primary { color: var(--paper); }
.chat-msg .chat-card .btn-primary:hover { color: var(--surface); }
.chat-msg .chat-card .btn-ghost { color: var(--ink); }
.chat-msg .chat-card .btn-ghost:hover { color: var(--accent-ink); }

/* Who is asking: a second row of suggestions on the empty panel, each a
   longer question than its label. It goes with the starters after the first
   question. */
.chat-audience { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.chat-audience-label { font-family: var(--font-mono); font-size: var(--t-micro); line-height: var(--lh-micro); letter-spacing: var(--ls-micro); text-transform: uppercase; font-weight: 500; color: var(--ink-3); }   /* the micro role */
.chat-audience .btn-ghost { background: var(--glow); border-color: transparent; color: var(--accent-ink); }
.chat-audience .btn-ghost:hover { border-color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .chat-cited { animation: none; }
  .chat-msg .chat-source:active { transform: none; }
}
@media print { .chat-launch, .chat { display: none !important; } }

/* --------------------------------------------------------------------------
   15. The palette
   ⌘K (main.js initPalette). The bar's Search row (a word and a key cap,
   ⌘K or Ctrl K by the class the head script sets; the word alone in the
   phone menu) is in each page's markup, so the bar never shifts on load. The dialog is a surface card at
   the top of the screen over a blurred page: one field, the rows that
   match, and a foot that names the keys. The last row hands the question
   to the assistant.
   -------------------------------------------------------------------------- */
.nav-kbd { display: inline-flex; align-items: center; gap: 8px; padding: 0; border: 0; background: none; cursor: pointer; transition: color var(--dur-hover); }   /* face, size and ink from the nav's role (section 2) */
.nav-kbd:hover { color: var(--ink); }
html:not(.js) .nav-search { display: none; }   /* nothing to open without the script */
.nav-kbd kbd::before { content: "Ctrl K"; }
.mac .nav-kbd kbd::before { content: "⌘K"; }
.nav-kbd kbd, .palette-foot kbd { font-family: var(--font-mono); font-size: var(--t-micro); line-height: 1; letter-spacing: .02em; padding: 3px 5px; border: 1px solid var(--hair-2); border-bottom-width: 2px; border-radius: 5px; color: var(--ink-3); }
html.palette-open { overflow: hidden; }
.palette {
  position: fixed; inset: 10vh auto auto 50%; transform: translateX(-50%);
  width: min(640px, calc(100vw - 2 * var(--gutter))); max-width: none; max-height: 78vh; max-height: 78dvh; margin: 0; padding: 0;
  display: none; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden;
  border: 1px solid var(--hair); border-radius: var(--radius); background: var(--surface); color: var(--ink); box-shadow: var(--shadow-lift);
}
.palette[open] { display: grid; animation: palette-in var(--dur-ui) var(--ease); }
@keyframes palette-in { from { opacity: 0; transform: translateX(-50%) translateY(-6px) scale(.985); } }
.palette::backdrop { background: var(--scrim); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
.palette-form { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-5); border-bottom: 1px solid var(--hair); }   /* the close button's edge on the rows' labels */
.palette-glass { width: 20px; height: 20px; flex: none; color: var(--ink-3); }
.palette-input { flex: 1; min-width: 0; padding: var(--sp-3) 0; border: 0; background: none; color: var(--ink); font-family: var(--font-body); font-size: var(--t-lede); line-height: var(--lh-lede); outline: 0; }
.palette-input { text-overflow: ellipsis; }
.palette-input::placeholder { color: var(--ink-3); }
.palette-close { width: 34px; height: 34px; flex: none; display: grid; place-items: center; padding: 0; border: 1px solid var(--hair-2); border-radius: var(--radius-btn); background: none; color: var(--ink); cursor: pointer; }
.palette-close svg { width: 14px; height: 14px; }
.palette-list { list-style: none; margin: 0; padding: var(--sp-2); overflow-y: auto; overscroll-behavior: contain; }
.palette-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; gap: 2px var(--sp-4); padding: var(--sp-3) var(--sp-4); border-radius: var(--radius-sm); cursor: pointer; }
.palette-row[aria-selected="true"] { background: color-mix(in srgb, var(--ink) 7%, var(--surface)); }
.palette-title { font-family: var(--font-body); font-size: var(--t-body); line-height: var(--lh-body); font-weight: 500; color: var(--ink); min-width: 0; }
.palette-kind { font-family: var(--font-mono); font-size: var(--t-micro); line-height: var(--lh-body); letter-spacing: var(--ls-micro); text-transform: uppercase; color: var(--ink-3); white-space: nowrap; }
.palette-desc { grid-column: 1 / -1; font-family: var(--font-body); font-size: var(--t-small); line-height: var(--lh-small); color: var(--ink-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-row.ask .palette-title { color: var(--accent-ink); }
.palette-row.none { cursor: default; }
.palette-foot { display: flex; gap: var(--sp-5); align-items: center; padding: var(--sp-3) var(--sp-5); border-top: 1px solid var(--hair); color: var(--ink-3); }   /* the micro role, as every label is */
.palette-foot span { display: inline-flex; align-items: center; gap: 6px; }
@media (max-width: 640px) {
  .palette-row { grid-template-columns: minmax(0, 1fr); }   /* the label drops under the title so the title keeps the width */
  .palette-kind { grid-row: 2; }
  .palette-desc { grid-row: 3; }
  .palette { inset: var(--sp-3) auto auto 50%; width: calc(100vw - 2 * var(--sp-3)); max-height: calc(100dvh - 2 * var(--sp-3)); }
  .palette-foot { display: none; }
  .palette-close { width: 42px; height: 42px; }
}
@media (prefers-reduced-motion: reduce) { .palette[open] { animation: none; } }
