/* ============================================================
   Volume One Digital — shared design system
   Extends the scroll-film homepage. Locked tokens, do not drift.
   ============================================================ */
:root{
  --peri:#7186B6; --electric:#7E97E8; --ink:#14173A; --coral:#FF6B5E;
  --lime:#C6F24E; --sun:#FFC845; --paper:#F7F5FF;
  /* Service palette — one hue per service, shared by the home index and the
     services cards so the two pages always match. Hue AND lightness both step
     between neighbors so the seven stay apart under red-green colorblindness;
     every value keeps ink text ≥4.5:1. Paid Media keeps --electric and
     Strategic keeps --coral. */
  --svc-green:#43A758; --svc-orange:#FFAD33; --svc-teal:#3FBFAE;
  --svc-magenta:#E879C8; --svc-violet:#B49BF5;
  --ink-2:#1b1f4a;            /* raised ink surface */
  --ink-line:rgba(247,245,255,.12);
  --display:'Bricolage Grotesque',sans-serif;
  --body:'Instrument Sans',sans-serif;
  --mono:'Archivo',sans-serif;  /* label face: swapped off Space Mono (Charles) */
  --edge:7vw;                 /* page gutter, matches homepage sections */
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{background:var(--ink);color:var(--paper);font-family:var(--body);
  font-size:16px;line-height:1.5;overflow-x:hidden;
  text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}
::selection{background:var(--electric);color:#fff}
a{color:inherit}
img{max-width:100%;display:block}
:focus-visible{outline:2.5px solid var(--electric);outline-offset:3px;border-radius:3px}

/* ---------- skip link (a11y) ---------- */
.skip{position:absolute;left:-999px;top:8px;z-index:200;background:var(--electric);color:#fff;
  font-family:var(--mono);font-size:12px;letter-spacing:.08em;padding:10px 16px;border-radius:8px}
.skip:focus{left:12px}

/* ============================================================
   NAV RIBBON  (shared across interior pages; the homepage keeps
   its own adaptive/film-aware header but reuses these link styles)
   ============================================================ */
.site-header{position:sticky;top:0;z-index:80;display:flex;align-items:center;gap:28px;
  padding:18px var(--edge);background:rgba(20,23,58,.72);backdrop-filter:blur(14px);
  -webkit-backdrop-filter:blur(14px);border-bottom:1px solid var(--ink-line)}
.site-header .lock{display:flex;align-items:center;gap:12px;text-decoration:none;color:var(--paper);margin-right:auto}
.site-header .lock img{width:32px;height:32px;filter:brightness(0) invert(1)}
.site-header .word{font-family:var(--display);font-weight:800;font-size:15px;letter-spacing:.06em;line-height:1.05}
.site-header .word small{display:block;font-family:var(--mono);font-weight:400;font-size:9px;letter-spacing:.42em;opacity:.7}
.nav{display:flex;align-items:center;gap:26px}
.nav a{font-family:var(--mono);font-size:12px;letter-spacing:.14em;text-transform:uppercase;
  text-decoration:none;color:var(--paper);opacity:.72;position:relative;padding:6px 0;transition:opacity .2s}
.nav a::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1.5px;
  background:var(--electric);transform:scaleX(0);transform-origin:left;transition:transform .28s cubic-bezier(.2,.7,.2,1)}
.nav a:hover{opacity:1}
.nav a:hover::after,.nav a[aria-current="page"]::after{transform:scaleX(1)}
.nav a[aria-current="page"]{opacity:1;color:var(--electric)}
.btn-cta{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-decoration:none;
  color:var(--paper);border:1.5px solid rgba(247,245,255,.55);border-radius:999px;padding:9px 18px;
  white-space:nowrap;transition:background .22s,border-color .22s,color .22s}
.btn-cta:hover{background:var(--electric);border-color:var(--electric);color:#fff}
.nav-toggle{display:none}

@media (max-width:860px){
  .site-header{gap:16px;padding:14px 22px}
  .nav-toggle{display:inline-flex;flex-direction:column;gap:5px;background:none;border:0;cursor:pointer;padding:6px;margin-left:auto;position:relative;z-index:95}
  .nav-toggle span{width:22px;height:2px;background:var(--paper);border-radius:2px;transition:.25s}
  .site-header .btn-cta{display:none}
  .nav{position:fixed;top:0;right:0;height:100vh;width:min(300px,80vw);flex-direction:column;
    align-items:flex-start;justify-content:center;gap:6px;padding:0 34px;
    background:var(--ink);border-left:1px solid var(--ink-line);z-index:90;
    visibility:hidden;transform:translateX(100%);
    transition:transform .38s cubic-bezier(.2,.7,.2,1),visibility .38s}
  .nav.open{transform:none;visibility:visible}
  .nav a{font-size:16px;letter-spacing:.1em;padding:12px 0;width:100%}
  .nav .btn-cta{display:inline-block;margin-top:16px;border-color:var(--electric)}
}

/* ============================================================
   PAGE PRIMITIVES
   ============================================================ */
main{display:block}
.wrap{max-width:1280px;margin:0 auto;padding:0 var(--edge)}
.eyebrow{font-family:var(--mono);font-size:12px;letter-spacing:.34em;text-transform:uppercase;color:var(--peri)}
/* padding-block, per the note on .page-hero: these all sit on .wrap and the
   shorthand was cancelling its horizontal gutter. */
.section{padding-block:clamp(64px,9vw,120px)}
.lead{font-size:clamp(17px,1.5vw,20px);line-height:1.6;color:rgba(247,245,255,.74);max-width:60ch}

/* page hero */
/* padding-block, not the padding shorthand: .page-hero always sits on a .wrap, and
   the shorthand was resetting that wrap's `padding:0 var(--edge)` gutter to zero,
   so every subpage headline and standfirst sat flush against the viewport edge. */
.page-hero{padding-block:clamp(72px,12vw,150px) clamp(40px,6vw,72px);position:relative}
.page-hero .eyebrow{margin-bottom:22px}
.page-hero h1{font-family:var(--display);font-weight:800;letter-spacing:-.03em;line-height:.94;
  font-size:clamp(52px,9vw,128px)}
.page-hero h1 em{font-style:normal;color:var(--electric)}
.page-hero .lead{margin-top:28px}
.hero-hearts{position:absolute;inset:0;pointer-events:none;overflow:hidden;z-index:-1}

/* buttons */
.btn{display:inline-flex;align-items:center;gap:10px;font-family:var(--mono);font-size:13px;
  letter-spacing:.06em;text-decoration:none;border-radius:999px;padding:15px 28px;cursor:pointer;border:0;
  transition:transform .2s,background .22s,color .22s}
.btn-primary{background:var(--electric);color:#fff}
.btn-primary:hover{background:var(--coral);transform:translateY(-2px)}
.btn-ghost{border:1.5px solid rgba(247,245,255,.5);color:var(--paper);background:none}
.btn-ghost:hover{border-color:var(--lime);color:var(--lime)}

/* ============================================================
   BENTO — case-study grid (irregular, clickable)
   ============================================================ */
.bento{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;
  grid-auto-rows:minmax(180px,auto);margin-top:52px}
.tile{position:relative;overflow:hidden;border-radius:24px;border:0;cursor:pointer;
  text-align:left;color:var(--paper);background:var(--ink-2);
  display:flex;flex-direction:column;justify-content:flex-end;min-height:180px;
  isolation:isolate;transition:transform .4s cubic-bezier(.2,.7,.2,1)}
.tile:hover{transform:translateY(-6px)}
.tile-media{position:absolute;inset:0;z-index:-2;background-size:cover;background-position:center;
  transition:transform .6s cubic-bezier(.2,.7,.2,1)}
/* Real assets in the media slot are elements (an <img>, or a muted <video> loop),
   not background-image, so they need cover behavior of their own. The 4:5 loop
   crops bias faces toward the top; mirror that for the element case. */
img.tile-media,video.tile-media{width:100%;height:100%;object-fit:cover;object-position:50% 35%}
.tile:hover .tile-media{transform:scale(1.06)}
.tile::after{content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,rgba(20,23,58,0) 30%,rgba(11,13,34,.86) 100%)}
.tile-body{padding:24px 24px 26px;display:flex;flex-direction:column;gap:8px}
.tile .client{font-family:var(--mono);font-size:11px;letter-spacing:.24em;text-transform:uppercase;color:var(--lime)}
.tile h3{font-family:var(--display);font-weight:800;font-size:clamp(20px,2vw,30px);line-height:1.02;letter-spacing:-.01em}
.tile .metric{font-family:var(--display);font-weight:800;font-size:clamp(28px,3.4vw,46px);line-height:1;letter-spacing:-.02em}
.tile .metric small{font-family:var(--mono);font-weight:400;font-size:11px;letter-spacing:.18em;
  display:block;margin-top:6px;color:rgba(247,245,255,.7)}
.tile .peek{font-family:var(--mono);font-size:12px;letter-spacing:.04em;color:rgba(247,245,255,.78);max-width:34ch}
.tile .go{position:absolute;top:20px;right:20px;width:38px;height:38px;border-radius:50%;
  display:grid;place-items:center;background:rgba(247,245,255,.14);color:#fff;
  transform:translateY(-6px);opacity:0;transition:.3s}
.tile:hover .go{transform:none;opacity:1}
/* The ↗ was the only signal a tile opens anything, and it was hover-only: invisible to
   keyboard users and permanently invisible on touch, which is how this page actually
   gets read. .bcard already had the focus-visible half; .tile never got it. */
.tile:focus-visible .go{transform:none;opacity:1}
.tile:focus-visible{outline:2px solid var(--lime);outline-offset:3px}
@media (hover:none){.tile .go{transform:none;opacity:.6}}
.tile.solid{--fill:var(--electric)}
.tile.solid::after{display:none}
.tile.solid .tile-media{display:none}
.tile.solid{background:var(--fill);color:var(--ink)}
.tile.solid .client{color:rgba(20,23,58,.72)}
.tile.solid .metric small,.tile.solid .peek{color:rgba(20,23,58,.72)}
.tile.solid .go{background:rgba(20,23,58,.14);color:var(--ink)}
/* spans */
.tile.w2{grid-column:span 2}.tile.w3{grid-column:span 3}
.tile.w4{grid-column:span 4}
.tile.h2{grid-row:span 2}
/* "Your brand" — the aspirational slot. An <a>, not a <button>: it goes to Contact
   rather than opening a panel, because there is no case study behind it. Shorter
   than a real tile so it reads as an invitation closing the grid, not a fifth study. */
.tile.aspire{text-decoration:none;min-height:150px;justify-content:center}
.tile.aspire .tile-body{gap:10px}
.tile.aspire h3{font-size:clamp(24px,2.6vw,38px)}
/* The aspire tile's CTA pill: always visible — hover-only affordances on tiles
   were already an a11y bug once (see .tile .go above). Right-centred against the
   left-aligned copy on the wide tile; the tile's own hover lift + the btn-primary
   coral swap are the feedback, so the pill itself must not translate. */
.tile-cta{position:absolute;right:24px;top:50%;transform:translateY(-50%)}
.tile:hover .tile-cta{background:var(--coral);transform:translateY(-50%)}
@media (max-width:900px){
  .bento{grid-template-columns:repeat(2,1fr)}
  .tile.w3,.tile.w4{grid-column:span 2}
}
@media (max-width:560px){
  .bento{grid-template-columns:1fr}
  .tile.w2,.tile.w3,.tile.w4{grid-column:span 1}.tile.h2{grid-row:span 1}
  .tile-cta{position:static;transform:none;margin:0 24px 24px;align-self:flex-start;order:1}
  .tile:hover .tile-cta{transform:none}
}

/* ============================================================
   QUICK-LOOK panel (slide-in, protected focus)
   ============================================================ */
.ql-scrim{position:fixed;inset:0;z-index:120;background:rgba(8,9,24,.62);
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  opacity:0;visibility:hidden;transition:opacity .35s,visibility .35s}
.ql-scrim.open{opacity:1;visibility:visible}
.ql{position:fixed;top:0;right:0;bottom:0;z-index:121;width:min(560px,94vw);
  background:var(--ink-2);border-left:1px solid var(--ink-line);
  transform:translateX(100%);transition:transform .42s cubic-bezier(.2,.7,.2,1);
  overflow-y:auto;padding:34px 34px 60px}
.ql.open{transform:none}
.ql-close{position:sticky;top:0;float:right;margin:-8px -8px 0 0;width:42px;height:42px;border-radius:50%;
  border:0;background:rgba(247,245,255,.1);color:var(--paper);cursor:pointer;font-size:20px;display:grid;place-items:center}
.ql-close:hover{background:rgba(247,245,255,.2)}
.ql .client{font-family:var(--mono);font-size:11px;letter-spacing:.24em;text-transform:uppercase;color:var(--lime)}
.ql h2{font-family:var(--display);font-weight:800;font-size:clamp(28px,4vw,42px);line-height:1.02;
  letter-spacing:-.02em;margin:12px 0 18px}
.ql p{color:rgba(247,245,255,.76);margin-bottom:16px}
/* --cov was never defined anywhere in the repo; dropped. */
.ql .ql-cover{border-radius:18px;overflow:hidden;margin:20px 0 26px;aspect-ratio:16/9;background:#0c0e22 center/cover}
.ql-actions{margin-top:28px;display:flex;gap:12px;flex-wrap:wrap}
/* Discipline + campaign chips, sitting under the title. A wrapped row of pills,
   closed by a hairline: the rule is what makes the band read as metadata that has
   ENDED before the story starts, rather than as the story's first line. Long
   campaign names wrap inside their pill rather than overflowing the panel. */
.ql-tags{list-style:none;display:flex;flex-wrap:wrap;gap:7px;
  margin:14px 0 26px;padding:0 0 24px;border-bottom:1px solid var(--ink-line)}
.ql-tags li{font-family:var(--mono);font-size:10px;font-weight:500;letter-spacing:.14em;
  text-transform:uppercase;line-height:1.4;padding:5px 11px;border-radius:999px;
  color:rgba(247,245,255,.68);box-shadow:inset 0 0 0 1px rgba(247,245,255,.22)}
/* the case-study arc: challenge / what we did / results */
.cs-beat{margin-bottom:22px}
.cs-beat .k{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.24em;
  text-transform:uppercase;color:var(--lime);margin-bottom:8px}
.cs-beat p{margin-bottom:0}
/* A real cover in the 16/9 slot (work.html): an image cropped to it, or a clip
   that plays in it. The figure takes over the slot's margins so a caption can
   sit between the media and the story. */
.ql-lead{margin:20px 0 26px}
.ql-lead .ql-cover{margin:0}
.ql-cover img,.ql-cover video{width:100%;height:100%;object-fit:cover;display:block}
/* The study's real work (work.html). Two-up in the narrow panel: portrait and
   square pieces pair off, .wide ones run the full width. */
.ql-visuals{margin-top:26px}
.ql-vgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px 14px;align-items:start}
.ql-visuals figure{margin:0}
.ql-visuals figure.wide{grid-column:1/-1}
/* An odd one out closes its group centred, rather than leaving a hole beside it. */
.ql-vgrid figure:last-child:nth-child(odd):not(.wide){grid-column:1/-1;
  width:calc(50% - 7px);justify-self:center}
.ql-visuals img,.ql-visuals video{width:100%;height:auto;border-radius:14px;display:block}
.ql-visuals video{background:#0c0e22;object-fit:cover}
.ql-visuals figcaption,.ql-lead figcaption{font-family:var(--mono);font-size:11px;letter-spacing:.1em;
  line-height:1.45;color:rgba(247,245,255,.55);margin-top:8px}
.ql-group{font-family:var(--mono);font-weight:500;font-size:10px;letter-spacing:.24em;
  text-transform:uppercase;color:var(--lime);margin:28px 0 12px}
.ql-group:first-child{margin-top:0}

/* ============================================================
   DATA-VIZ  (per the analytics reference)
   ============================================================ */
.viz-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin:22px 0}
.stat{border-radius:20px;padding:22px 22px 26px;color:var(--ink);display:flex;flex-direction:column;gap:6px}
/* Sized for a 4-up impact row in a 560px panel. Some impact values are words
   ("Dozens") rather than figures, so this has to wrap gracefully, not overflow. */
.stat .n{font-family:var(--display);font-weight:800;font-size:clamp(30px,3.8vw,44px);line-height:.95;
  letter-spacing:-.02em;overflow-wrap:anywhere}
/* Hierarchy comes from size and tracking, not transparency. The .78 alpha was costing
   ~2 points of contrast on every chip for a distinction the type already carries. */
.stat .l{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase}
/* Ink on every chip, not white. White on --coral measured 2.79:1 and on --electric
   2.81:1 for the 44px figure, both under the 3:1 large-text floor; the labels were
   worse at 2.23 / 2.30. Because STAT_COLORS cycles from index 0, those two chips are
   always stats #1 and #2, so the headline numbers were the least readable on the page.
   Ink takes them to 5.19 and 6.16. No brand token changes; periwinkle stays periwinkle. */
.stat.sun{background:var(--sun)}.stat.coral{background:var(--coral);color:var(--ink)}
.stat.lime{background:var(--lime)}.stat.peri{background:var(--electric);color:var(--ink)}
.bars{display:flex;flex-direction:column;gap:12px;margin:18px 0}
.bar{display:grid;grid-template-columns:1fr auto;align-items:center;gap:14px}
.bar .track{height:26px;border-radius:8px;background:rgba(247,245,255,.08);overflow:hidden}
.bar .fill{height:100%;border-radius:8px;background:linear-gradient(90deg,var(--peri),var(--electric));
  display:flex;align-items:center;padding-left:12px;font-family:var(--mono);font-size:11px;color:#fff;
  width:var(--w);transition:width 1s cubic-bezier(.2,.7,.2,1)}
.bar .lbl{font-family:var(--mono);font-size:12px;letter-spacing:.04em;color:rgba(247,245,255,.82);white-space:nowrap}
.panel{background:var(--ink);border:1px solid var(--ink-line);border-radius:22px;padding:26px 26px 30px}
.panel h4{font-family:var(--display);font-weight:800;font-size:18px;margin-bottom:16px}
.linechart{width:100%;height:auto;display:block}
.linechart .grid-l{stroke:rgba(247,245,255,.08);stroke-width:1}
.linechart .line{fill:none;stroke:var(--electric);stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.linechart .area{fill:url(#lg);opacity:.5}
.linechart .dot{fill:var(--lime)}
.badge{font-family:var(--mono);font-size:11px;fill:#fff}
.badge-bg{fill:var(--ink);stroke:var(--ink-line)}

/* ============================================================
   SERVICES page
   ============================================================ */
.svc-list{margin-top:56px;border-top:1px solid var(--ink-line)}
.svc-row{display:grid;grid-template-columns:auto 1fr auto;gap:28px;align-items:start;
  padding:34px 0;border-bottom:1px solid var(--ink-line);position:relative;transition:transform .3s}
.svc-row:hover{transform:translateX(12px)}
.svc-row .idx{font-family:var(--mono);font-size:12px;letter-spacing:.2em;color:var(--peri);padding-top:8px}
.svc-row h3{font-family:var(--display);font-weight:800;font-size:clamp(24px,3vw,38px);letter-spacing:-.01em;line-height:1.02}
.svc-row .desc{color:rgba(247,245,255,.72);margin-top:12px;max-width:52ch}
.svc-row .caps{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
.svc-row .cap{font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--paper);
  border:1px solid var(--ink-line);border-radius:999px;padding:6px 12px}
.svc-row .accent{width:10px;height:10px;border-radius:50%;margin-top:10px}
.a-electric{background:var(--electric)}.a-coral{background:var(--coral)}.a-lime{background:var(--lime)}
.a-sun{background:var(--sun)}.a-peri{background:var(--peri)}.a-ink{background:#8b93d6}
@media (max-width:720px){.svc-row{grid-template-columns:auto 1fr;gap:16px}.svc-row .accent{display:none}}

/* ============================================================
   BOUNCE CARDS — services (paper tiles on the ink page, bounce kept)
   Title + value line up top; flat-token capability panel bounces
   up on hover. Tiles are real links to contact with the service
   preselected. Panel colors mirror the home service index 1:1.
   ============================================================ */
.bounce-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px;margin-top:52px}
.bounce-grid + .bounce-grid{margin-top:16px}
.bcard{grid-column:span 12;position:relative;overflow:hidden;border-radius:24px;
  background:var(--paper);border:1px solid rgba(20,23,58,.08);color:var(--ink);
  display:flex;flex-direction:column;gap:16px;padding:28px 28px 0;
  text-decoration:none;transition:transform .3s cubic-bezier(.2,.7,.2,1)}
.bcard:hover{transform:scale(.97) rotate(-1deg)}
.bcard .go{position:absolute;top:20px;right:20px;width:38px;height:38px;border-radius:50%;
  display:grid;place-items:center;background:rgba(20,23,58,.12);color:var(--ink);
  transform:translateY(-6px);opacity:0;transition:.3s}
.bcard:hover .go,.bcard:focus-visible .go{transform:none;opacity:1}
.bcard h2{font-family:var(--display);font-weight:800;font-size:clamp(21px,2.2vw,28px);
  line-height:1.06;letter-spacing:-.01em;padding-right:44px}
.bcard .bc-desc{font-size:14.5px;line-height:1.5;color:rgba(20,23,58,.7);max-width:46ch}
.bc-panel{position:relative;margin-top:auto;translate:0 32px;border-radius:18px 18px 0 0;
  padding:22px 24px 54px;color:var(--ink);
  transition:translate .25s cubic-bezier(.2,.7,.2,1),rotate .25s cubic-bezier(.2,.7,.2,1)}
.bcard:hover .bc-panel{translate:0 16px;rotate:2deg}
.bc-panel ul{list-style:none;display:flex;flex-direction:column;gap:9px}
.bc-panel li{font-family:var(--body);font-weight:500;font-size:15px;display:flex;gap:9px;align-items:baseline}
.bc-panel li::before{content:"+";font-family:var(--mono);font-weight:700;opacity:.7}
.bc-proof{margin-top:16px;font-family:var(--mono);font-size:12px;letter-spacing:.06em;
  color:rgba(20,23,58,.78)}
/* flat token panels, ink text on all (AA-safe); order follows the page:
   b1 Paid, b2 Strategic, b3 Lifecycle, b4 Social, b5 AI, b6 Creative, b7 Web */
.bc-b1{background:var(--electric)}
.bc-b2{background:var(--coral)}
.bc-b3{background:var(--svc-green)}
.bc-b4{background:var(--svc-orange)}
.bc-b5{background:var(--svc-teal)}
.bc-b6{background:var(--svc-violet)}
.bc-b7{background:var(--svc-magenta)}
@media (min-width:768px){
  .bcard.w4{grid-column:span 4}.bcard.w8{grid-column:span 8;min-height:300px}
}
/* services close band */
/* The "if it isn't listed, ask" line under the seven cards. Deliberately quiet:
   it widens the offer without competing with the cards or the close. */
.svc-tailor{margin-top:clamp(28px,4vw,44px);font-size:15px;line-height:1.6;
  color:rgba(247,245,255,.62);max-width:52ch}

.svc-close{border-top:1px solid var(--ink-line);margin-top:clamp(64px,8vw,110px);
  padding:clamp(56px,8vw,96px) 0 clamp(24px,4vw,48px)}
.svc-close h2{font-family:var(--display);font-weight:800;font-size:clamp(30px,4.6vw,64px);
  letter-spacing:-.02em;line-height:1.02;max-width:14em}
.svc-close h2 em{font-style:normal;color:var(--electric)}
.svc-close .actions{margin-top:34px;display:flex;gap:12px;flex-wrap:wrap}
.svc-close .mail{display:inline-block;font-family:var(--display);font-weight:600;
  font-size:clamp(19px,2.6vw,32px);color:var(--electric);text-decoration:none;
  border-bottom:2px solid transparent;margin-top:36px}
.svc-close .mail:hover{border-color:var(--electric)}
@media (prefers-reduced-motion:reduce){
  .bcard,.bcard:hover{transform:none;transition:none}
  .bc-panel,.bcard:hover .bc-panel{translate:0 32px;rotate:0deg;transition:none}
  .bcard .go,.bcard:hover .go{transition:none;transform:none}
}

/* ============================================================
   ABOUT page
   ============================================================ */
/* one page, three movements: studio → founder → personal work */
.anchor-rail{display:flex;gap:0;margin-top:38px;border-top:1px solid var(--ink-line);
  border-bottom:1px solid var(--ink-line)}
.anchor-rail a{font-family:var(--mono);font-size:12px;letter-spacing:.18em;text-transform:uppercase;
  text-decoration:none;color:var(--peri);padding:16px 26px 16px 0;margin-right:26px;transition:color .25s}
.anchor-rail a:hover{color:var(--electric)}
.about-h2{font-family:var(--display);font-weight:800;font-size:clamp(28px,4vw,54px);
  letter-spacing:-.02em;line-height:1.02;margin:18px 0 0}
.about-h2 em{font-style:normal;color:var(--electric)}
.movement{padding-block:clamp(56px,8vw,96px) 0}
/* studio: one column of copy that hands off to the sector run */
.studio-copy{margin-top:clamp(26px,3.4vw,38px);max-width:62ch}
.studio-copy p{color:rgba(247,245,255,.78);margin-bottom:18px;
  font-size:clamp(16px,1.4vw,18px);line-height:1.6}
.studio-copy p:last-child{margin-bottom:0}
/* the payoff: sectors as one typographic run, the section's largest element */
.sectors{font-family:var(--display);font-weight:800;
  font-size:clamp(26px,3.7vw,50px);line-height:1.14;letter-spacing:-.022em;
  color:rgba(247,245,255,.9);margin-top:clamp(30px,3.8vw,46px)}
.sectors b{font-weight:800}
.sectors .a1{color:var(--electric)}
.sectors .a2{color:var(--coral)}
.sectors .a3{color:var(--lime)}
.sectors .a4{color:var(--sun)}
/* founder band: the one paper surface on the page */
.founder-band{background:var(--paper);color:var(--ink);margin-top:clamp(56px,8vw,96px)}
.founder-band .inner{max-width:1280px;margin:0 auto;padding:clamp(56px,8vw,96px) var(--edge)}
.founder-band .eyebrow{color:var(--peri)}
.founder-grid{display:grid;grid-template-columns:auto 1fr;gap:clamp(32px,5vw,72px);align-items:start;margin-top:38px}
/* Circular profile treatment, shared by this band and the /charles hero so the crop
   and framing stay identical across both pages. */
.portrait{width:240px;max-width:100%;aspect-ratio:1;border-radius:50%;overflow:hidden;position:relative;
  border:3px solid var(--peri);
  background:linear-gradient(160deg,rgba(126,151,232,.18),rgba(113,134,182,.1))}
.portrait img{width:100%;height:100%;object-fit:cover;object-position:center 22%;display:block}
.founder-grid .bio p{color:rgba(20,23,58,.8);max-width:56ch;margin:18px 0 26px;font-size:clamp(16px,1.4vw,18px)}
.fact-rail{list-style:none;border-top:1px solid rgba(20,23,58,.16)}
/* Direct child, not descendant: the Capabilities row nests its own <ul>, and a
   descendant selector would give every bullet the row's border and flex layout. */
.fact-rail > li{display:flex;gap:18px;padding:13px 0;border-bottom:1px solid rgba(20,23,58,.16);align-items:baseline}
.fact-rail .k{font-family:var(--mono);font-size:11px;letter-spacing:.18em;color:var(--peri);min-width:120px;text-transform:uppercase}
.fact-rail .f{font-family:var(--body);font-weight:500;font-size:15px;color:var(--ink)}
/* Stack on small screens. The 120px label plus an 18px gap took 138px of a 323px
   column at 375px, so values ran in a 185px gutter: the /charles "Studio" row wrapped
   to eight lines. Label above value gives every value the full width. Shared by both
   rails (About's founder rail has the same squeeze, less acutely). */
@media (max-width:640px){
  .fact-rail > li{flex-direction:column;gap:6px;align-items:flex-start}
  .fact-rail .k{min-width:0}
}
@media (max-width:760px){.founder-grid{grid-template-columns:1fr}}
/* personal work: uniform 4:5 grid, square corners, captions below, no metrics */
.pgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin-top:38px}
.pgrid figure{margin:0}
.pgrid .frame{aspect-ratio:4/5;border-radius:0;background:var(--pf,var(--ink-2));
  display:grid;place-items:center;overflow:hidden}
.pgrid .frame img.mark{width:30%;filter:brightness(0) invert(1);opacity:.25}
.pgrid figcaption{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:rgba(247,245,255,.62);margin-top:10px}
@media (max-width:960px){.pgrid{grid-template-columns:repeat(2,1fr)}}
@media (max-width:520px){.pgrid{grid-template-columns:1fr}}
/* staggered reveal children */
@media (prefers-reduced-motion:no-preference){
  .stagger>*{opacity:0;transform:translateY(14px);transition:opacity .55s cubic-bezier(.2,.7,.2,1),transform .55s cubic-bezier(.2,.7,.2,1)}
  .stagger.in>*{opacity:1;transform:none}
  .stagger.in>*:nth-child(2){transition-delay:.06s}
  .stagger.in>*:nth-child(3){transition-delay:.12s}
  .stagger.in>*:nth-child(4){transition-delay:.18s}
  .stagger.in>*:nth-child(5){transition-delay:.24s}
  .stagger.in>*:nth-child(6){transition-delay:.3s}
  .stagger.in>*:nth-child(7){transition-delay:.36s}
  .stagger.in>*:nth-child(8){transition-delay:.42s}
}
@media (max-width:760px){.about-cols{grid-template-columns:1fr}}

/* ============================================================
   CONTACT page + form
   ============================================================ */
/* asymmetric split — mirrors .about-cols proportions so the page reads as the same site */
.contact-grid{display:grid;grid-template-columns:1.1fr .9fr;gap:clamp(40px,7vw,96px);
  margin-top:8px;align-items:start}
.contact-aside .mail{display:inline-block;font-family:var(--display);font-weight:800;
  font-size:clamp(26px,3.4vw,46px);line-height:1.05;letter-spacing:-.02em;
  color:var(--electric);text-decoration:none;
  border-bottom:2px solid transparent;transition:border-color .25s}
.contact-aside .mail:hover{border-color:var(--electric)}
.contact-aside .facts{margin-top:26px;font-family:var(--mono);font-size:12px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--peri);line-height:2}
.contact-aside .aside-note{margin-top:34px;padding-top:26px;border-top:1px solid var(--ink-line);
  color:rgba(247,245,255,.62);font-size:15px;max-width:34ch}

/* form sits directly on the page — no card */
form.vform{display:grid;gap:30px;max-width:460px}
.field{display:flex;flex-direction:column;gap:7px}
.field label{font-family:var(--mono);font-size:11px;letter-spacing:.18em;text-transform:uppercase;
  color:rgba(247,245,255,.66);transition:color .2s}
.field:focus-within label{color:var(--electric)}
.field .req{color:var(--coral)}
/* underline inputs */
.field input,.field textarea,.field select{font-family:var(--body);font-size:16px;color:var(--paper);
  background:transparent;border:0;border-bottom:1.5px solid var(--ink-line);border-radius:0;
  padding:10px 2px 12px;width:100%;transition:border-color .2s}
.field select{appearance:none;-webkit-appearance:none;cursor:pointer;
  background-image:linear-gradient(45deg,transparent 50%,var(--peri) 50%),linear-gradient(135deg,var(--peri) 50%,transparent 50%);
  background-position:calc(100% - 12px) calc(50% + 2px),calc(100% - 7px) calc(50% + 2px);
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;padding-right:28px}
.field select option{background:var(--ink);color:var(--paper)}
.field input:focus,.field textarea:focus,.field select:focus{outline:none;
  border-bottom-color:var(--electric);border-bottom-width:2px;padding-bottom:11px}
.field textarea{resize:vertical;min-height:120px}
.field .hint{font-size:13px;color:rgba(247,245,255,.5)}
.field.invalid input,.field.invalid textarea{border-bottom-color:var(--coral)}
.field .err{font-size:13px;color:var(--coral);display:none}
.field.invalid .err{display:block}
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:30px}
form.vform .btn-primary{justify-self:start;margin-top:6px}
.form-note{font-family:var(--mono);font-size:11px;letter-spacing:.06em;color:rgba(247,245,255,.45);margin-top:-14px}
/* inline success — no card */
.form-success{display:none;color:var(--lime);font-family:var(--mono);font-size:13px;
  letter-spacing:.04em;line-height:1.6;padding-left:16px;border-left:2px solid var(--lime)}
.form-success.show{display:block}
@media (max-width:820px){
  .contact-grid{grid-template-columns:1fr;gap:44px}
  form.vform{max-width:none;gap:24px}
  .field-row{gap:24px}
  form.vform .btn-primary{justify-self:stretch;justify-content:center}
  .contact-aside .aside-note{margin-top:26px;padding-top:22px}
}
@media (max-width:560px){.field-row{grid-template-columns:1fr;gap:24px}}

/* ============================================================
   Shared footer  (matches homepage footer)
   ============================================================ */
.site-footer{background:var(--ink);color:rgba(247,245,255,.65);padding:44px var(--edge) 48px;
  display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;
  border-top:1px solid var(--ink-line)}
.site-footer .fmark{display:flex;align-items:center;gap:12px;font-family:var(--mono);font-size:11px;letter-spacing:.2em}
.site-footer .fmark img{width:26px;filter:brightness(0) invert(1);opacity:.9}
.site-footer nav{display:flex;gap:22px;flex-wrap:wrap}
.site-footer nav a{font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;
  text-decoration:none;color:rgba(247,245,255,.6);transition:color .2s}
.site-footer nav a:hover{color:var(--electric)}
.site-footer .socials{display:flex;gap:18px}
.site-footer .socials a{color:rgba(247,245,255,.6);transition:color .2s}
.site-footer .socials a:hover{color:var(--electric)}
.site-footer svg{width:20px;height:20px;fill:currentColor;display:block}

/* ---------- entrance motion (one authored moment) ---------- */
@media (prefers-reduced-motion:no-preference){
  .reveal{opacity:0;transform:translateY(22px);transition:opacity .7s cubic-bezier(.2,.7,.2,1),transform .7s cubic-bezier(.2,.7,.2,1)}
  .reveal.in{opacity:1;transform:none}
}

/* ============================================================
   /charles — the unlisted personal portfolio (charles.html)
   Everything here is additive. Nothing above this line changes, so the public
   site is unaffected by anything in this block.
   ============================================================ */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* Header carries no nav, so the mark needs a word beside it or it reads unfinished. */
.site-header .lock-note{font-family:var(--mono);font-size:11px;letter-spacing:.2em;
  text-transform:uppercase;color:rgba(247,245,255,.55);margin-left:auto}

/* Paper hero band, the same surface treatment as .founder-band on About. The headshot
   carries a light backdrop of its own, so it dissolves into this rather than sitting
   on a dark page as a hard rectangle. Everything below the band stays dark, giving the
   page the same paper-then-ink rhythm About has. */
.hero-band{background:var(--paper);color:var(--ink)}
.hero-band h1{color:var(--ink)}
/* --peri, not --electric: the lighter blue is what reads on ink, this one on paper. */
.hero-band h1 em{color:var(--peri)}
.hero-band .lead{color:rgba(20,23,58,.78)}
/* Symmetric padding, overriding .page-hero's tall-top/short-bottom default: with the
   portrait now a small circle, the default left it riding low in the paper band. Equal
   space above and below lets the circle sit centered in the band's white space. */
/* Asymmetric on purpose: the hand-off line at the bottom of the hero points into the
   next section, so it reads better with less air beneath it than above. Also pays
   back the height that line costs, keeping the hero at the 669px it measured before
   the line was pulled out of the columns. */
.hero-band .page-hero{padding-block:clamp(56px,9vw,110px) clamp(38px,5.4vw,74px)}

/* ---------- portfolio grid (/charles) ----------
   Full-bleed media tiles, Superside-style: the visual fills the tile and the text zone
   is overlaid top-left. Spans follow the asset (three wide tiles); rows are fixed
   height so imagery crops rather than reflows.

   THE SCRIM IS THE CONTRACT. Text over arbitrary imagery is where the original P0
   contrast failure lived. A fixed tile-height gradient failed the white-backdrop sweep
   the moment a title wrapped deep (text measured reaching 78% of tile height), so the
   scrim lives ON .pf-text itself: a solid ~.82 ink field behind every glyph with a
   pixel-anchored fade that sits entirely in the block's bottom padding. Text can wrap
   as deep as it likes and never leaves the dense zone. Verified against a pure-white
   backdrop, the worst case any real asset can present. Do not thin it. */
/* NOT a column grid (Charles, 2026-09-05: "id rather have you change the size of the
   tiles themselves to fit the images rather than crop the images weird"). A fixed
   column grid forces every cover into the same box, so a portrait poster in a
   landscape cell either crops through the artwork or letterboxes on a colour bed.
   Both were tried and both were rejected.

   This is a justified row layout instead, the shape a photo wall uses: layoutTiles()
   in charles.html walks the studies IN ORDER, fills each row until it is wide enough,
   then solves one height for that row so every tile in it is exactly its own image's
   aspect. Every cover fills its tile edge to edge, uncropped; tile sizes vary because
   the artwork varies; rows are flush left and right with no holes. Widths and heights
   are written as inline px by that function -- the flex box here only wraps them and
   holds the gaps. Ordering is preserved exactly, which a CSS-columns masonry would
   not do. */
.pf-grid{display:flex;flex-wrap:wrap;gap:14px;margin-top:34px;align-items:flex-start}
/* Before the first measure (and if scripting is off) the tiles are readable squares
   rather than zero-height slivers; layoutTiles() overwrites both on its first pass. */
.pf{flex:0 0 auto;width:calc(25% - 11px);aspect-ratio:1}
.pf[style*="height"]{aspect-ratio:auto}
@media (max-width:1100px){ .pf{width:calc(33.333% - 10px)} }
@media (max-width:820px){
  .pf-grid{gap:10px}
  .pf{width:calc(50% - 5px)}
  .pf-text{padding:12px 12px 40px;gap:5px}
  .pf-client{font-size:10.5px;letter-spacing:.14em}
  .pf-client .at{font-size:10px}
  .pf-title{font-size:14px}
  .pf-stat{font-size:20px}
  .pf-stat small{font-size:10px;letter-spacing:.12em}
  .pf .slot-label{font-size:9px;padding:6px 9px}
}

/* flex-start, because a bare <button> vertically centres its content: without this the
   text block floats mid-tile and raw image shows above the scrim. */
.pf{position:relative;border:0;cursor:pointer;text-align:left;border-radius:20px;
  overflow:hidden;padding:0;background:var(--ink-2);color:var(--paper);
  display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;
  isolation:isolate;transition:transform .35s cubic-bezier(.2,.7,.2,1)}
.pf:hover{transform:translateY(-5px)}
.pf:focus-visible{outline:2px solid var(--lime);outline-offset:3px}

/* media layer: real asset once src is set, labeled placeholder until then */
.pf-media{position:absolute;inset:0;z-index:-1;width:100%;height:100%;object-fit:cover;
  display:block;transition:transform .5s cubic-bezier(.2,.7,.2,1)}
.pf:hover .pf-media{transform:scale(1.04)}
/* The 4:5 encode crop does most of the portrait correction. This is the smaller
   second one: cover still trims a little, and what it trims from the centre is
   usually where the face is. mediaHTML adds pf-tall when the asset is portrait. */
.pf-media.pf-tall{object-position:50% 35%}

/* Text overlay, cut back to two lines: the client and one figure. The old scrim
   was dense ink over most of the tile, which buried the media the tiles now
   carry; this one is a short top gradient that clears within the text zone and
   leaves the rest of the frame legible. The study title lives in the panel. */
.pf-text{position:relative;display:flex;flex-direction:column;gap:6px;
  padding:16px 16px 34px;
  background:linear-gradient(180deg,
    rgba(11,13,34,.86) 0%, rgba(11,13,34,.6) 60%, rgba(11,13,34,0) 100%)}
.pf-client{font-family:var(--mono);font-size:10.5px;font-weight:500;letter-spacing:.16em;
  text-transform:uppercase;
  color:var(--lime);max-width:86%;text-shadow:0 1px 6px rgba(11,13,34,.9)}
.pf-client .at{display:block;margin-top:2px;font-size:10px;letter-spacing:.08em;
  text-transform:none}
/* The figure is the tile's headline; it was sized as a caption (17px max) and read
   as one. Display scale + a heavier shadow so it holds over any asset. */
.pf-stat{display:block;font-family:var(--display);font-weight:800;
  font-size:clamp(20px,1.9vw,27px);line-height:1.05;letter-spacing:-.01em;
  text-shadow:0 2px 10px rgba(11,13,34,.9)}
.pf-stat small{display:block;margin-top:4px;font-family:var(--mono);font-weight:500;
  font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:rgba(247,245,255,.95);
  text-shadow:0 1px 6px rgba(11,13,34,.9)}
/* metricIsText studies put a phrase where the others put a figure (iRhythm's "The
   Zio patch campaign", under an NDA that rules out the number). A phrase set at
   display-figure scale wraps to four lines and eats the tile, so it steps down. */
.pf.artefact .pf-stat{font-size:clamp(16px,1.35vw,20px);line-height:1.15}

/* Tiles are sized to their artwork now, so a portrait cover paired with a wide one
   can come out genuinely narrow -- 125px on a phone, measured. Viewport queries
   cannot see that; a container query can, so the overlay steps down against the
   TILE's width rather than the screen's. Without it the iRhythm phrase wrapped
   past the bottom of its own tile. */
.pf{container-type:inline-size}
@container (max-width:230px){
  .pf-text{padding:11px 11px 26px;gap:4px}
  .pf-client{font-size:9px;letter-spacing:.1em}
  .pf-client .at{font-size:8.5px;letter-spacing:.04em}
  .pf-stat{font-size:17px}
  .pf.artefact .pf-stat{font-size:13px}
  .pf-stat small{margin-top:3px;font-size:8.5px;letter-spacing:.09em}
}
@container (max-width:160px){
  .pf-text{padding:8px 8px 20px}
  .pf-client{font-size:8px}
  .pf-client .at{display:none}
  .pf-stat{font-size:14px}
  .pf.artefact .pf-stat{font-size:11px}
  .pf-stat small{font-size:7.5px;letter-spacing:.06em}
}

/* placeholder state: the study's gradient plus a dashed frame naming the intended
   asset, parked bottom-centre so it never collides with the overlaid text */
.pf .slot-empty{display:flex;align-items:flex-end;justify-content:center;padding-bottom:16px}
/* Chip is opaque enough to hold 4.5:1 even over a white asset behind it. */
.slot-label{font-family:var(--mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;
  color:rgba(247,245,255,.95);text-align:center;max-width:88%;
  border:1.5px dashed rgba(247,245,255,.45);border-radius:10px;padding:7px 11px;
  background:rgba(11,13,34,.72)}

.pf .go{position:absolute;top:16px;right:16px;width:32px;height:32px;border-radius:50%;
  display:grid;place-items:center;font-size:14px;
  background:rgba(11,13,34,.62);color:var(--paper);
  transform:translateY(-6px);opacity:0;transition:.3s}
.pf:hover .go,.pf:focus-visible .go{transform:none;opacity:1}
/* Touch has no hover, so the only affordance would otherwise never appear. */
@media (hover:none){.pf .go{transform:none;opacity:.6}}
@media (prefers-reduced-motion:reduce){
  .pf,.pf:hover{transform:none;transition:none}
  .pf-media,.pf:hover .pf-media{transform:none;transition:none}
}

/* ---------- wide gallery panel (/charles only) ----------
   Story on top, media below as a bento grid (the interactive-bento-gallery
   reference): items span cells by their real shape via --c/--r set inline, dense
   flow packs the leftovers, captions rise only on hover or focus, and clicking a
   tile opens the .gv viewer. work.html keeps the narrow shared .ql untouched. */
.ql.ql-wide{width:min(980px,96vw)}
.ql-wide #qlContent{display:grid;grid-template-columns:1fr;gap:34px}
/* The old side-by-side split is gone: the grid wants the panel's full width, and
   the story keeps a readable measure instead. */
.ql-wide .ql-story{max-width:74ch}

.ql-bento{display:grid;gap:10px;grid-template-columns:repeat(4,1fr);
  grid-auto-rows:88px;grid-auto-flow:dense}
@media (max-width:640px){.ql-bento{grid-template-columns:repeat(2,1fr);grid-auto-rows:72px}}
.gb-item{position:relative;overflow:hidden;border-radius:14px;margin:0;border:0;
  padding:0;background:#0c0e22;cursor:pointer;text-align:left;
  grid-column:span var(--c,1);grid-row:span var(--r,2)}
.gb-item.gb-empty{cursor:default}
@media (max-width:640px){.gb-item{grid-column:span min(var(--c,1),2)}}
.gb-media{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
iframe.gb-media{border:0;background:var(--paper)}
.gb-item .slot-empty{display:flex;align-items:center;justify-content:center}
/* The play badge marks which tiles are clips, since the grid shows posters, not
   players; watching happens in the viewer. */
.gb-play{position:absolute;top:10px;right:10px;width:28px;height:28px;border-radius:50%;
  display:grid;place-items:center;font-size:12px;
  background:rgba(11,13,34,.66);color:var(--paper);pointer-events:none}
.gb-expand{position:absolute;top:10px;right:10px;width:30px;height:30px;border-radius:50%;
  display:grid;place-items:center;border:0;cursor:pointer;font-size:14px;
  background:rgba(11,13,34,.66);color:var(--paper)}
.gb-expand:hover,.gb-expand:focus-visible{background:rgba(11,13,34,.9)}
/* Hover-only caption on the reference's bottom scrim. focus-within keeps it (and
   any link inside a placeholder's caption) reachable by keyboard. */
.gb-cap{position:absolute;inset:auto 0 0 0;padding:26px 12px 10px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.06em;
  color:rgba(247,245,255,.92);
  background:linear-gradient(180deg,transparent,rgba(11,13,34,.85));
  opacity:0;transition:opacity .22s;pointer-events:none}
.gb-item:hover .gb-cap,.gb-item:focus-visible .gb-cap,.gb-item:focus-within .gb-cap{
  opacity:1;pointer-events:auto}
.gb-cap a{color:var(--paper)}
.gb-item:focus-visible{outline:2px solid var(--lime);outline-offset:2px}
/* A frame that failed to load has no fill in scope; land it on the plate, not on
   transparent. */
.slot-empty{background:#0c0e22}

/* ---------- gallery viewer (/charles only) ----------
   The layer above the panel: stage centered, caption under it, thumbnail dock
   along the bottom. Stage media contains rather than covers, so a 9:16 reel and
   a 2:1 screenshot both present whole. */
.gv{position:fixed;inset:0;z-index:130;  /* above the .ql drawer (121) */display:none;flex-direction:column;
  align-items:center;justify-content:center;padding:24px;
  background:rgba(8,10,26,.92);backdrop-filter:blur(10px);
  -webkit-backdrop-filter:blur(10px)}
.gv.open{display:flex}
.gv-stage{display:grid;place-items:center;max-width:min(1100px,92vw);
  width:100%;height:min(66vh,760px);
  /* One explicit 100% track: with the default auto row, the child's
     max-height:100% resolves against nothing and a 4:5 poster overflows the
     viewport straight through the caption and the dock. */
  grid-template-rows:100%;grid-template-columns:100%}
.gv-stage video,.gv-stage img{max-width:100%;max-height:100%;border-radius:14px}
.gv-stage video{height:100%}
.gv-stage iframe{width:min(1000px,90vw);height:100%;border:0;border-radius:14px;
  background:var(--paper)}
.gv-cap{margin:12px 0 0;font-family:var(--mono);font-size:11px;letter-spacing:.08em;
  color:rgba(247,245,255,.75);text-align:center;max-width:70ch}
.gv-cap a{color:var(--paper)}
.gv-close{position:absolute;top:18px;right:18px;width:40px;height:40px;border-radius:50%;
  border:1px solid rgba(247,245,255,.3);background:rgba(11,13,34,.7);color:var(--paper);
  font-size:15px;cursor:pointer}
.gv-close:hover,.gv-close:focus-visible{border-color:rgba(247,245,255,.75)}
.gv-arrow{position:absolute;top:50%;transform:translateY(-50%);width:44px;height:44px;
  border-radius:50%;border:1px solid rgba(247,245,255,.3);background:rgba(11,13,34,.7);
  color:var(--paper);font-size:20px;line-height:1;cursor:pointer;z-index:2}
.gv-arrow:hover,.gv-arrow:focus-visible{border-color:rgba(247,245,255,.75)}
.gv-prev{left:clamp(8px,2vw,28px)}
.gv-next{right:clamp(8px,2vw,28px)}
/* The dock: every piece in the study at a glance, the current one crowned. */
.gv-dock{display:flex;gap:8px;margin-top:14px;max-width:92vw;overflow-x:auto;
  padding:4px;scrollbar-width:none}
.gv-dock::-webkit-scrollbar{display:none}
.gv-thumb{position:relative;flex:0 0 auto;width:46px;height:62px;border-radius:8px;
  overflow:hidden;border:2px solid transparent;background:#0c0e22;cursor:pointer;
  padding:0;opacity:.6;transition:opacity .2s,border-color .2s}
.gv-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.gv-thumb:hover,.gv-thumb:focus-visible{opacity:1}
.gv-thumb.isCurrent{opacity:1;border-color:var(--electric)}
.gv-thumb-play,.gv-thumb-embed{position:absolute;inset:0;display:grid;place-items:center;
  font-size:13px;color:rgba(247,245,255,.9);background:rgba(11,13,34,.25)}
@media (max-width:640px){
  .gv{padding:14px}
  .gv-stage{height:min(58vh,620px)}
  .gv-arrow{width:38px;height:38px}
}

/* ---------- reel rail (/charles only) ----------
   Expand-on-hover (the Skiper HoverExpand reference): every card is a collapsed
   sliver of fixed height, and .isActive animates its width open to a near-square
   card. The whole set sits on one row, centered by the first/last auto-margins
   when it fits and dragging when it does not (touch-action pan-x keeps native
   swipe on phones; the mouse drag lives in JS). Heights and widths are the
   reference's 24rem/5rem geometry, clamped for phones. */
.reel-rail{padding:clamp(28px,4vw,44px) 0 clamp(20px,3vw,36px)}
.rail-head{margin-bottom:16px}
.rail-track{--rh:clamp(380px,58vh,560px);
  display:flex;align-items:center;gap:10px;
  overflow-x:auto;overscroll-behavior-x:contain;touch-action:pan-x pan-y;
  padding:10px 24px 14px;scrollbar-width:none;cursor:grab}
/* Coarse pointers give up native horizontal panning: a swipe is a next/prev
   step (the JS keeps the selected card centered), and pan-y keeps the page
   scrolling vertically through the rail. Mouse drag is unaffected. */
@media (pointer:coarse){.rail-track{touch-action:pan-y}}
.rail-track::-webkit-scrollbar{display:none}
.rail-track.isDragging{cursor:grabbing}
/* Auto-margins on the ends center the row when it fits the viewport and yield to
   scrolling when it overflows, which is exactly the two states the design wants. */
.rail-card:first-child{margin-left:auto}
.rail-card:last-child{margin-right:auto}
/* Collapsed slivers widened (was clamp(44px,5.5vw,80px)): the tight strips made
   card-to-card scrubbing fiddly and hid what each clip was. */
.rail-card{position:relative;flex:0 0 auto;margin:0;height:var(--rh);
  width:clamp(72px,9vw,132px);border-radius:20px;overflow:hidden;
  background:#0c0e22;cursor:pointer;
  transition:width .3s ease-in-out}
/* Open card is 4:5 of the taller height rather than square: the clips are
   vertical, so the extra height goes to the reel instead of to a wider crop. */
.rail-card.isActive{width:min(calc(var(--rh)*.8),76vw);cursor:default}
.rail-card:focus-visible{outline:2px solid var(--lime);outline-offset:3px}
.rail-card video{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;display:block}
/* Same affordance chip as the tile's ↗; only the open card offers it. */
.rc-chip{position:absolute;top:12px;right:12px;z-index:2;width:34px;height:34px;
  border-radius:50%;display:grid;place-items:center;border:0;cursor:pointer;
  font-size:14px;background:rgba(11,13,34,.62);color:var(--paper);
  visibility:hidden;opacity:0;transition:opacity .25s}
.rail-card.isActive .rc-chip{visibility:visible;opacity:.75}
.rc-chip:hover,.rc-chip:focus-visible{opacity:1}
/* Caption rides inside the open card on the reference's bottom gradient scrim,
   not below it: a sliver has nowhere to put text, so the overlay appears and
   disappears with the expansion. visibility keeps the hidden buttons out of the
   tab order. */
.rc-overlay{position:absolute;inset:auto 0 0 0;z-index:1;margin:0;
  display:flex;flex-direction:column;gap:3px;padding:44px 16px 14px;
  background:linear-gradient(180deg,transparent,rgba(11,13,34,.78));
  visibility:hidden;opacity:0;transition:opacity .25s}
.rail-card.isActive .rc-overlay{visibility:visible;opacity:1}
/* Sound mode hands the frame to the native controls: the caption would sit on
   top of the scrubber, so it steps aside until the loop returns. */
.rail-card.isSound .rc-overlay{visibility:hidden;opacity:0}
.rc-client{font-family:var(--display);font-weight:800;font-size:14px}
.rc-cap{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;
  color:rgba(247,245,255,.72)}
.rc-study{align-self:flex-start;margin-top:6px;border:0;background:none;padding:0;
  cursor:pointer;font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--paper);
  border-bottom:1px solid rgba(247,245,255,.4)}
.rc-study:hover,.rc-study:focus-visible{border-bottom-color:var(--paper)}
@media (prefers-reduced-motion:reduce){
  .rail-card,.rc-chip,.rc-overlay{transition:none}
}

/* Hero head: the portrait sits inline to the left of the name, standfirst below the
   pair. Reuses .portrait from the About founder band, so the circular crop and face
   framing match across both pages; only the diameter differs here.

   The circle is the ONLY thing that flexes. The headline keeps .page-hero's own
   clamp(52px,9vw,128px) at every width: shrinking the name to make room for a photo
   would invert the hierarchy on a page whose whole job is that name. So the diameter is
   viewport-scaled with flex:none.

   17vw is not arbitrary: the two-line name stands 2 x .94 x 9vw = 16.9vw tall through
   the fluid range, so a 17vw circle matches the headline block's height almost exactly
   at every size between the clamps. The 64px floor is what keeps "Clayton." clear of
   the circle at 320px, the narrowest phone worth supporting. */
.hero-head{display:flex;align-items:center;gap:clamp(12px,2.6vw,40px);flex-wrap:wrap}
.hero-portrait{flex:none;width:clamp(64px,17vw,240px)}
.hero-head + .lead{margin-top:clamp(24px,3vw,34px);max-width:58ch}
.lead + .lead{margin-top:14px;max-width:58ch}

/* Hero bio, one column (Charles, 2026-09-05).

   This was two columns for a while, which was a workaround rather than a design:
   the bio ran long enough to push the hero to 843px against a 900px viewport, so
   the whole first screen was a portrait, three numbers and a paragraph with no work
   in it. Splitting it halved the height but left a break to read around.

   At 54 words the bio is five lines and needs no split. Measured: 659px hero on
   desktop, identical to the two-column version, and 41px shorter on the phone.
   There is whitespace to the right of the column, and at this height that is air
   rather than the dead space it was at 843px.

   The measure runs to 68ch rather than the 60ch body default because five lines at
   60ch would be six, and six is where the hero starts growing again. text-wrap:pretty
   keeps the last line from stranding a word, which is what made the old break look
   wrong ("...and higher ed." dropping "ed." onto a line of its own). */
.hero-copy{margin-top:clamp(22px,2.6vw,30px)}
.hero-copy .lead{margin:0;max-width:68ch;text-wrap:pretty}
/* The hand-off is not body copy: a step down in size, pointing at the work the way
   a caption points at a picture. */
.hero-handoff{margin:clamp(16px,1.8vw,22px) 0 0;
  font-size:clamp(14px,1.05vw,15px);line-height:1.5;letter-spacing:.02em;
  color:rgba(20,23,58,.56)}
.hero-handoff .ic{margin-right:.5em;opacity:.75}

/* ---------- inline icons ----------
   Replaces the ↗ ▶ ⤢ ↓ characters the page used to print. Those carry an emoji
   presentation, so on iOS, macOS and some Android builds they rendered as
   full-colour emoji over the video work, in a palette this page never chose.
   These are drawn instead: currentColor, sized in em so each follows the
   font-size of whatever chip or line it sits in, and identical everywhere. */
.ic{width:1em;height:1em;display:inline-block;vertical-align:-.14em;flex:none}
/* Slightly larger where the icon carries a line of type rather than filling a
   chip, so it reads at the weight of the words next to it. */
.rc-study .ic,.btn .ic{width:1.15em;height:1.15em;margin-left:.1em}

/* Three-stat stack beside the name/photo block (Charles's spec: three only, minimal).
   Numbers on the display scale, labels in the label face; the left rule keys it to
   the headline without boxing it in. */
.hero-stats{list-style:none;margin-left:auto;flex:none;align-self:center;
  display:flex;flex-direction:column;gap:clamp(14px,2vw,22px);
  padding-left:clamp(16px,2.4vw,30px);border-left:1.5px solid rgba(20,23,58,.16)}
.hero-stats .n{display:block;font-family:var(--display);font-weight:800;
  font-size:clamp(22px,2.5vw,34px);line-height:1;letter-spacing:-.01em;color:var(--ink)}
.hero-stats .l{display:block;margin-top:5px;font-family:var(--mono);font-weight:500;
  font-size:10.5px;letter-spacing:.15em;text-transform:uppercase;color:var(--peri)}
@media (max-width:900px){
  .hero-stats{flex-direction:row;width:100%;margin:20px 0 0;gap:clamp(20px,6vw,48px);
    padding-left:0;border-left:0;border-top:1.5px solid rgba(20,23,58,.16);
    padding-top:18px}
}

/* ---------- marquee (D10: names, never logos) ----------
   Pure CSS, no JS loop: four copies of the roster, each group translated by its own
   width plus one gap, so the row loops seamlessly. One pass takes --duration.

   Speed is deliberate and is the whole point of this treatment. This started as a JS
   loop at roughly 400px/sec, went to 40s, and is now 90s (~38px/sec): a slow drift
   rather than a ticker, so a name stays legible long enough to actually be read.

   --gap and --duration are the two knobs. Raising --duration slows it down. */
/* Bottom margin trimmed (was clamp(48px,7vw,84px)): the gap into "The work in
   motion" read as a section break rather than a beat. */
.marquee-set{--gap:2.5rem; --duration:90s; margin:clamp(24px,4vw,44px) 0 clamp(20px,3vw,36px)}
/* Edge mask, so names dissolve at both ends instead of being guillotined by overflow. */
/* No flex gap anywhere: the separator dot owns the spacing, with equal margin on both
   sides, so it sits centred between two names instead of hugging the one after it.
   Because nothing sits between the groups either, a group translates by exactly its
   own width (-100%) to loop. */
.marquee{display:flex;flex-direction:row;overflow:hidden;max-width:100%;padding:10px 0;
  -webkit-mask-image:linear-gradient(to right,transparent,#000 10%,#000 90%,transparent);
  mask-image:linear-gradient(to right,transparent,#000 10%,#000 90%,transparent)}
.marquee + .marquee{margin-top:2px}
.marquee-group{display:flex;flex-direction:row;flex-shrink:0;min-width:100%;
  will-change:transform;animation:v1-marquee var(--duration) linear infinite}
.marquee.reverse .marquee-group{animation-name:v1-marquee-reverse}
@keyframes v1-marquee{
  from{transform:translateX(0)}
  to{transform:translateX(-100%)}
}
@keyframes v1-marquee-reverse{
  from{transform:translateX(-100%)}
  to{transform:translateX(0)}
}
/* The reference component carries a `group` class with no hover rule attached. Pausing
   on hover is what it was for, and it lets a reader stop on a name. */
.marquee:hover .marquee-group{animation-play-state:paused}
.marquee .brand{white-space:nowrap;font-family:var(--display);font-weight:800;
  text-transform:uppercase;font-size:clamp(17px,2.4vw,30px);letter-spacing:-.01em;
  line-height:1.15;color:rgba(247,245,255,.82)}
/* Half a gap either side, so the dot lands midway between two names and the total
   name-to-name distance stays what it was. Every name carries its own leading dot,
   which also separates the last name of one group from the first of the next. */
.marquee .brand::before{content:"·";color:var(--electric);opacity:.7;
  margin-inline:calc(var(--gap) / 2)}
.marquee.reverse .brand{color:rgba(247,245,255,.62)}
.marquee.reverse .brand::before{color:var(--coral)}
/* Sits above the rows, labelling them before they scroll. */
/* .58 not .45: this is the page's only section heading and at .45 it measured 4.18:1,
   just under AA. */
/* Type comes from .eyebrow now (the markup carries both classes), so this heading
   matches "The work in motion" exactly; only placement lives here. */
.marquee-note{margin:0 0 20px;padding:0 var(--edge)}
/* Reduced motion: stop the animation and drop the duplicate groups, leaving one
   readable row the visitor can scroll by hand. */
@media (prefers-reduced-motion:reduce){
  .marquee{overflow-x:auto}
  .marquee-group{animation:none;min-width:0}
  .marquee .marquee-group:not(:first-child){display:none}
}

/* Word heroes: "ZIO PATCH" and "FILM" sit where "44M" normally does. */
.tile .metric.txt{font-size:clamp(19px,2vw,27px);letter-spacing:.01em;line-height:1.1}
/* Agency work names its employer inline, so a client is never read as Charles's own.
   11px not 10px (10px is under the functional-text floor), and no alpha: at .75 this
   measured 2.55:1 on the periwinkle tile. It is a disclosure, so it has to be legible. */
.tile .client .at{font-size:11px;letter-spacing:.12em;text-transform:none}

/* Impact values range from "44M" to "Dr. Bensimhon"; step the size down by length.
   Paired with sizeOf() in charles.html. */
.stat .n.long{font-size:clamp(21px,2.5vw,29px);line-height:1.05}
.stat .n.xlong{font-size:clamp(16px,1.8vw,21px);line-height:1.2;letter-spacing:0}
/* Some panels carry five impact stats, not four. Let the odd one out run full width
   rather than leaving a hole in the 2-up grid. */
.viz-grid .stat:last-child:nth-child(odd){grid-column:span 2}

/* Fact rail on the dark surface (the About page version sits on the pale band). */
.fact-rail-dark{border-top-color:var(--ink-line);margin-top:30px}
.fact-rail-dark > li{border-bottom-color:var(--ink-line)}
.fact-rail-dark .k{color:var(--electric)}
.fact-rail-dark .f{color:rgba(247,245,255,.8)}
/* Capabilities: a real list, multi-column so eight short items do not tower over the
   one- and two-line rows above it. auto-fill gives 4 columns at the desktop rail width
   and collapses to 1 when the rail stacks on mobile.

   The items are kept lowercase in the markup, exactly as they appear in Charles's
   handoff, so the copy can still be diffed against his doc; ::first-letter does the
   list-form capitalisation at render time. Delete that one line to revert.

   (Historical note: the old .capabilities paragraph this replaced drew a `line-length`
   finding of ~86 chars/line from the detector. That was a FALSE POSITIVE: the measured
   line was 70 chars. The detector assumes a 0.5em glyph advance, 536px ÷ 6.25 = 86,
   but Space Mono's advance is ~0.61em.) */
/* flex:1 because the row is a flex container and a grid child would otherwise size to
   its content (measured 429px against the 948px its sibling values get), collapsing
   auto-fill to two columns. min-width:0 lets long items wrap instead of forcing width. */
.cap-list{list-style:none;display:grid;gap:7px 24px;flex:1;min-width:0;
  grid-template-columns:repeat(auto-fill,minmax(200px,1fr))}
.cap-list li{position:relative;padding-left:15px;line-height:1.45}
.cap-list li::first-letter{text-transform:uppercase}
.cap-list li::before{content:"·";position:absolute;left:0;color:var(--electric);
  font-weight:700}

/* Minimal footer: no nav, no socials. An unlisted page should not funnel anywhere. */
.site-footer-min{justify-content:space-between}
.site-footer-min .fmail{font-family:var(--mono);font-size:11px;letter-spacing:.12em;
  text-decoration:none;color:rgba(247,245,255,.6);transition:color .2s}
.site-footer-min .fmail:hover{color:var(--electric)}

/* ============================================================
   /charles — de-branded header (2026-09-01)
   A light-mode pass (body.pf-light) lived here briefly and was reverted the
   same day: the page went back to the shared ink ground, which the tiles,
   scrims, and stat colours were designed against. What survives from that
   pass is structural, not themed: the header carries Charles's name instead
   of the retired V1 mark.
   ============================================================ */
.lock-name{font-family:var(--display);font-weight:800;font-size:15px;
  letter-spacing:.02em;color:var(--paper);margin-right:auto}

/* ---------- paper pop-outs (charles.html only; the class sits on its <aside>) ----
   Every study opens on the same paper panel with ink text: after a pass of
   rotating brand-colour grounds, one consistent light surface reads calmer, and
   the colour belongs to the stat tiles and the media instead. Accents tuned for
   paper: labels in periwinkle, ink buttons, and a hairline inset on the stat
   tiles so the light lime/sun fills still read as blocks on the light ground.
   work.html never adds this class, so its dark panel is untouched. */
.ql-paper{background:var(--paper);color:var(--ink);border-left:1px solid rgba(20,23,58,.14)}
.ql-paper .client,.ql-paper .cs-beat .k{color:var(--peri)}
.ql-paper .ql-tags{border-bottom-color:rgba(20,23,58,.13)}
.ql-paper .ql-tags li{color:rgba(20,23,58,.70);box-shadow:inset 0 0 0 1px rgba(20,23,58,.18)}
.ql-paper p{color:rgba(20,23,58,.82)}
.ql-paper .ql-close{background:rgba(20,23,58,.08);color:var(--ink)}
.ql-paper .ql-close:hover{background:rgba(20,23,58,.16)}
.ql-paper .stat{box-shadow:inset 0 0 0 1.5px rgba(20,23,58,.12)}
.ql-paper .btn-primary{background:var(--ink);color:var(--paper)}
.ql-paper .btn-primary:hover{background:var(--ink-2)}
.ql-paper .btn-ghost{border-color:rgba(20,23,58,.45);color:var(--ink)}
.ql-paper .btn-ghost:hover{border-color:var(--ink);color:var(--ink)}

/* ---------- "In their words" (charles.html): LinkedIn recommendation cards ----
   All eight ride the band on the dark stretch; the four study-specific ones also
   render inside their panels via .ql-quote. Cards keep quotes short-line and the
   attribution in the label face, role on its own quieter line. */
.quotes-band{padding-block:clamp(40px,6vw,72px) clamp(16px,3vw,32px)}
.quote-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin-top:26px}
@media (max-width:820px){.quote-grid{grid-template-columns:1fr}}
.q-card{margin:0;border:1px solid var(--ink-line);border-radius:18px;
  padding:22px 24px;background:var(--ink-2);display:flex;flex-direction:column;gap:14px}
.q-card blockquote{margin:0;font-size:15.5px;line-height:1.55;color:rgba(247,245,255,.85)}
.q-card figcaption{margin-top:auto;font-family:var(--mono);font-weight:500;font-size:11px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--electric)}
.q-card figcaption span{display:block;margin-top:4px;font-weight:400;font-size:10px;
  letter-spacing:.1em;color:rgba(247,245,255,.55)}

/* In-panel quote: a ruled beat between the results copy and the stat tiles. */
.ql-quote{margin:4px 0 22px;padding:4px 0 4px 18px;border-left:3px solid var(--peri)}
.ql-quote blockquote{margin:0;font-size:15.5px;line-height:1.55;font-style:italic;
  color:rgba(20,23,58,.85)}
.ql-quote figcaption{margin-top:10px;font-family:var(--mono);font-weight:500;font-size:10px;
  letter-spacing:.15em;text-transform:uppercase;color:var(--peri)}

/* Paper band for the experience snapshot, the page's second light moment. The
   base .fact-rail is already ink-on-paper. */
.snapshot-band{background:var(--paper);color:var(--ink);
  margin-top:clamp(48px,7vw,90px);padding-block:clamp(8px,2vw,24px) clamp(56px,8vw,96px)}

/* Always-visible corner chip on gallery tiles (BEFORE / AFTER comparisons). */
/* Gallery organized by asset type: labeled sub-grids in one column. */
.ql-gallery{display:flex;flex-direction:column;gap:26px}
.gb-group-label{font-family:var(--mono);font-weight:600;font-size:10.5px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--peri);margin:0 0 10px}

.gb-chip{position:absolute;top:10px;left:10px;z-index:2;
  font-family:var(--mono);font-weight:600;font-size:9.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink);background:var(--paper);
  border-radius:999px;padding:5px 10px;box-shadow:0 1px 8px rgba(11,13,34,.35)}

/* Covers that are complete designs (posters, ads, brand lockups) show whole
   rather than cropping to the tile; the tile takes the study's gradient as its
   bed, set inline by the tile builder. */
.gb-media.pf-contain{object-fit:contain;background:#0c0e22}
