/* masa portal — components. Brand-agnostic: every color/type decision routes through
   the token file (tokens.<brand>.css). No brand hex values in this file. */
*{box-sizing:border-box}
body{margin:0;background:var(--g);color:var(--ink);
  font-family:var(--font-body);
  font-size:16px;line-height:1.55;-webkit-font-smoothing:antialiased;
  transition:background-color .3s var(--ease-out),color .3s var(--ease-out);}
.shell{max-width:880px;margin:0 auto;padding:20px 18px 72px;}
.disp{font-family:var(--font-disp);font-weight:800;letter-spacing:-.03em;
  text-transform:var(--disp-case);line-height:1.05;}
.hand{font-family:var(--font-hand);color:var(--ink);}
.k{font-family:ui-monospace,Menlo,monospace;font-size:11px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--soft);margin:0;}
:focus-visible{outline:3px solid var(--accent);outline-offset:2px;border-radius:4px;}
.num{font-variant-numeric:tabular-nums;}
[hidden]{display:none !important;}

/* top bar */
.bar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:22px;}
/* ⚑ CYAN → ACCENT-ONLY (Juan, Jul 17): the brand goes grayscale like the home page (white wordmark,
   gray dough mascot); cyan is reserved for the ATTENTION signal (the "needs your ok" / .needs indicator). */
.brand{display:flex;align-items:center;gap:9px;font-family:var(--font-disp);
  font-weight:800;font-size:19px;letter-spacing:-.02em;color:var(--ink);}
.brand .m{width:14px;height:14px;background:var(--masa-dough,#3F3F3F);border-radius:var(--r-dough);}
.bar .lbl{font-size:12.5px;color:var(--soft);}
.themebtn{background:none;border:1px solid var(--line);border-radius:999px;width:30px;height:30px;
  cursor:pointer;color:var(--soft);font-size:14px;display:grid;place-items:center;}
.themebtn:hover{border-color:var(--soft);color:var(--ink);}

/* tokenless gate */
.gate{text-align:center;padding:60px 10px;max-width:44ch;margin:0 auto;}
.gate h1{font-size:26px;margin:0 0 10px;}
.gate p{color:var(--soft);font-size:15px;}
/* buddy = the site mascot's clay treatment at gate scale (azul.css .dough reference) */
.buddy{width:92px;height:86px;position:relative;margin:0 auto 20px;animation:breathe 4.8s ease-in-out infinite;}
.buddy .blob{position:absolute;inset:0;background:var(--masa-dough,#3F3F3F);border-radius:var(--r-dough);
  box-shadow:inset -8px -11px 18px rgba(0,0,0,.30),inset 7px 8px 16px rgba(255,255,255,.22);}
.buddy::after{content:"";position:absolute;left:14%;right:14%;bottom:-15px;height:12px;border-radius:50%;
  background:radial-gradient(ellipse at center,rgba(0,0,0,.35),transparent 70%);pointer-events:none;} /* black ground shadow on flask dark (was navy) */
.buddy .eye{position:absolute;top:41%;width:11px;height:11px;border-radius:50%;background:var(--oncolor);
  box-shadow:inset 0 1.5px 3px rgba(0,0,0,.18);animation:blink 5s infinite;}
.buddy .eye.l{left:33%;} .buddy .eye.r{left:57%;}
@keyframes breathe{0%,100%{transform:translateY(0) scale(1);}50%{transform:translateY(-3px) scale(1.03);}}
@keyframes blink{0%,7%,100%{transform:scaleY(1);}3.5%{transform:scaleY(.15);}}

/* client head */
.chead{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin:0 0 4px;}
.chead h1{font-size:clamp(26px,5vw,34px);margin:0;}
.plan{font-family:ui-monospace,Menlo,monospace;font-size:11px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--soft);border:1px solid color-mix(in srgb,var(--soft) 45%,transparent);
  border-radius:var(--r-pill);padding:3px 10px;}
.csub{color:var(--soft);font-size:14px;margin:0 0 22px;}

/* zone 1 — right now */
.now{display:grid;grid-template-columns:1.4fr 1fr;gap:14px;margin-bottom:14px;}
@media(max-width:640px){.now{grid-template-columns:1fr;}}
.card{background:var(--panel);border:0;box-shadow:var(--clay);border-radius:var(--r-card);padding:20px;}
.phase .k{margin-bottom:10px;}
.phase h2{font-size:21px;margin:0 0 6px;}
.phase p{margin:0;font-size:14px;color:var(--soft);}
.phase .next{margin-top:14px;padding-top:13px;border-top:1px dashed var(--line);font-size:14px;}
.phase .next b{color:var(--ink);}
.needs{border-left:4px solid var(--coral);}
.needs .k{color:var(--coral);margin-bottom:10px;}
.needs ul{margin:0;padding-left:18px;}
.needs li{font-size:14px;margin-bottom:8px;}
/* r54 — needs deep-link into the capture thread; keep the row's look, just make it
   navigable: inherits the list text color, a coral underline on hover/focus says
   "this goes somewhere" (both themes — --coral flips in the token set) */
.needs li a.need-link{color:inherit;text-decoration:none;}
.needs li a.need-link:hover,.needs li a.need-link:focus-visible{text-decoration:underline;text-decoration-color:var(--coral);text-underline-offset:2px;}
.needs .zero{font-size:14px;color:var(--soft);margin:0;}
/* ⚑ PRIMARY CTA = the site's light-gradient (cohesion, Jul 17) — cyan stays an accent (links/dots/kicker) */
.needs .act{display:inline-block;margin-top:10px;font-size:13.5px;font-weight:600;color:#191919;
  background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));text-decoration:none;border-radius:var(--r-pill);
  padding:9px 18px;box-shadow:0 0 0 .5px #09090b8c,0 1px 2px #00000059;}
.needs .act:hover{filter:brightness(1.03);}

/* zone 2 — outcomes */
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:14px;}
@media(max-width:760px){.stats{grid-template-columns:repeat(2,1fr);}}
.stat{background:var(--panel);border:0;box-shadow:var(--clay);border-radius:var(--r-card);padding:15px 16px 12px;}
button.stat{font:inherit;text-align:left;color:inherit;cursor:pointer;display:block;width:100%;
  transition:transform .25s var(--ease-out),box-shadow .25s var(--ease-out);}
button.stat:hover{transform:translateY(-2px);}
.stat-more{display:block;font-family:ui-monospace,Menlo,monospace;font-size:9px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--soft);margin-top:7px;}
/* stat history dialog */
.statbox{border:none;border-radius:var(--r-card);background:var(--panel);color:var(--ink);
  padding:22px 22px 16px;width:min(88vw,360px);box-shadow:var(--clay);}
.statbox::backdrop{background:rgba(10,12,20,.7);backdrop-filter:blur(5px);}
.statbox .sb-v{font-family:var(--font-disp);font-weight:800;font-size:34px;letter-spacing:-.02em;margin:4px 0 10px;}
.sb-chart svg{display:block;width:100%;height:56px;}
.sb-chart polyline{fill:none;stroke:var(--soft);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.sb-chart .spark-fill{fill:var(--soft);opacity:.12;stroke:none;}
.sb-chart .spark-end{fill:var(--soft);}
.sb-read{font-weight:700;font-size:15px;margin:0 0 8px;color:var(--ink);min-height:1.3em;}
.sb-hint{font-size:12px;color:var(--soft);margin:8px 0 0;}
.scrub-wrap{position:relative;cursor:crosshair;touch-action:none;}
.sb-chart .scrub{height:72px;}
.scrub-guide{stroke:var(--soft);stroke-width:1;stroke-dasharray:3 3;opacity:.7;}
/* dots are HTML overlays so they stay perfectly round (stretched SVG circles = ovals) */
.spark-wrap{position:relative;display:block;}
.dot-el{position:absolute;border-radius:50%;background:var(--soft);
  transform:translate(-50%,-50%);pointer-events:none;}
.spark-end-el{width:6px;height:6px;}
.scrub-dot-el{width:10px;height:10px;box-shadow:0 0 0 2px var(--panel);}
.needs-alt{margin-top:9px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:12.5px;}
.alt-or{color:var(--soft);}
.alt-chip.icon{display:inline-grid;place-items:center;width:34px;height:30px;border-radius:9px;
  color:var(--ink);background:var(--card-2,rgba(127,127,160,.12));border:1px solid var(--line,rgba(127,127,160,.25));}
.alt-chip.icon svg{stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.alt-chip.icon:hover{color:var(--ink);border-color:var(--soft);}
#pk-sec{margin-bottom:16px;} /* air between the shelf and 'worth celebrating' (Juan, Jul 11) */

/* wide screens: the archive is a BENTO SHELF v2 (Juan's spec Jul 11 2026) — each closed
   card leads with its MAIN piece (first media, no internal scrolling), title + date under it;
   grid stretch equalizes every row to its tallest card. Open a tile = full row, everything in it. */
@media(min-width:760px){
  .gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;align-items:stretch;}
  .gallery .proj{margin:0;}
  .gallery .proj[open]{grid-column:1 / -1;grid-row:1;} /* expanded = the shelf's top row; tiles flow below */
  .gallery .proj:not([open]){display:flex;}
  .gallery .proj:not([open]) .proj-sum{flex:1;padding:0;}
  .gallery .proj:not([open]) .proj-head{display:flex;flex-direction:column;gap:0;width:100%;height:100%;}
  /* the tile is a PILE (Juan's design + the documented thumbnail-pile pattern): the main
     piece sits on top, the others fan out behind it — says "several pieces live in here,
     and it opens right here" instead of reading like an external link */
  .gallery .proj:not([open]) .peek,.gallery .proj:not([open]) .bento{
    order:-1;margin:12px 12px 6px;flex:1 1 auto;aspect-ratio:var(--pile-ratio,4/5);min-height:0;position:relative;
    overflow:visible;border-radius:0;height:auto;display:block;}
  /* full-bleed 4:5 cover (native-ratio pieces tried + walked back by Juan same night) */
  .gallery .proj:not([open]) .peek img,.gallery .proj:not([open]) .bento img{
    position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:10px;display:block;}
  .gallery .proj:not([open]) .peek img:nth-child(n+5),.gallery .proj:not([open]) .bento img:nth-child(n+5){display:none;}
  .gallery .proj:not([open]) .peek img:nth-child(1),.gallery .proj:not([open]) .bento img:nth-child(1){z-index:4;}
  .gallery .proj:not([open]) .peek img:nth-child(2),.gallery .proj:not([open]) .bento img:nth-child(2){z-index:3;transform:rotate(-3.5deg) translate(-3.5%,1.5%);}
  .gallery .proj:not([open]) .peek img:nth-child(3),.gallery .proj:not([open]) .bento img:nth-child(3){z-index:2;transform:rotate(3deg) translate(3.5%,-1%);}
  .gallery .proj:not([open]) .peek img:nth-child(4),.gallery .proj:not([open]) .bento img:nth-child(4){z-index:1;transform:rotate(-5.5deg) translate(-1%,3%);}
  .gallery .proj:not([open]) .peek .more{display:block;position:absolute;right:-4px;bottom:-4px;z-index:5;
    width:auto;height:auto;padding:3px 9px;border-radius:999px;font-size:10.5px;font-weight:700;
    background:var(--sunk);color:var(--ink);}
  /* disclosure cue: chevron = "opens here", never an arrow (arrows read as navigation) */
  .gallery .proj:not([open]) .proj-sum{position:relative;}
  .gallery .proj:not([open]) .proj-chev{display:grid;place-items:center;position:absolute;top:8px;right:8px;
    z-index:6;width:24px;height:24px;border-radius:999px;background:var(--panel);color:var(--soft);}
  .gallery .proj:not([open]) .proj-t{font-size:13.5px;line-height:1.3;white-space:normal;
    padding:10px 12px 0;} /* wraps fully — titles are never truncated (Juan's call) */
  .gallery .proj:not([open]) .proj-meta{font-size:8.5px;white-space:normal;line-height:1.5;
    padding:3px 12px 12px;}
  .gallery .proj:not([open]) .pm-counts{display:none;} /* the pile IS the inventory */
  .gallery .proj:not([open]) .proj-chev{display:none;}
  /* in-works tiles: align the status pill + review link with the (12px-inset) title text,
     and give the pill a touch more top air below the title */
  .gallery .proj:not([open]) .proj-status{margin:6px 0 1px 12px;}
  .gallery .proj:not([open]) .proj-cta{margin:3px 0 2px 12px;font-size:11.5px;}
}
.sb-rows{list-style:none;margin:12px 0 4px;padding:0;max-height:38vh;overflow-y:auto;}
.sb-rows li{display:flex;justify-content:space-between;gap:12px;font-size:13px;
  padding:7px 2px;border-bottom:1px dashed var(--line);}
.sb-rows li:last-child{border-bottom:none;}
.sb-m{color:var(--soft);}
.sb-n{font-weight:700;}
.sb-close{margin-top:10px;color:var(--ink);border-color:var(--line);}
.stat .k{font-size:10px;color:var(--soft);letter-spacing:.09em;}
.stat .v{font-family:var(--font-disp);font-weight:800;font-size:26px;letter-spacing:-.02em;margin:4px 0 0;}
.stat .d{display:inline-block;font-size:11.5px;font-weight:600;margin:3px 0 8px;
  border-radius:var(--r-pill);padding:2px 9px;font-variant-numeric:tabular-nums;}
.stat .d.up{color:var(--ok);background:color-mix(in srgb,var(--ok) 12%,transparent);}
.stat .d.flat{color:var(--soft);background:color-mix(in srgb,var(--soft) 11%,transparent);}
.stat svg{display:block;width:100%;height:26px;}
.stat polyline{fill:none;stroke:var(--soft);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.stat .spark-fill{fill:var(--soft);opacity:.12;stroke:none;}
.stat .spark-end{fill:var(--soft);}
.statnote{font-size:12.5px;color:var(--soft);margin:0 0 26px;}

/* zone 3 — timeline */
.sec-h{display:flex;align-items:baseline;gap:10px;margin:0 0 14px;}
.sec-h h2{font-size:19px;margin:0;}
.tl{list-style:none;margin:0 0 30px;padding:0;background:var(--panel);
  border:0;box-shadow:var(--clay);border-radius:var(--r-card);}
.tl li{display:flex;gap:14px;padding:13px 18px;align-items:flex-start;border-bottom:1px solid var(--line);}
.tl li:last-child{border-bottom:none;}
.tl .dot{flex:none;width:22px;height:22px;border-radius:50%;display:grid;place-items:center;
  font-size:12px;margin-top:1px;border:2px solid var(--line);color:var(--soft);background:var(--g);}
.tl li.done .dot{background:var(--ok);border-color:var(--ok);color:var(--oncolor);}
.tl li.cur .dot{border-color:var(--gold);color:var(--gold);font-weight:800;}
.tl .t{font-weight:700;font-size:14.5px;}
.tl li.cur .t::after{content:"← we're here";font-family:ui-monospace,Menlo,monospace;
  font-size:10px;letter-spacing:.08em;color:var(--gold);margin-left:10px;text-transform:uppercase;}
.tl .w{display:block;font-size:12.5px;color:var(--soft);font-weight:400;}
.tl li.todo{opacity:.62;}

/* zone 4 — milestone wall. B+D+E system (Juan's pick, Jul 10 2026):
   tier 2 (default) = RECEIPT (proof-of-results ephemera) · tier 1 (landmarks) = POSTER
   (solid ground, grain, ghost numeral) + a "save for your story" 9:16 share view. */
.mcards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:30px;align-items:start;}
@media(max-width:700px){.mcards{grid-template-columns:1fr;}}
.mcard{position:relative;}

/* receipt (tier 2) — fixed paper colors by design, both themes (a receipt is an object) */
.mcard.receipt{background:#F5EFDD;color:#20233A;border-radius:4px;padding:18px 16px 12px;
  font-family:ui-monospace,Menlo,monospace;border:0;
  box-shadow:0 10px 24px -14px rgba(0,0,0,.35);margin-bottom:8px;}
.mcard.receipt::after{content:"";position:absolute;left:0;right:0;bottom:-7px;height:14px;
  background:radial-gradient(circle at 8px 0px,var(--g) 5px,transparent 5.5px) repeat-x;background-size:16px 14px;}
.mcard.receipt .store{text-align:center;font-weight:700;font-size:11.5px;letter-spacing:.18em;text-transform:uppercase;margin:0 0 2px;}
.mcard.receipt .store2{text-align:center;font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:#8a8272;margin:0 0 10px;}
.mcard.receipt .rrule{border-top:1px dashed #b9b09a;margin:8px 0;}
.mcard.receipt .li{display:flex;justify-content:space-between;font-size:11px;padding:2.5px 0;font-variant-numeric:tabular-nums;}
.mcard.receipt .big{font-family:var(--font-disp);font-weight:800;font-size:30px;letter-spacing:-.03em;
  text-align:center;text-transform:var(--disp-case);margin:8px 0 2px;color:#20233A;}
.mcard.receipt .why{text-align:center;font-size:10.5px;color:#6d6553;margin:0 0 6px;}
.mcard.receipt .total{display:flex;justify-content:space-between;font-weight:700;font-size:11.5px;padding-top:4px;}
.mcard.receipt .thanks{text-align:center;font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:#8a8272;margin:8px 0 0;}

/* poster (tier 1) — printed objects: poster tokens hold in both themes (see tokens file) */
.mcard.poster{border-radius:var(--r-card);padding:22px 20px;overflow:hidden;min-height:190px;
  color:var(--poster-ink);display:flex;flex-direction:column;justify-content:space-between;}
.mcard.poster::before{content:"";position:absolute;inset:0;opacity:.14;pointer-events:none;
  background-image:radial-gradient(rgba(255,255,255,.8) .5px,transparent .8px);background-size:4px 4px;}
.mcard.poster .ghost{position:absolute;right:-12px;bottom:-26px;font-family:var(--font-disp);
  font-weight:800;font-size:130px;letter-spacing:-.06em;line-height:1;opacity:.13;}
.mcard.poster .k{color:rgba(245,239,221,.88);font-size:10px;letter-spacing:.14em;}
.mcard.poster .big{font-family:var(--font-disp);font-weight:800;font-size:36px;letter-spacing:-.04em;
  line-height:.98;margin:10px 0 6px;text-transform:var(--disp-case);}
.mcard.poster .why{font-size:12.5px;color:rgba(245,239,221,.92);max-width:26ch;margin:0;}
.mcard.poster .foot{position:relative;display:flex;justify-content:space-between;align-items:baseline;margin-top:16px;gap:8px;}
.mcard.poster .sig{font-size:16px;color:var(--poster-sig);}
.mcard.poster .date{font-family:ui-monospace,Menlo,monospace;font-size:9.5px;letter-spacing:.08em;
  text-transform:uppercase;color:rgba(245,239,221,.85);}
.mcard.poster.gold{background:var(--poster-gold);} .mcard.poster.teal{background:var(--poster-teal);} .mcard.poster.indigo{background:var(--poster-indigo);}
.mcard.poster .share{position:relative;margin-top:12px;align-self:flex-start;font:inherit;font-size:11.5px;
  font-weight:700;color:#F5EFDD;background:rgba(255,255,255,.14);border:1px solid rgba(245,239,221,.4);
  border-radius:var(--r-pill);padding:6px 14px;cursor:pointer;}
.mcard.poster .share:hover{background:rgba(255,255,255,.24);}

/* story overlay (E) — 9:16 share view */
.story-overlay{position:fixed;inset:0;z-index:60;background:rgba(10,12,20,.86);backdrop-filter:blur(6px);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:20px;}
.story-card{width:min(300px,80vw);aspect-ratio:9/16;border-radius:22px;background:var(--story-bg,#38416B);
  color:#F5EFDD;position:relative;overflow:hidden;padding:26px 20px;display:flex;flex-direction:column;justify-content:space-between;}
.story-card::before{content:"";position:absolute;inset:0;opacity:.12;pointer-events:none;
  background-image:radial-gradient(rgba(255,255,255,.8) .5px,transparent .8px);background-size:4px 4px;}
.story-card .blob{position:absolute;top:-40px;right:-34px;width:130px;height:120px;border-radius:var(--r-dough);opacity:.18;background:#fff;}
.story-card .k{color:rgba(245,239,221,.6);font-size:9px;letter-spacing:.16em;font-family:ui-monospace,Menlo,monospace;text-transform:uppercase;}
.story-card .big{font-family:var(--font-disp);font-weight:800;font-size:44px;letter-spacing:-.04em;line-height:.95;text-transform:var(--disp-case);margin:0 0 8px;}
.story-card .why{font-size:12.5px;color:rgba(245,239,221,.8);margin:0;}
.story-card .brand{display:flex;align-items:center;gap:7px;font-size:11px;color:rgba(245,239,221,.6);}
.story-card .brand i{width:10px;height:10px;background:#F5EFDD;border-radius:var(--r-dough);opacity:.7;}
.story-hint{color:#ECE7DA;font-size:13.5px;margin:0;}
.story-close{font:inherit;font-size:13px;font-weight:700;color:#ECE7DA;background:none;
  border:1px solid rgba(236,231,218,.4);border-radius:var(--r-pill);padding:8px 18px;cursor:pointer;}

/* zone 6 — post kit (copy-button surface; keyboard research verdict Jul 10 2026) */
.pk-note{font-size:13px;color:var(--soft);margin:-6px 0 14px;}
.pk{background:var(--panel);border:0;box-shadow:var(--clay);border-radius:var(--r-card);
  padding:16px 18px;margin-bottom:12px;}
.pk .k{margin-bottom:8px;}
.pk .cap{font-size:14px;white-space:pre-wrap;margin:0 0 12px;color:var(--ink);}
.pk .copy{font:inherit;font-size:13.5px;font-weight:700;color:var(--onchip);background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));
  border:none;border-radius:var(--r-pill);padding:9px 20px;cursor:pointer;}
.pk .copy:hover{filter:brightness(1.03);}
.pk .copy.done{background:var(--ok);}

/* your work — project cards (v2, Jul 11 2026): one <details> per project,
   platform groups inside, media strip + lightbox. finals only, never variations. */
.proj{background:var(--panel);border:0;box-shadow:var(--clay);border-radius:var(--r-card);
  margin-bottom:12px;overflow:hidden;}
.proj-sum{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:16px 18px;cursor:pointer;list-style:none;}
.proj-sum::-webkit-details-marker{display:none;}
.proj-head{display:flex;flex-direction:column;gap:2px;min-width:0;}
.proj-t{font-family:var(--font-disp);font-weight:800;font-size:16.5px;letter-spacing:-.02em;
  text-transform:var(--disp-case);}
.proj-meta{font-family:ui-monospace,Menlo,monospace;font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--soft);}
.proj-chev{flex:none;color:var(--soft);font-size:15px;transition:transform .25s var(--ease-out);}
.proj[open] .proj-chev{transform:rotate(180deg);}
/* the takeover's close carries WORDS, not just the rotated chevron (house rule: anything a
   client meets once keeps its label, and on touch there is no tooltip). It is a <span> inside
   the <summary>, never a nested <button> — the summary already IS the toggle. */
.proj-close{display:none;font-family:ui-monospace,Menlo,monospace;font-size:10px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--soft);}
.proj-x{display:flex;align-items:center;gap:8px;flex:none;}
/* the OPEN card's rule needs air under it too (Juan, Jul 16 — the fix he asked for; a
   previous pass only gave it to the credits rule at the bottom and missed this one).
   2px put the posts grid directly against the perforation, so the line read as an
   underline on the header rather than a separator above the content. */
.proj-body{padding:20px 18px 16px;border-top:1px dashed var(--line);}
.proj-note{font-size:13.5px;color:var(--soft);margin:12px 0 0;}
.proj-from{font-family:ui-monospace,Menlo,monospace;font-size:10px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--soft);margin:8px 0 16px;}
.pgroup-k{margin:16px 0 8px;}
.proj .pk{background:var(--sunk);border:none;}

/* in the works — active / under-review deliverables. Same .proj card as 'your work';
   a WARM status pill (terracotta/amber/teal, never blue — masa is not a SaaS dashboard)
   carries the state, and waiting/review cards lock the copy/post/save actions the way
   the capture review card does (a draft isn't postable until it's approved). */
#inprog{margin-bottom:16px;}
#inprog .proj:last-child{margin-bottom:0;}
.proj-status{align-self:flex-start;font-family:ui-monospace,Menlo,monospace;font-size:9.5px;
  font-weight:700;letter-spacing:.11em;text-transform:uppercase;border-radius:var(--r-pill);
  padding:3px 10px;margin:5px 0 1px;border:1px solid transparent;line-height:1.5;}
.proj-status.review{color:var(--gold);background:color-mix(in srgb,var(--gold) 12%,transparent);
  border-color:color-mix(in srgb,var(--gold) 42%,transparent);}
.proj-status.waiting{color:var(--coral);background:color-mix(in srgb,var(--coral) 12%,transparent);
  border-color:color-mix(in srgb,var(--coral) 42%,transparent);}
.proj-status.delivered{color:var(--ok);background:color-mix(in srgb,var(--ok) 12%,transparent);
  border-color:color-mix(in srgb,var(--ok) 42%,transparent);}
/* a 'waiting on you' pill earns a live cue — a quiet pulse says "your move" without words */
@media(prefers-reduced-motion:no-preference){
  .proj-status.waiting{animation:wip-pulse 2.6s ease-in-out infinite;}
  @keyframes wip-pulse{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--coral) 40%,transparent);}
    50%{box-shadow:0 0 0 5px transparent;}}
}
/* review + approve → : a straight shot into the thread from a waiting card (stops the
   click from toggling the card — it navigates instead) */
.proj-cta{align-self:flex-start;font-size:12.5px;font-weight:700;color:var(--ink);
  text-decoration:none;margin:4px 0 1px;}
.proj-cta:hover,.proj-cta:focus-visible{text-decoration:underline;text-underline-offset:2px;}
/* lock note — closes the body of a waiting/review card, the way the capture review card does */
.proj-lock{font-size:12.5px;color:var(--soft);margin:14px 0 2px;padding-top:12px;
  border-top:1px dashed var(--line);font-style:italic;}
/* locked cards: nothing here is approved to use yet, so hide copy/post/save + save-all.
   The media + captions stay fully visible (that's the point — they're REVIEWING it). */
.proj.wip-locked .pactions,
.proj.wip-locked .pk .copy,
.proj.wip-locked .fin-save{display:none;}
.proj.wip-locked .pcap{padding-bottom:14px;} /* no action row → keep the caption's bottom air */

/* post cards — the post is the unit: true-ratio media + its copy attached below.
   post-shaped, not a platform-chrome clone (agnostic canon). */
/* ALIGNMENT (Jul 16 2026 r2, web-checked + Juan's revised call): media keeps its TRUE ratio
   AND the caption boxes still line up across a row. Subgrid is the tool: each card spans two
   shared rows (media / tail), so the media row sizes to the TALLEST media in that row and a
   shorter piece bottom-aligns inside it (align-self:end) — every caption box, and so every
   action row, starts on the same line with nothing cropped. ⚑ r1 normalized all media to a
   uniform 4:5 crop to buy the same alignment; Juan reverted that (crop > eye-jump was the
   wrong trade), which RESTORES the v4.2 "true ratios on the card" rule.
   Subgrid = Baseline widely available (Safari 16 / Chrome 117 / FF 71); @supports carries
   the rest. align-self:end is load-bearing twice: grid's default `stretch` would blow out
   the aspect-ratio box AND top-align it. */
.posts{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px;margin:0 0 12px;
  align-items:start;} /* cards keep their natural height — no stretch gap between copy and actions */
/* THE CARD SURFACE LIVES ON ITS PARTS, NOT ON .post (Jul 16 2026, Juan's catch).
   Subgrid makes each card SPAN the full media row, so a card that painted its own
   background filled the empty space above a shorter piece — a 4:5 carousel next to a
   9:16 reel wore a slab of dark blue above its photo. The card box still spans (subgrid
   requires it), but it is now TRANSPARENT: .pmedia and .ptail carry the surface, so the
   background starts at the top of the picture and hugs it. .pmedia keeps its own radius
   + clip, which is also what rounds the media's own corners. (--clay is `none`, so there
   is no shadow to reunite across the two boxes — if a shadow ever returns, put a
   filter:drop-shadow on .post so it follows the painted union, not the spanned box.) */
.post{background:none;border:0;display:flex;flex-direction:column;min-width:0;}
.posts > .post > .pmedia{background:var(--g);border-radius:var(--r-sm) var(--r-sm) 0 0;}
.posts > .post > .ptail{background:var(--sunk);border-radius:0 0 var(--r-sm) var(--r-sm);}
@supports (grid-template-rows:subgrid){
  .posts{align-items:stretch;} /* the card must span the row for subgrid to inherit it */
  .posts > .post{display:grid;grid-row:span 2;grid-template-rows:subgrid;row-gap:0;}
  .posts > .post > .pmedia{grid-row:1;align-self:end;}
  /* ⚑ align-self:start is LOAD-BEARING (Juan's catch Jul 17 2026 — measured).
     The subgrid shares BOTH rows, so grid's default `stretch` made every tail as tall as
     the TALLEST tail in the row. Pressing "… more" on one card therefore grew its two
     neighbours by the identical 467px — of pure painted dead space (their captions never
     changed). The `final / what we proposed` chips did the same at 31px to the script card.
     Same bug, three symptoms: ANY tail growth was charged to the whole row.
     `start` lets each tail be its own natural height, so nothing below the picture is
     shared. The MEDIA row still aligns (align-self:end on row 1) — that's the pairing win,
     and it is untouched. This is also what NN/g prescribes: cards vary in HEIGHT to fit
     their content; it's the WIDTH that stays fixed. */
  .posts > .post > .ptail{grid-row:2;display:flex;flex-direction:column;align-self:start;}
}
.posts .pmedia{overflow:hidden;}
/* aspect-ratio alone yields to taller content (the 9:16 video/slide strip drove the box
   past 4:5) — pin the MEDIA layer to fill the box so the ratio governs; overlays
   (.ptag/.dots/.stxt/.spills/.restag) keep their own absolute placement. */
.posts .pmedia > .pvid,
.posts .pmedia > .slides,
.posts .pmedia > .popen,
.posts .pmedia > .pmed-empty,
.posts .pmedia > img,
.posts .pmedia > video{position:absolute;inset:0;width:100%;height:100%;}
.pmedia{position:relative;background:var(--g);}
.pmedia .popen{padding:0;border:none;background:none;cursor:pointer;display:block;width:100%;height:100%;}
.pmedia img,.pmedia video{width:100%;height:100%;object-fit:cover;display:block;}
/* honest empty state — a deliverable approved before its media lands (the engine
   flags a need, never invents media). An intentional tile, never a broken <img>. */
.pmed-empty{width:100%;height:100%;min-height:120px;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:8px;background:var(--sunk);color:var(--soft);
  border:1px dashed var(--line);}
.pmed-empty svg{opacity:.55;}
.pmed-empty-t{font-size:12.5px;letter-spacing:.01em;}
.slides .slide:has(.pmed-empty){cursor:default;}
.restag{position:absolute;top:8px;right:8px;font-family:ui-monospace,Menlo,monospace;font-size:9px;
  letter-spacing:.07em;text-transform:uppercase;color:#F5EFDD;background:rgba(10,16,69,.72);
  border-radius:var(--r-pill);padding:3px 8px;pointer-events:none;}
.ptag{position:absolute;top:8px;left:8px;font-family:ui-monospace,Menlo,monospace;font-size:9.5px;
  letter-spacing:.08em;text-transform:uppercase;color:#F5EFDD;background:rgba(10,16,69,.72);
  border-radius:var(--r-pill);padding:3px 9px;pointer-events:none;}
/* story text overlay — sits in the safe zone (clear of the top ~14% / bottom ~21% a platform UI covers) */
/* ⚑ A TEXT-SHADOW IS NOT A LEGIBILITY STRATEGY (Juan caught it on a real phone, Jul 17 2026).
   This was cream text + a soft navy blur — which works on the dark media it was designed
   against and FAILS on light media: on the cream sun story it read as a muddy ghost, barely
   legible. Media is client-supplied; its luminance is unknowable at author time, so the
   contrast has to come from a SCRIM the text carries with it, not from luck about the photo.
   The scrim is sized to the text (inline padding box), so it still shows placement inside the
   9:16 safe zone rather than covering the frame. */
.stxt{position:absolute;left:10%;right:10%;top:38%;margin:0;text-align:center;
  font-family:var(--font-disp);font-weight:800;font-size:15px;line-height:1.35;color:#F5EFDD;
  pointer-events:none;
  background:rgba(10,16,69,.62);backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  border-radius:10px;padding:8px 10px;
  text-shadow:0 1px 2px rgba(10,16,69,.9);}
/* carousel — one piece of content: slides snap-scroll inside the frame, dots track position */
.slides{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;height:100%;overscroll-behavior-x:contain; /* swiping the carousel never chains into the posts lane */
  scrollbar-width:none;-webkit-overflow-scrolling:touch;}
.slides::-webkit-scrollbar{display:none;}
.slide{flex:0 0 100%;height:100%;padding:0;border:none;background:none;cursor:pointer;scroll-snap-align:start;}
.dots{position:absolute;left:0;right:0;bottom:8px;display:flex;justify-content:center;gap:5px;pointer-events:none;}
.dots i{width:6px;height:6px;border-radius:50%;background:rgba(245,239,221,.45);}
.dots i.on{background:#F5EFDD;}
/* SCRIPT cover — a script has no media yet, so the media zone carries a cover instead:
   same box, same ratio, same row. It is a SKIN of the card, never a special case. */
/* the cover is a LINK (it opens the script — same as every other kind's media opening the
   lightbox), so it must not look like body copy that happens to be clickable */
/* the dashed rule must follow the frame's corners (Juan, Jul 16): .pmedia rounds its TOP
   corners and clips, so a square-cornered border got sliced off up there and stayed square
   at the bottom — most visible on hover, when it turns accent. Match the parent exactly:
   round the top, leave the bottom square where it meets .ptail. */
.scover{width:100%;height:100%;display:flex;flex-direction:column;gap:6px;justify-content:center;
  padding:16px 15px;background:var(--g);border:1px dashed var(--line);
  border-radius:var(--r-sm) var(--r-sm) 0 0;
  text-decoration:none;color:inherit;cursor:pointer;}
.scover:hover{border-color:var(--soft);}
.scover:hover .scover-t{color:var(--ink);}
.scover-k{font-family:ui-monospace,Menlo,monospace;font-size:9.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--soft);}
.scover-t{font-family:var(--font-disp);font-weight:800;font-size:16px;line-height:1.25;margin:0;color:var(--ink);}
.scover-m{font-family:ui-monospace,Menlo,monospace;font-size:9.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--soft);margin:0;}
.scover-q{font-size:12.5px;line-height:1.45;color:var(--soft);margin:6px 0 0;font-style:italic;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3;overflow:hidden;}
.postit.sopen{text-decoration:none;display:inline-flex;align-items:center;}
.pcap{padding:12px 14px 13px;display:flex;flex-direction:column;gap:10px;min-width:0;}
.pcap .cap{font-size:13px;white-space:pre-wrap;margin:0;color:var(--ink);min-width:0;}
/* CLAMP + INLINE "more" (Jul 16 2026 r2). max-height, NOT -webkit-line-clamp: the two are
   mutually exclusive, because display:-webkit-box breaks float placement and lands the
   label on line 1 (verified in-browser — it renders wrong, not obviously-broken).
   --lines/--lh drive BOTH the clamp and the float's notch from one place; let them drift
   apart and max-height half-clips a third line. */
.pcap .cap{--lines:2;--lh:1.5;line-height:var(--lh);}
.pcap .cap.clamped{max-height:calc(var(--lines) * var(--lh) * 1em);overflow:hidden;}
/* reserve 2 lines even for a short caption so caption boxes are equal height across a row
   → action rows line up (Juan's "buttons in the same place" ask). Grid-only. */
.posts .pcap .cap.clamped{min-height:2lh;}
/* Preview type (Juan: "the 2-line preview seems small"). 13px → 14.5px lifts it to BODY
   size and no further — deliberately NOT bigger. A card excerpt is tier-3 hierarchy (media,
   then kind, then copy); up-scaling past body makes it compete with the media and reads as
   louder, not calmer. The real constraint is line length: a ~260px card at 14.5px sits near
   ~42 characters/line, already under the 45–75 readable band, and every extra px pushes it
   further down. Air + a quieter ink do the "minimalist" work instead of size. */
.posts .pcap .cap{font-size:14.5px;line-height:1.5;}
/* the inline "more" — a reverse float that carves a notch out of the LAST line only and
   sits in it. shape-outside silently no-ops without all three of float + width + height.
   The button must be the paragraph's FIRST child while clamped (portal.js moves it).
   WCAG 2.2 SC 2.5.8 explicitly EXEMPTS inline targets in a sentence — but conformance
   isn't usability, so it still takes horizontal padding; the portal is phone-dominant and
   a thumb deserves better than the legal minimum. */
.cap-toggle{background:none;border:0;font:inherit;font-size:inherit;font-weight:700;
  color:var(--soft);cursor:pointer;padding:0 0 0 .35em;}
.cap.clamped .cap-toggle{float:right;height:calc(var(--lines) * var(--lh) * 1em);width:4.4em;
  shape-outside:inset(calc(100% - (var(--lh) * 1em)) 0 0);
  display:flex;align-items:flex-end;justify-content:flex-end;}
/* expanded: the float is off and the control has moved to the end of the text, so it just
   trails the last word like any inline word would */
.cap:not(.clamped) .cap-toggle{float:none;display:inline;height:auto;width:auto;}
.cap-toggle:hover{color:var(--ink);}
.pcap .copy{align-self:flex-start;font:inherit;font-size:12.5px;font-weight:700;color:var(--onchip);
  background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));border:none;border-radius:var(--r-pill);padding:8px 16px;cursor:pointer;}
.pcap .copy:hover{filter:brightness(1.03);}
.pcap .copy.done{background:var(--ok);}
.plabel{font-family:ui-monospace,Menlo,monospace;font-size:9.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--soft);margin:0;padding:0 14px 12px;}
.post.story .plabel,.post:not(:has(.pcap)) .plabel{padding-top:12px;}
.pactions{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
/* icon CTAs (trial, Juan's call Jul 11 2026) — words live in the tooltip/aria + the status line */
.pcap .copy.icon{font-size:16px;line-height:1;padding:0;width:38px;height:38px;border-radius:50%;
  display:grid;place-items:center;}
.postit.icon{font-size:15px;line-height:1;padding:0;width:38px;height:38px;border-radius:50%;
  display:grid;place-items:center;}
.pstatus{font-size:11.5px;font-weight:600;color:var(--soft);min-height:1em;flex:1;}
.ptabs{display:flex;gap:6px;flex-wrap:wrap;}
.ptab{font:inherit;font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--soft);background:none;border:1px solid var(--line);border-radius:var(--r-pill);
  padding:4px 12px;cursor:pointer;}
.ptab.on{color:var(--oncolor);background:var(--ink);border-color:var(--ink);}
.postit{font:inherit;font-size:12.5px;font-weight:700;color:var(--ink);background:none;
  border:1.5px solid var(--line);border-radius:var(--r-pill);padding:7px 15px;cursor:pointer;}
.postit:hover{color:var(--ink);background:var(--sunk);}
/* story sticker pills — previewed in the lower safe zone (above the bottom ~21% platform UI) */
.spills{position:absolute;left:8%;right:8%;bottom:24%;display:flex;justify-content:center;
  gap:6px;flex-wrap:wrap;pointer-events:none;}
.spill{font-size:10.5px;font-weight:700;color:#0A1045;background:rgba(245,239,221,.92);
  border-radius:var(--r-pill);padding:4px 11px;box-shadow:0 2px 8px rgba(10,16,69,.3);}
.stk{display:flex;flex-direction:column;gap:6px;padding:9px 14px;border-top:1px dashed var(--line);}
.stk-t{font-family:ui-monospace,Menlo,monospace;font-size:9.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--soft);white-space:nowrap;}
.stk-seg{display:flex;align-items:center;gap:8px;}
.stk-v{font-size:12px;font-weight:700;flex:1;min-width:0;overflow-wrap:anywhere;}
.stk-c.icon{font:inherit;font-size:13px;line-height:1;color:var(--ink);background:none;
  border:1px solid var(--line);border-radius:50%;width:30px;height:30px;flex:none;
  display:grid;place-items:center;cursor:pointer;}
.stk-c.icon:hover{border-color:var(--soft);}
/* scroll focus — the in-view post settles to full presence (masa motion canon:
   CSS scroll-driven, transform/opacity only; info never hides behind the focus state) */
@media (prefers-reduced-motion:no-preference){
  @supports (animation-timeline: view()){
    .post{animation:postfocus linear both;animation-timeline:view();
      animation-range:entry 0% entry 60%;}
    @keyframes postfocus{from{opacity:.5;transform:translateY(14px) scale(.975);}to{opacity:1;transform:none;}}
  }
}
.story-actions{display:flex;gap:10px;}

/* media strip — uniform grid (order + calm; masonry rejected per gallery research) */
.mstrip{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:0 0 12px;}
@media(max-width:640px){.mstrip{grid-template-columns:repeat(3,1fr);}}
.thumb{position:relative;padding:0;border:1px solid var(--line);border-radius:var(--r-sm);
  overflow:hidden;cursor:pointer;background:var(--sunk);aspect-ratio:1;}
.thumb img{width:100%;height:100%;object-fit:cover;display:block;}
.thumb .play,.pvid .play{position:absolute;inset:0;display:grid;place-items:center;font-size:22px;
  color:#F5EFDD;background:rgba(10,16,69,.34);}
.thumb .dur{position:absolute;right:6px;bottom:6px;font-family:ui-monospace,Menlo,monospace;
  font-size:9.5px;color:#F5EFDD;background:rgba(10,16,69,.72);border-radius:var(--r-pill);padding:2px 7px;}
.thumb:hover{border-color:var(--soft);}

/* file / link chips */
.filechip{display:flex;align-items:center;gap:10px;background:var(--sunk);border:1px solid var(--line);
  border-radius:var(--r-sm);padding:11px 14px;margin:0 0 8px;text-decoration:none;color:var(--ink);}
.filechip .fc-t{font-weight:700;font-size:13.5px;flex:1;min-width:0;}
.filechip .fc-go{font-size:12.5px;font-weight:700;color:var(--ink);white-space:nowrap;}
.filechip:hover{border-color:var(--soft);}

/* rich link card — authored preview (image 1.91:1, title, domain) */
.linkcard{display:flex;flex-direction:column;background:var(--sunk);border-radius:var(--r-sm);
  overflow:hidden;text-decoration:none;color:var(--ink);margin:0 0 10px;max-width:420px;
  box-shadow:var(--clay);transition:transform .25s var(--ease-out);}
.linkcard:hover{transform:translateY(-2px);}
.linkcard img{width:100%;aspect-ratio:1.91/1;object-fit:cover;display:block;}
.lc-meta{display:flex;flex-direction:column;gap:3px;padding:12px 14px 4px;}
.lc-t{font-weight:700;font-size:14px;}
.lc-d{font-size:12.5px;color:var(--soft);}
.lc-f{display:flex;justify-content:space-between;align-items:baseline;padding:6px 14px 12px;}
.lc-dom{font-family:ui-monospace,Menlo,monospace;font-size:10px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--soft);}

/* print = the PDF export: File→Print (or share→print) gives a clean paper copy */
@media print{
  body{background:#fff;color:#0A1045;}
  .bar .themebtn,.pactions,.stk-c,.share,.stat-more,.pk .copy,.story-overlay,dialog{display:none !important;}
  .proj{box-shadow:none;border:1px solid #DBE1F4;}
  details.proj{page-break-inside:avoid;}
  details.proj:not([open]) .proj-body{display:block;} /* everything prints, open or not */
  .card,.stat,.tl,.appr li,.pk,.post,.statbox{box-shadow:none;border:1px solid #DBE1F4;}
  video{display:none;}
}

/* lightbox — native dialog */
.lightbox{border:none;padding:0;background:transparent;max-width:min(92vw,760px);width:auto;}
.lightbox::backdrop{background:rgba(10,12,20,.86);backdrop-filter:blur(6px);}
.lb-fig{margin:0;display:grid;place-items:center;}
.lb-fig img,.lb-fig video{max-width:min(88vw,720px);max-height:72vh;border-radius:14px;display:block;}
.lb-cap{display:flex;justify-content:center;align-items:baseline;gap:12px;text-align:center;
  color:#ECE7DA;font-size:13px;margin:10px 2px 0;}
.lb-cap a{color:#ECE7DA;font-weight:700;text-decoration:none;border:1px solid rgba(236,231,218,.4);
  border-radius:var(--r-pill);padding:5px 14px;}
.lb-nav{display:flex;justify-content:center;gap:10px;margin-top:12px;}
.lb-btn{font:inherit;font-size:13px;font-weight:700;color:#ECE7DA;background:none;
  border:1px solid rgba(236,231,218,.4);border-radius:var(--r-pill);padding:8px 18px;cursor:pointer;}
.lb-btn:hover{background:rgba(255,255,255,.12);}

/* zone 5 — approvals */
.appr{list-style:none;margin:0 0 30px;padding:0;display:flex;flex-direction:column;gap:10px;}
.appr li{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
  background:var(--panel);border:0;box-shadow:var(--clay);border-radius:var(--r-sm);padding:13px 16px;}
.appr .t{font-weight:700;font-size:14.5px;}
.appr .w{display:block;font-size:12.5px;color:var(--soft);font-weight:400;}
.appr .go{font-size:13px;font-weight:700;color:var(--ink);text-decoration:none;white-space:nowrap;}
.appr .go:hover{text-decoration:underline;}
.appr li.none{justify-content:flex-start;color:var(--soft);font-size:14px;}

footer{text-align:center;font-size:12.5px;color:var(--soft);margin-top:14px;}
footer .hand{font-size:18px;display:block;margin-top:4px;}

/* motion — masa spec: transform/opacity only, spring on character moments only */
@media(prefers-reduced-motion:no-preference){
  .rise{opacity:0;transform:translateY(14px);
    animation:rise .5s var(--ease-out) forwards;animation-delay:var(--d,0ms);}
  @keyframes rise{to{opacity:1;transform:none;}}
  /* milestone cards = the character moment: stamp-in on a spring */
  .mcard.rise{transform:scale(1.05) rotate(.6deg);
    animation:stamp .55s cubic-bezier(.34,1.38,.5,1) forwards;animation-delay:var(--d,0ms);}
  @keyframes stamp{to{opacity:1;transform:none;}}
  .tl li.cur .dot{animation:herepulse 2.6s ease-in-out infinite;}
  @keyframes herepulse{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--gold) 45%,transparent);}
    50%{box-shadow:0 0 0 5px transparent;}}
}
@media(prefers-reduced-motion:reduce){body{transition:none;}.buddy,.buddy .eye{animation:none;}}

/* your-work cards open/close smoothly — native ::details-content transition (2025 CSS,
   progressive enhancement: unsupported browsers just snap, nothing breaks) */
@media(prefers-reduced-motion:no-preference){
  @supports (interpolate-size: allow-keywords){
    :root{interpolate-size:allow-keywords;}
    .proj::details-content{block-size:0;overflow:clip;
      transition:block-size .45s var(--ease-out),content-visibility .45s allow-discrete;}
    .proj[open]::details-content{block-size:auto;}
  }
}
/* collapsed-card media peek: closed cards tease their media; open shows the full layout */
.peek{display:flex;gap:4px;margin-top:8px;}
.peek img{width:34px;height:34px;object-fit:cover;border-radius:8px;display:block;}
.peek .more{width:34px;height:34px;border-radius:8px;background:var(--sunk);color:var(--soft);
  display:grid;place-items:center;font-size:10.5px;font-weight:700;}
.proj[open] .peek{display:none;}
/* bento preview — the newest card leads with pure media at one shared height */
.bento{display:flex;gap:7px;margin-top:12px;height:148px;overflow-x:auto;
  scrollbar-width:none;border-radius:12px;}
.bento::-webkit-scrollbar{display:none;}
.bento img{height:100%;width:auto;object-fit:cover;border-radius:10px;display:block;flex:none;}
@media(max-width:640px){.bento{height:112px;}}
.proj[open] .bento{display:none;}

/* shelf open/close rides the View Transitions API — each card is a named group and the
   browser FLIPs the whole reflow (web-verified pattern; contain:paint prevents glitches) */
.gallery .proj{contain:paint;}
::view-transition-group(*){animation-duration:.45s;animation-timing-function:cubic-bezier(.22,.61,.36,1);}
@media(prefers-reduced-motion:reduce){
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none !important;}
}

/* dialogs enter on the brand spring (transform/opacity only, canon) */
@media(prefers-reduced-motion:no-preference){
  dialog[open]{animation:dlg-in .42s var(--spring);}
  @keyframes dlg-in{from{opacity:0;transform:translateY(14px) scale(.955);}}
  dialog[open]::backdrop{animation:bd-in .3s var(--ease-out);}
  @keyframes bd-in{from{opacity:0;}}
}
/* icon buttons: svg OPTICALLY centered (the send plane + play lean; nudge them back) */
.pactions .icon svg,.stk-c.icon svg{display:block;}
.postit.icon svg{transform:translate(-1px,1px);} /* paper plane reads up-right without this */
.play svg{transform:translateX(1.5px);}          /* triangles read left of center */
.spill svg{vertical-align:-1.5px;}
.proj-chev svg{display:block;}
.play svg{fill:currentColor;stroke:none;}

/* phone: social posts read as a SWIPE ROW (fixed lane, next card peeks = the discoverability
   cue mobile carousels need); newsletter + site cards stay full-width vertical. Lives at the
   END of the sheet + .gallery-scoped so it outranks the base .posts grid (bug Juan caught). */
@media(max-width:759px){
  /* ⚑ THE PILE, ON THE PHONE TOO (Juan caught this on a real device, Jul 17 2026).
     Canon says "tiles are PILES" — but the entire pile treatment was scoped to
     min-width:760px, so the phone silently fell back to the RAW .bento scroller and looked
     broken: a strip of ragged-width thumbs (63/90/75/168/75/168px — `height:100%;width:auto`
     lets each keep its native ratio) with the 4th image CLIPPED MID-SUBJECT at the strip's
     edge. Measured: scrollWidth 674 vs 315 visible. The pile fixes it at the root — no
     scroller, no ragged widths, nothing clipped — and it is the design already ruled in and
     shipped on desktop; the phone just never received it.
     Phone-scoped duplicate rather than un-scoping the desktop block: those rules are
     entangled with the bento GRID's tile layout (display:flex on the card, a full-height
     .proj-head) that the phone's single column does not use. max-height caps it so one
     project can't eat the whole screen on a tall phone. */
  .gallery .proj:not([open]) .bento{
    display:block;position:relative;height:auto;overflow:visible;
    aspect-ratio:4/5;max-height:46vh;margin:12px 0 2px;}
  .gallery .proj:not([open]) .bento img{
    position:absolute;inset:0;width:100%;height:100%;object-fit:cover;border-radius:10px;
    flex:none;display:block;}
  .gallery .proj:not([open]) .bento img:nth-child(n+5){display:none;}
  .gallery .proj:not([open]) .bento img:nth-child(1){z-index:4;}
  .gallery .proj:not([open]) .bento img:nth-child(2){z-index:3;transform:rotate(-3.5deg) translate(-3.5%,1.5%);}
  .gallery .proj:not([open]) .bento img:nth-child(3){z-index:2;transform:rotate(3deg) translate(3.5%,-1%);}
  .gallery .proj:not([open]) .bento img:nth-child(4){z-index:1;transform:rotate(-5.5deg) translate(-1%,3%);}

  /* ⚑ align-items:flex-start is LOAD-BEARING here, for the SAME reason as the desktop
     subgrid's align-self:start — and it is a SECOND mechanism, which is why fixing the
     grid did not fix this (Juan caught it; measured). A flex row's default `stretch` makes
     every card match the TALLEST, and the phone lane is ONE flex line, so it was worse than
     desktop: pressing "… more" on one card grew ALL FIVE by 436px, where desktop only hit
     the 2 cards sharing its grid row. Cards in a swipe lane are read one at a time — there
     is nothing to align to, so there is no reason to pay for equal heights. */
  /* ⚑ :is(.gallery,.tk) — NOT .gallery alone. An open card is REPARENTED into the takeover
     dialog (see below), so it stops being a .gallery descendant the moment it opens. Every
     lane rule scoped to .gallery would have silently stopped applying exactly when the
     takeover needs it — the lane would fall back to a grid mid-flight. (.gallery stays in the
     selector for the resize-while-open edge: a card opened on the desktop shelf, then dragged
     narrow, is still in the grid.)

     ⚑ THE POSTS STACK VERTICALLY (Juan's call Jul 17 2026, web-checked). WAS: a horizontal
     snap lane — which put a horizontal CAROUSEL inside a horizontal LANE, so the same swipe
     meant two different things depending on where the finger landed. Juan felt it as "sliding
     among the slides doesn't work as expected".
     ⛔ There is NO css value that fixes it — both ends are broken, and I measured both:
       · `overscroll-behavior-x:contain` → a swipe on the media can NEVER leave a carousel.
         The lane stayed at 311 through a 5-unit swipe, and the media is ~80% of the card:
         the biggest target on screen dead-ends.
       · chaining (`auto`) → one flick skipped TWO posts (measured 311 → 931).
     So the fix is to REMOVE the nesting, not tune it. The research agrees and is unusually
     blunt: nested snap is UNDER-SPECIFIED at the spec level (w3c/csswg-drafts#9187 "Improve or
     clarify nested snap behaviors" — "too easy on all browsers to accidentally scroll into the
     next section"), nested scrollers are "one of the biggest accessibility evils" (Roselli),
     and NN/g asks for a carousel that is "not so sensitive that it flies past multiple slides
     with one flick" — which is the exact overshoot chaining produces.
     Vertical stack ⇒ the ONLY horizontal gesture left on the screen is the carousel. No
     nesting, no ambiguity. It is also what every feed does, which is why carousel-inside-a-
     carousel does not exist in the wild. The takeover has the room: the horizontal lane was
     designed when a card was a tile competing for space INSIDE the grid, and the takeover's
     whole premise is that the kit owns the screen. Cards keep their 18px inset and their
     rounded frame — only the AXIS changes. */
  :is(.gallery,.tk) .posts{display:flex;flex-direction:column;align-items:stretch;
    grid-template-columns:none;overflow:visible;gap:14px;scroll-snap-type:none;padding-bottom:6px;}
  :is(.gallery,.tk) .posts>*{flex:0 0 auto;width:100%;min-width:0;}
  /* uniform lane: every card's media crops to one 4:5 frame (browse surface = scent;
     the true ratio lives one press away in the lightbox). !important outranks the
     per-kind inline ratio only inside the phone lane. This lane is a horizontal SCROLLER,
     not a grid — cards are read one at a time, so there is no row to align to and the
     desktop subgrid deliberately does not apply here (it is overridden back to flex). */
  :is(.gallery,.tk) .posts .pmedia{aspect-ratio:4/5 !important;align-self:auto;}
  :is(.gallery,.tk) .posts>*{display:flex;flex-direction:column;}
  /* ⚑ align-self:stretch is a BUG FIX, not tidying (measured on the LIVE portal Jul 17 2026:
     media 298px wide, tail 166px — the caption block shrink-wrapped to its own text, narrower
     than the picture above it, on every post card on every phone).
     WHY: the subgrid block above sets `.ptail{align-self:start}` — correct there, where it
     stops one grown tail dragging its row-mates (the Jul 17 "… more" fix). But this lane
     overrides the card back to `display:flex; flex-direction:column`, and in a flex COLUMN
     the cross axis is HORIZONTAL — so the same `start` that meant "don't stretch my height"
     silently came to mean "don't stretch my width".
     The trap was already known here: the `.pmedia` rule directly above resets align-self for
     exactly this reason. The Jul-17 tail rule landed later and never got its matching reset.
     ⛔ Any align-self set in the subgrid block needs a partner reset in this lane. */
  :is(.gallery,.tk) .posts .ptail{flex:1;display:flex;flex-direction:column;align-self:stretch;}
  :is(.gallery,.tk) .posts .pcap{flex:1;} /* boxes flush (lane-hug tried + removed Jul 12 — not worth it) */

  /* ── THE TAKEOVER (Juan's call Jul 17 2026) ────────────────────────────────────────────
     An open project OWNS the phone. Not a new page — the tile's own box morphs to fill the
     viewport via the same-document view transition the shelf already runs (every .proj
     already carries a unique view-transition-name, so the browser FLIPs tile-rect →
     viewport-rect for free; no layout math, no library).
     WHY: an inline-expanding <details> made the open project compete with every sibling tile
     and the whole page around it — Juan's "too congested". The card is an entry point (NN/g:
     "a linked entry point to further details, rather than the full details themselves"), so
     opening it should GO somewhere. Full-bleed gives the kit the whole screen and nothing to
     compete with, which is the entire point.
     Phone only: on desktop the shelf already has room to expand into a full row.

     ⚑ IT LIVES IN THE TOP LAYER (Jul 17 2026) — it was position:fixed until now.
     The open card is REPARENTED into <dialog class="tk"> and shown with showModal().
     THE REASON IS PRESENT-TENSE, not futureproofing: focus was NOT trapped. A keyboard or
     screen-reader user could tab straight out of a view that owns the whole screen and into
     the page behind it, with no way to tell they had left. showModal() traps focus and makes
     the background inert, and throws in Esc, both for free from the platform.
     It also retires a standing ⛔ rule rather than restating it: position:fixed resolves
     against the viewport ONLY while no ANCESTOR of .proj has transform / filter / perspective
     / backdrop-filter / contain:layout|paint|strict. Any one of those silently made that
     ancestor the containing block and the takeover quietly stopped being full-screen — it
     broke by DRIFTING, not by erroring, which is the kind you ship. The top layer has no
     ancestors to be trapped by, so "never add a transform to .gallery" is no longer a rule
     anyone has to remember; the trap is gone, not merely documented.
     The morph survives the move: the card keeps its view-transition-name across the reparent,
     so the tile still flies tile-rect → viewport-rect. */
  /* the phone's dialog.tk overrides do NOT live here — they sit next to the base rules,
     further down, because source order decides between two selectors of equal specificity
     and the base block comes after this media query. See "THE TAKEOVER, AT ANY WIDTH". */
  /* ⚑ overflow:visible is LOAD-BEARING — it is what makes the sticky header work.
     The base `.proj{overflow:hidden}` clips the rounded TILE (right for a card in the grid).
     But `overflow:hidden` establishes a SCROLLPORT, and `position:sticky` pins to its nearest
     scrollport ancestor — so the header pinned to the CARD, which does not scroll, instead of
     to the dialog, which does. The header then just rode up with the content and left the
     screen (measured: top -28px), taking the only way out of a full-screen view with it.
     Full-bleed means border-radius:0, so there is no longer anything to clip here. */
  .tk .proj[open]{margin:0;border:0;border-radius:0;background:var(--panel);overflow:visible;
    min-height:100%;padding-bottom:env(safe-area-inset-bottom,0px);}
  /* the header stays put so the way out is ALWAYS on screen — a full-bleed view with the
     close scrolled off is a trap, and this client opens the portal ~twice a month */
  .tk .proj[open] .proj-sum{position:sticky;top:0;z-index:2;background:var(--panel);
    padding-top:calc(16px + env(safe-area-inset-top,0px));}
  .tk .proj[open] .proj-close{display:inline;}
  /* FULL BLEED (Juan's call Jul 17 2026): the lane runs to both edges of the phone.
     The kit owns the screen — a gutter around the media is the tile's idiom, and keeping it
     here just re-imports the framing the takeover exists to escape. The body loses its
     horizontal padding; the lane pays it back as scroll-padding so the FIRST card still
     starts inboard and the last can scroll clean to the edge. Text keeps its own inset —
     full-bleed is for MEDIA; a line of copy running into the bezel is not a design, it's a
     measure problem. */
  .tk .proj[open] .proj-body{border-top:1px dashed var(--line);padding-left:0;padding-right:0;}
  .tk .proj[open] .proj-body > *:not(.posts){margin-left:18px;margin-right:18px;}
  /* the stack keeps the 18px inset the full-bleed ruling gave the lane back as scroll-padding.
     No scroll-padding / flex-basis:88% any more — those bought the NEXT card's peek in a
     horizontal lane, and a vertical stack shows the next card by simply being below. */
  .tk .proj[open] .posts{padding-left:18px;padding-right:18px;}
  /* MADE FROM sits close to the work it credits (Juan: it should have a chance above the
     fold). It was 30px + a 20px rule below a lane that already ends in air — provenance is
     the trust position, so it should read as this kit's receipt, not as page furniture
     stranded at the bottom. */
  .tk .proj[open] .proj-from-end{margin:14px 18px 4px;padding-top:12px;}
  /* the page behind must not scroll under the takeover. showModal() makes it INERT (no
     focus, no clicks) but does NOT stop it scrolling, so this stays. */
  body.takeover{overflow:hidden;}
}
/* ── THE TAKEOVER, AT ANY WIDTH ───────────────────────────────────────────────────────────
   ⚑ These MUST live outside the phone media query. A <dialog> with no styling is not
   invisible — the UA paints its OWN white background, 1px border and 1em padding around the
   content, and centres it. The project takeover is phone-only so that never showed; the
   SCRIPT view opens this same dialog on DESKTOP, where it framed the reading view in a white
   UA box (caught in a screenshot — computed styles all looked fine).
   Desktop shape = a centred reading panel, not full-screen: a script is read, and a 5-shot
   script stretched across 1400px is a measure problem. The phone block above takes it
   edge-to-edge, where owning the screen IS the point. */
dialog.tk{border:0;padding:0;margin:auto;background:var(--panel);color:inherit;
  width:min(720px,94vw);max-width:none;max-height:88vh;border-radius:var(--r-card);
  overflow-y:auto;overscroll-behavior:contain;}
dialog.tk::backdrop{background:rgba(10,12,20,.62);}
/* ⚠️ this override must come AFTER the base rule, not up in the phone block — the two
   selectors have identical specificity, so SOURCE ORDER is what decides. Written in the
   phone block (which sits earlier in this file) it lost silently: the takeover came up as a
   353×715 rounded panel on a 375×812 phone instead of owning the screen. */
@media(max-width:759px){
  /* phone: the takeover OWNS the screen — edge to edge, no rounding, no seam */
  dialog.tk{width:100%;max-width:none;height:100%;max-height:none;border-radius:0;
    -webkit-overflow-scrolling:touch;}
  /* the backdrop is never meant to be SEEN here (the dialog is opaque and fills the
     viewport) — it matches the panel so a slow paint can't flash black behind the morph */
  dialog.tk::backdrop{background:var(--panel);}
}

/* ── THE SCRIPT, IN THE TAKEOVER (Juan, Jul 17 2026) ──────────────────────────────────────
   "instead of opening on a separate page… it'd be cool if everything was just in one place."
   Same dialog, different content — so it wears the project takeover's chrome verbatim
   (sticky header + the WORDED close). It is the same surface; only the body differs, and a
   different-looking header would say otherwise.
   Desktop gets it too: the script view is a READING view at any width, and the shelf has no
   inline place to put a 5-shot script (that is the whole reason it was a separate page). */
.tk .tkview{min-height:100%;background:var(--panel);}
.tk .tkview > .proj-sum{position:sticky;top:0;z-index:2;background:var(--panel);
  padding-top:calc(16px + env(safe-area-inset-top,0px));border-bottom:1px dashed var(--line);
  cursor:default;}
.tk .tkview > .proj-sum .proj-close{display:inline;}
/* the header's title is a real <h1> here — strip the UA margins so it sits like the project
   takeover's span-titled header rather than shoving the close button around */
.tk .tkview > .proj-sum h1.proj-t{margin:0;font-size:inherit;line-height:1.25;}
.tk .tkview-x{font:inherit;background:none;border:0;padding:0;cursor:pointer;color:var(--soft);
  display:inline-flex;align-items:center;gap:6px;min-height:24px;}
.tk .tkview .sview{padding:18px 18px calc(28px + env(safe-area-inset-bottom,0px));margin:0 auto;}
/* the script REPLACES the project it was opened from — the card is still mounted underneath
   (so closing the script returns to it rather than dumping the client back at the grid), it
   just must not be on screen at the same time. */
.tk.has-script > .proj{display:none;}

/* provenance as credits at the card's end */
/* the credits rule needs real air on BOTH sides (Juan, Jul 16): 18/12 read as the line
   crowding the posts above it and its own text below. A divider that hugs its content
   stops reading as a separator and starts reading as an underline. */
.proj-from-end{margin:30px 0 4px;padding-top:20px;border-top:1px dashed var(--line);}

/* media pills settle in on open instead of popping (canon: transform/opacity only) */
@media(prefers-reduced-motion:no-preference){
  /* pills stay unseen during the transition (else they ride the new snapshot, then
     re-animate = the double-appear Juan caught), then ease in once, together */
  @media(min-width:760px){
    .proj[open]:not(.pills-in) .ptag,.proj[open]:not(.pills-in) .restag,.proj[open]:not(.pills-in) .ptabs{opacity:0;}
  }
  .proj.pills-in .ptag,.proj.pills-in .restag,.proj.pills-in .ptabs{
    animation:pill-in .5s var(--ease-out) both;}
  @keyframes pill-in{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:none;}}
}

/* the closing row: yours-to-keep + before→after share a row EVERYWHERE (Juan: stacked = too much
   empty space). Groups HUG — content-sized, side by side with barely any air (Juan's call
   Jul 12 2026: "pretty much no gap between them"); 1fr halves left a dead zone mid-row.
   The b→a pair itself sits near-flush so the comparison reads as ONE composite. */
.fin-rows{display:flex;flex-wrap:wrap;gap:12px 14px;margin:14px 0 2px;align-items:flex-start;}
.fin-rows .mstrip{display:flex;gap:8px;margin:0 0 4px;}
.fin-rows .thumb{width:clamp(64px,11vw,112px);flex:0 0 auto;}
.fin-ba .mstrip{gap:3px;}

/* standalone anchor cards get the same air the pair-row enjoys (Juan: all cards match) */
.proj-body>.linkcard,.proj-body>.filechip{margin:12px 0;}

/* story SLIDES (grammar ruling Jul 12 2026: 4–7 slides, per-slide copy, sticker ladder) —
   the 9:16 sequence rides the carousel swipe; copy + sticker rows follow the active slide */
.post.story .slide{position:relative;}

/* closing-row header: the save-all icon hugs the row it acts on (proximity, never the card title) */
.fin-hdr{display:flex;align-items:center;gap:10px;margin:0 0 6px;}
.fin-hdr .pgroup-k{margin:0;}
.fin-save{width:30px;height:30px;}

/* milestone share = a quiet icon chip (words live in tooltip/aria — Juan's call Jul 12 2026) */
.mcard .share.icon-only{display:inline-grid;place-items:center;width:34px;height:34px;padding:0;border-radius:50%;}
.mcard.receipt{position:relative;}
.mcard.receipt .share.icon-only{position:absolute;right:10px;top:10px;border:1px solid rgba(42,36,23,.25);
  background:transparent;color:#2A2417;cursor:pointer;}
.mcard.receipt .share.icon-only:hover{background:rgba(42,36,23,.08);}

/* closing-row alignment (Juan's catch Jul 12 2026): the b→a label kept its default 16px
   top margin while the keep header sat flush — the groups read crooked, not alternating */
.fin-rows .pgroup-k{margin:0 0 6px;}
.fin-hdr{margin:0 0 6px;}
.fin-hdr .pgroup-k{margin:0;}
/* lightbox icon actions — centered under the copy */
.lb-btn.icon{display:inline-grid;place-items:center;width:38px;height:38px;padding:0;border:1px solid rgba(236,231,218,.4);border-radius:50%;}
.lb-btn.icon:hover{border-color:#ECE7DA;}
.lb-cap a#lb-save{display:none;} /* legacy selector guard — the save action moved into .lb-nav */

/* scroll v4: the transition overlay follows the page scroll (set per-frame from JS) */
::view-transition{transform:translateY(var(--vt-y,0px));}

/* post-card video at rest (r42): poster + .play overlay, no native controls — the
   control bar's top row sat under the ptag/restag pills. First press flips to
   native controls and the frame goes .playing: the pills fade out of the way. */
.pvid{position:relative;display:block;width:100%;height:100%;}
.pvid .play{cursor:pointer;}
.pvid .play[hidden]{display:none;}
.pvid .play:focus-visible{outline:3px solid var(--accent);outline-offset:-3px;}
@media(prefers-reduced-motion:no-preference){.ptag,.restag{transition:opacity .3s var(--ease-out);}}
/* animation:none also releases pill-in's fill-mode hold, or the fade could never win */
.pmedia.playing .ptag,.pmedia.playing .restag{opacity:0;animation:none;pointer-events:none;}

/* FINAL CUT — the client's own footage supersedes the AI reference we sent them to shoot
   against. One word, past tense (Polaris badge convention), and never colour alone. */
.finaltag{position:absolute;top:8px;right:8px;font-family:ui-monospace,Menlo,monospace;
  font-size:9px;letter-spacing:.09em;text-transform:uppercase;color:var(--soft);
  background:var(--sunk);border-radius:var(--r-pill);padding:3px 9px;pointer-events:none;}
/* THE SUPERSEDED REFERENCE — reachable, labeled, permanent, never the default view.
   ⚑ Was a <details> in .ptail until Jul 17 2026. .ptail is grid-row 2 of the subgrid, so
   expanding it grew the SHARED row: the post card went 540→880px and dragged the script
   card beside it up by the same 340px of dead space. Expand-below assumes a card owns its
   own height — row-shared heights are exactly what the subgrid alignment bought. The
   reference now rides the MEDIA box, which is aspect-ratio-pinned, so the row is
   layout-neutral by construction: nothing below the picture moves when it swaps. */
/* z-index is LOAD-BEARING, not decoration (caught in the browser Jul 17 2026 — computed
   style reported the ai label "visible: opacity 1" while the reference image physically
   COVERED it; only a screenshot showed it). .pref is absolutely positioned and appended
   after the pills, so without an explicit stack it paints over the §6b disclosure — the
   one label that may never be hidden. The reference sits above the hero, below the pills. */
.pref{opacity:0;pointer-events:none;z-index:1;} /* .pmedia > img already insets it to fill the frame */
.pmedia.showref .pref{opacity:1;pointer-events:auto;}
.pmedia .ptag,.pmedia .restag,.pmedia .finaltag,.pmedia .pref-note{z-index:2;}
/* purposeful, one-shot, opacity-only — the confirm micro-interaction the motion spec allows */
@media(prefers-reduced-motion:no-preference){.pref{transition:opacity .22s var(--ease-out);}}
/* the reference's provenance rides the frame, not the tail — a note below the picture would
   put the height back into the shared row, which is the whole bug. */
.pref-note{position:absolute;left:8px;right:8px;bottom:8px;margin:0;font-size:10.5px;
  line-height:1.35;color:#F5EFDD;background:rgba(10,16,69,.74);border-radius:8px;
  padding:5px 8px;opacity:0;pointer-events:none;}
.pmedia.showref .pref-note{opacity:1;}
/* the role slots, made literal: both named, the current one marked. A latching pair — never
   a press-and-hold (that gesture cannot be labeled without breaking the gesture-neutral copy
   rule, cannot be found by a twice-a-month client if unlabeled, and needs more motor control
   than a press). aria-pressed carries the state a hold gesture could not express. */
.roles{display:flex;gap:6px;flex-wrap:wrap;border-top:1px dashed var(--line);
  margin:0 14px;padding:8px 0 10px;}
.roles .role{font-family:ui-monospace,Menlo,monospace;font-size:9.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--soft);background:none;border:1px solid var(--line);
  border-radius:var(--r-pill);padding:5px 10px;min-height:24px;cursor:pointer;}
.roles .role[aria-pressed="true"]{color:var(--oncolor);background:var(--ink);
  border-color:var(--ink);}
@media(prefers-reduced-motion:no-preference){
  .roles .role{transition:color .18s var(--ease-out),background .18s var(--ease-out);}}

/* sparkline wears the delta's colour (Juan, Jul 18 2026): up=green, down=red, flat=gray */
.spark-wrap.up polyline,.sb-chart .scrub-wrap.up polyline{stroke:var(--ok);}
.spark-wrap.up .spark-fill,.sb-chart .scrub-wrap.up .spark-fill{fill:var(--ok);}
.spark-wrap.up .dot-el,.scrub-wrap.up .dot-el{background:var(--ok);}
.spark-wrap.down polyline,.sb-chart .scrub-wrap.down polyline{stroke:var(--err);}
.spark-wrap.down .spark-fill,.sb-chart .scrub-wrap.down .spark-fill{fill:var(--err);}
.spark-wrap.down .dot-el,.scrub-wrap.down .dot-el{background:var(--err);}
