/* masa capture — mirrors site tokens (site/assets/css/tokens.css) */
/* ⚑ FONT = NATIVE iOS SYSTEM STACK (Juan, Jul 17) — the capture thread is deliberately native (SF Pro on
   iPhone), like iMessage. The site's Outfit/Cal Sans were tried for cohesion then reverted here: this ONE
   surface stays native on purpose. Portal + kitchen keep Outfit/Cal Sans. */
:root{
  --font-body:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  /* ⚑ FLASK-DARK RESKIN (Jul 17 2026, stage 1) — the product now matches the marketing site's flask
     skin (site/assets/css/azul-tokens.css dark ground). Was the light NOCHE theme. Var NAMES kept so
     the ~1400-line stylesheet inherits without churn; accent = flask cyan #92DCE5 (single accent),
     actions keep dark-text-on-accent via the new --on-cta. Surface polish (bubbles/cards/matte/chips)
     follows surface-by-surface. */
  --bg:var(--masa-bg,#191919);--surface:var(--masa-surface,#232323);--sunk:var(--masa-sunk,#2A2A2A);--line:var(--masa-line,rgba(250,250,250,.14));--ink:var(--masa-ink,#FAFAFA);
  --ink-soft:var(--masa-soft,#A1A1AA);--terra:var(--masa-accent,#92DCE5);--terra-h:var(--masa-accent-h,#6EC1CE);--cta:var(--masa-accent,#92DCE5);--cta-h:var(--masa-accent-h,#6EC1CE);--amber:var(--masa-soft,#A1A1AA);--on-cta:var(--masa-on-accent,#191919);
  --teal:var(--masa-accent,#92DCE5);--linen:var(--masa-surface,#232323);--ok:var(--masa-ok,#7FD1A7);--warn:var(--masa-warn,#E8BE6B);--err:var(--masa-err,#EC8E8E);--r-md:12px;--r-lg:16px;--gap:16px;--lh-body:1.6;
  /* r105 — --lh-body is PROSE leading. It was silently inheriting into single-line
     UI CONTROLS (pin rows, status chips, the "N more" toggle), where it added ~6px
     of dead box per control and made the sticky top read thick. Controls use
     --lh-ui; prose keeps --lh-body. --pin-h = the one height every interactive row
     in the sticky top shares. */
  --lh-ui:1.2;--pin-h:36px;
  /* ONE shadow scale (ruling Jul 9 2026): always masa-ink-tinted (the warm
     dark IS the flair) — never orange (glow ≠ elevation), never pure black */
  --shadow-media:0 4px 14px rgba(0,0,0,.35);
  --shadow-float:0 12px 34px rgba(0,0,0,.45);
  --shadow-deep:0 20px 60px rgba(0,0,0,.55);
}
*{box-sizing:border-box}
body{
  margin:0;background:var(--bg);color:var(--ink);line-height:var(--lh-body);
  font-family:var(--font-body);
}
/* kills iOS double-tap-to-zoom on controls (the "page zooms into white space"
   bug) WITHOUT disabling pinch-zoom — the a11y-safe form of the fix */
button,a,.thumb-btn,.chip-choice{touch-action:manipulation}
/* r94 (Juan) — WHITE MATTE behind sent-media thumbs: a transparent PNG (her
   hand-drawn logo/doodles) would otherwise go see-through on the dark thread
   and bleed through stacked tiles. An opaque matte is the safe fallback
   (web-checked); harmless for photos, which cover it. */
.t-thumbs .thumb-btn,.t-thumbs .thumb-btn img{background:#fff}
/* APP SHELL (chat recipe, 2026 baseline): 100dvh column — thin top bar,
   ONLY the middle scrolls, composer dock pinned above keyboard/home bar
   (viewport meta interactive-widget=resizes-content makes this hold on iOS) */
html,body{height:100%}
.app{
  max-width:520px;margin:0 auto;padding:0 var(--gap);
  /* pinned to the VISUAL viewport (JS sets --vvh): the composer rides right
     above the keyboard on every browser, keyboard open or closed */
  height:var(--vvh,100dvh);display:flex;flex-direction:column;
}
.brand{display:flex;align-items:center;gap:10px;padding:10px 0;flex:none}
/* CONTACT HEADER (Apple idiom, COMPACT — Jul 9 2026: the header is one slim
   row; the chat gets the space). GRID keeps the identity cluster centered
   while the demo badge owns the right cell — nothing can overlap. */
.brand.contact{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:6px;padding:7px 0;min-height:40px}
.hdr-core{grid-column:2;display:flex;align-items:center;gap:8px;min-width:0}
.brand.contact h1{flex:none}
.brand.contact .brand-mark{margin:0;font-size:.88rem;padding:1px 10px}
/* .brand.contact scope OUTRANKS the base .masa-face sizing (the same-
   specificity source-order bug — see the thread-hero note above) */
.brand.contact .hdr-face{width:26px;height:24px;gap:4px}
.brand.contact .hdr-face .m-eye{width:4.5px;height:4.5px}
/* r106 — .hdr-sub is GONE: the header greeting moved into the thread's first
   bubble, so the slot it styled no longer exists. */
/* r62 — 'your space →' secondary nav: right column, quiet, matches the card's
   'full package →' link (both route to the portal). Distinct from the centered
   brand identity — a nav affordance, not the brand. */
.hdr-space{grid-column:3;justify-self:end;font-size:.74rem;font-weight:700;color:var(--ink);
  text-decoration:none;white-space:nowrap;padding-right:2px}
.hdr-space[hidden]{display:none}
.hdr-space:hover{text-decoration:underline;text-underline-offset:3px}
.hdr-space:focus-visible{outline:3px solid var(--terra);outline-offset:2px;border-radius:4px}
.brand.contact .demo-chip{grid-column:3;justify-self:end;max-width:100%;margin:0}
.brand-mark{
  display:inline-block;font-weight:800;letter-spacing:.02em;color:var(--linen);
  background:var(--ink);border-radius:var(--r-md);padding:2px 12px;font-size:1rem;
}
h1{font-size:clamp(.85rem,3.7vw,1.02rem);margin:0;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.scroll-region{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;display:flex;flex-direction:column;gap:var(--gap);padding:6px 2px;overscroll-behavior:contain}
/* r67 — SWIPE/PEEK EDGE FADE (Juan: elements hard-clip off the screen width on the
   time-peek + reply swipe). A horizontal mask softly fades content at the edges —
   applied ONLY during the gesture (JS toggles .edge-fade) so normal messages are
   never masked. Web-backed over a hard cutoff (CSS mask-image gradient). */
.scroll-region.edge-fade{
  -webkit-mask-image:linear-gradient(to right,transparent 0,#000 22px,#000 calc(100% - 22px),transparent 100%);
  mask-image:linear-gradient(to right,transparent 0,#000 22px,#000 calc(100% - 22px),transparent 100%);
}
.thread-wrap{margin-top:auto} /* short threads hug the composer, like every messenger */
.dock{flex:none;display:grid;gap:6px;padding:6px 0 max(10px, env(safe-area-inset-bottom));background:var(--bg)}
/* (decision row removed by ruling Jul 9 2026 — the waiting cards + shelf
   footer carry the buttons; pin rows give awareness) */
/* pinned open projects (Telegram multi-pin precedent): one thin row each.
   r51 — #pin-bar is a fixed-COLLAPSED-height flex child (JS sets its px height);
   the actual rows live in .pin-float, positioned OUT of flow so an open dropdown
   grows the float DOWN over the thread (siblings reflow, thread stays put — #6). */
.pin-stack{flex:none;position:relative;margin-bottom:6px;min-width:0}
.pin-stack[hidden]{display:none}
/* the floating layer: absolute over the top of the thread, its own stacking
   context (below drop-zone 50 / msg-menu 60 / dialogs). left/right:0 → the rows
   span the full pin-bar width (already inset from both screen edges by .app's
   var(--gap) side padding — the chips never touch/clip the viewport edge).
   r55 — FLOATING PILLS (Juan's redesign): the r52/r54 wrapper (opaque --bg +
   bottom hairline border) is GONE — it read as one container bar with square
   top corners, not distinct chips. The float is now a PURE positioning layer,
   no bg/border/radius of its own; each .pin-row carries its own opaque --linen
   bg (all four corners rounded) and each open .pin-drop its own opaque --bg, so
   occlusion still holds: an OPEN menu is fully solid, and only the small 4px
   grid gaps BETWEEN collapsed pills show the thread — the intended chip look. */
/* r69 (Juan) — SOLID panel, not floating chips: the thread was showing through the
   gaps + the chevron row. A solid --bg fill + a bottom radius + soft shadow makes the
   sticky top read as one panel hanging from the header (esp. when the "N more" collapse
   is open over the thread). */
.pin-float{position:absolute;top:0;z-index:30;display:grid;gap:4px;
  /* r70 (Juan) — the solid panel spans the FULL page width: extend past the .app's
     side padding (--gap) with negative left/right, then pad the ROWS back in so they
     keep their place while the bg + shadow reach the page edges */
  left:calc(-1 * var(--gap));right:calc(-1 * var(--gap));
  /* r92 (Juan) — vertical padding so the rows breathe inside the panel (top +
     bottom), not flush to the header above / the rounded bottom edge.
     r103 (Juan) — SYMMETRIC: was 8px top / 10px bottom, so the first row sat 2px
     closer to the edge than the last one did, in every view (collapsed, expanded,
     and dropdown-open — all three measured). One value, one optical inset. */
  background:var(--bg);padding:8px var(--gap);border-radius:0 0 var(--r-md) var(--r-md)}
/* r105 (Juan: "thinner... and uniform") — ONE height for every interactive row in
   the panel. They disagreed because they were sized by three different accidents:
   a .pin-row WITH a status chip = 40.4px (the chip drove it), a text-only row =
   35px, and a .pin-need = 44px (an explicit min-height). The sub-items were
   TALLER than the parent rows they hang under — inverted hierarchy.
   --pin-h is the floor for all three, set by the tallest REQUIRED content (the
   status chip, once its own line-height leak is fixed). Reach comes from the
   ::after pad, not from inflating the box — the .pin-row idiom, now applied to
   .pin-need too, so one strategy governs the whole panel instead of two. */
.pin-row{
  position:relative; /* r44: anchors the invisible 44px hit pad below */
  min-height:var(--pin-h);
  display:flex;align-items:center;gap:8px;justify-content:space-between;
  width:100%;box-sizing:border-box; /* r51: fill the slot — a <button> shrinks to content otherwise (the r50 narrowing Juan caught) */
  min-width:0;max-width:100%; /* the bar itself can never outgrow the viewport (Juan's screenshot Jul 12 2026 — a long title pushed the row past 100vw) */
  font:inherit;font-size:.82rem;line-height:var(--lh-ui);font-weight:700;color:var(--ink);text-align:left;
  background:var(--linen);border:1px solid var(--line);border-left:3px solid var(--terra);
  border-radius:var(--r-md);padding:6px 12px;cursor:pointer;
}
/* r54 unseen-indicator: terra left-accent = unseen deliverable; once the client
   routes to its card (focusItem marks it seen) the accent goes NEUTRAL, same as
   a plain edge. Both themes (var(--line)/var(--terra) both flip in the dark set). */
.pin-row.seen{border-left-color:var(--line)}
/* 44px tap height (r44): the thin row reads ~40px — the ::after pad makes up
   the rest (the audit idiom). Stacked rows sit 4px apart, so mid-gap the LATER
   row's pad wins the tap — a near-miss lands on the adjacent pin row, never a
   stranger control; the single-row case (the common one) gets the full pad. */
.pin-row::after{content:"";position:absolute;inset:-5px 0}
.pin-row span:first-child{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} /* min-width:0 — flex refuses to shrink below content without it (mobile overflow, Juan's catch Jul 12 2026) */
.pin-row .chip-status{margin:0;flex:none}
/* pressed = the background sinks one step (r44) — a shift only, no transform,
   so the same feedback serves reduced motion untouched (the handoff-chip rule) */
.pin-row:active{background:var(--sunk)}
.pin-row:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);padding:var(--gap)}
.card-primary{border:2px solid var(--line);background:var(--linen)}
.card-primary[hidden]{display:none}
.card-primary .btn-send{display:block;text-align:center;text-decoration:none}
h2{font-size:1.05rem;margin:0 0 .35em}
.h-ic{vertical-align:-3px;color:var(--ink-soft);fill:currentColor;margin-right:2px}
.hint{color:var(--ink-soft);font-size:.9rem;margin:0 0 12px}

/* ⚑ PRIMARY BUTTON = the site's flask LIGHT-GRADIENT (Jul 17 — cohesion). Cyan drops back to an accent
   (dots/links/mic), matching how the home page uses it. */
.btn{
  font:inherit;font-weight:600;border:.5px solid transparent;border-radius:var(--r-md);
  padding:12px 18px;cursor:pointer;color:#191919;
  background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));
  box-shadow:inset 0 1px #ffffffd9,inset 0 -1px #09090b0f,0 0 0 .5px #09090b8c,0 1px 2px #00000059,0 4px 12px #00000038;
}
.btn:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.btn[disabled]{opacity:.45;cursor:not-allowed}
.btn-ghost{background:transparent;color:var(--ink);border:1px solid var(--line)}

/* masa's motion signature = the lab's spring-overshoot curve (mascot.html) —
   used on character moments (morphs); plain ease stays on simple fades */
:root{--spring:cubic-bezier(.34,1.38,.5,1);--accent:var(--terra)}
/* NIGHT (Jul 12 2026): the thread follows the phone — prefers-color-scheme flips the
   tokens (portal/kitchen night values); data-theme attr can force either for testing. */
@media(prefers-color-scheme:dark){:root:not([data-theme="light"]){
  --bg:var(--masa-bg,#191919);--surface:var(--masa-surface,#232323);--sunk:var(--masa-sunk,#2A2A2A);--line:var(--masa-line,rgba(250,250,250,.14));--ink:var(--masa-ink,#FAFAFA);
  --ink-soft:var(--masa-soft,#A1A1AA);--terra:var(--masa-accent,#92DCE5);--terra-h:var(--masa-accent-h,#6EC1CE);--teal:var(--masa-accent,#92DCE5);--linen:var(--masa-surface,#232323);--ok:var(--masa-ok,#7FD1A7);--warn:var(--masa-warn,#E8BE6B);--err:var(--masa-err,#EC8E8E);
  --shadow-media:0 4px 14px rgba(0,0,0,.4);--shadow-float:0 12px 34px rgba(0,0,0,.5);
  --shadow-deep:0 20px 60px rgba(0,0,0,.65);
}}
:root[data-theme="dark"]{
  --bg:var(--masa-bg,#191919);--surface:var(--masa-surface,#232323);--sunk:var(--masa-sunk,#2A2A2A);--line:var(--masa-line,rgba(250,250,250,.14));--ink:var(--masa-ink,#FAFAFA);
  --ink-soft:var(--masa-soft,#A1A1AA);--terra:var(--masa-accent,#92DCE5);--terra-h:var(--masa-accent-h,#6EC1CE);--teal:var(--masa-accent,#92DCE5);--linen:var(--masa-surface,#232323);--ok:var(--masa-ok,#7FD1A7);--warn:var(--masa-warn,#E8BE6B);--err:var(--masa-err,#EC8E8E);
  --shadow-media:0 4px 14px rgba(0,0,0,.4);--shadow-float:0 12px 34px rgba(0,0,0,.5);
  --shadow-deep:0 20px 60px rgba(0,0,0,.65);
}


/* client greeting — their name on their link (accent colors the chip border +
   waveform bars; masa keeps the chrome, mascot, and CTAs) */
.client-chip{
  display:inline-block;margin:0;padding:3px 12px;font-weight:700;font-size:.8rem;flex:none;
  color:var(--ink);background:var(--linen);border:2px solid var(--line);border-radius:999px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:46%;
}
.client-chip[hidden]{display:none}

.rec-row{display:flex;align-items:center;gap:10px}
.rec-row[hidden]{display:none} /* mic yields the slot to SEND when content exists */
/* the mic: a compact circle in the primary slot (composer-diet ruling) */
.btn-rec{
  background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));display:inline-flex;align-items:center;justify-content:center;
  flex:none;width:38px;height:38px;padding:0;border-radius:50%;
  transition:background .2s ease,transform .18s var(--spring);
}
.btn-rec:hover{filter:brightness(1.05)}
.btn-rec:active{transform:scale(.92)}
.rec-mic{color:var(--on-cta);fill:var(--on-cta);flex:none} /* dark icon on the accent (flask reskin) */
.rec-stop{display:none;fill:var(--on-cta);flex:none}
/* recording: a plain STOP control (mascot-as-host ruling — controls stopped
   wearing the mascot's face; recording is a composer TAKEOVER instead) */
.btn-rec[aria-pressed="true"]{
  background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));max-width:52px;width:52px;height:52px;padding:0;
  justify-content:center;border-radius:50%;
}
.btn-rec[aria-pressed="true"]:hover{filter:brightness(1.05)}
.btn-rec[aria-pressed="true"] .rec-mic,
.btn-rec[aria-pressed="true"] #rec-label{display:none}
.btn-rec[aria-pressed="true"] .rec-stop{display:block;animation:pulse 1.6s ease-in-out infinite}
@keyframes pulse{50%{opacity:.45}}
.rec-time{font-variant-numeric:tabular-nums;color:var(--ink-soft);font-weight:700}
/* the recorder's timer earns its place only while recording — idle "0:00" is noise */
#rec-time{display:none}
.rec-row.recording #rec-time{display:inline}
/* recording layout (Apple's own, per Juan's reference shots Jul 9 2026):
   wave fills · time · STOP at the far right (the mic/send thumb slot) */
.rec-row.recording .rec-wave{order:1}
.rec-row.recording #rec-time{order:2}
.rec-row.recording #rec-btn{order:3}
.rec-wave{
  flex:1;min-width:0;height:38px;background:var(--sunk);border-radius:var(--r-md);
  max-width:0;opacity:0;transition:max-width .3s ease,opacity .25s ease;
}
.rec-row.recording .rec-wave{max-width:100%;opacity:1}
.rec-row.has-take{display:none} /* a take exists — re-record/discard live by the player */
/* quiet icon actions (take preview + friends) */
.icon-ghost{
  flex:none;width:38px;height:38px;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--line);border-radius:50%;background:transparent;color:var(--ink-soft);cursor:pointer;
}
.icon-ghost svg{fill:none;stroke:currentColor}
.icon-ghost:hover{border-color:var(--ink-soft);color:var(--ink)}
.icon-ghost:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.email-hint{text-align:center;margin:0}
.email-hint a,#gate-card a{color:var(--ink);font-weight:700}
.email-hint[hidden]{display:none}

/* THE THREAD (capture v2) — messaging idiom: client items right, masa items
   left with the mascot face; soft cards, no strokes on artwork */
.thread-wrap[hidden]{display:none}
.thread{list-style:none;margin:0;padding:0;display:grid;gap:10px}
/* FLUID ENTRANCE (motion ruling Jul 8 2026 — spring physics build trust):
   only the freshly-arrived message springs up into place; ~360ms, the masa
   --spring curve. Everything already in the thread stays settled. */
@keyframes msg-in{from{opacity:0;transform:translateY(10px) scale(.985)}to{opacity:1;transform:none}}
/* the handoff round divider is a thread citizen too (r43) — it arrives with
   the same entrance as the receipt bubble beside it */
.thread-item.just-in,.handoff-div.just-in,.handoff-row.just-in{animation:msg-in .36s var(--spring) both;transform-origin:bottom}
@media (prefers-reduced-motion:reduce){
  .thread-item.just-in,.handoff-div.just-in,.handoff-row.just-in{animation:none}
}
.thread-item{position:relative;max-width:86%;padding:10px 14px;border-radius:var(--r-lg);font-size:.92rem}
/* TIME PEEK (Apple): times wait off-canvas right; dragging the transcript
   left fades them in (--peek set by JS). Desktop: hover a message instead. */
.t-time{
  position:absolute;top:50%;right:-58px;transform:translateY(-50%);
  font-size:.7rem;color:var(--ink-soft);white-space:nowrap;pointer-events:none;
  opacity:var(--peek,0);
}
@media (hover:hover){.thread-item:hover .t-time{opacity:1}}
/* the one quiet receipt, under the LAST sent message only (Apple) */
.t-receipt{font-size:.72rem;color:var(--ink-soft);text-align:right;margin-top:4px}
.bare-media .t-receipt{padding-right:2px}
.thread-item p{margin:0}
/* TEXT BUBBLES wrap + preserve newlines (r46, Juan's screenshots): a long
   unbroken word wraps INSIDE the bubble, and typed line breaks survive to
   render. Mirrors the composer textarea rule (capture.css ~532): pre-wrap keeps
   the \n's, overflow-wrap:anywhere + word-break:break-word snap the long word.
   Scoped to the TEXT paragraphs only — the masa bubble's own <p> and the sent
   note's .t-note — so package cards (.thread-item.package) and media/voice
   tiles (.bare-media) keep their own layout, never inheriting stray whitespace. */
.thread-item.masa p,.t-note{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}
.thread-item.mine{justify-self:end;background:var(--linen);border:1px solid var(--line);border-bottom-right-radius:6px;text-align:right}
.thread-item.masa{justify-self:start;background:var(--surface);border:1px solid var(--line);border-bottom-left-radius:6px;display:flex;gap:10px;align-items:flex-start;touch-action:pan-y}
/* APPLE REPLY (Jul 9 2026): replying SPOTLIGHTS the original — the room dims,
   the target stays lit; no original-message bar over the composer. Dimmed
   surfaces are NOT interactive — a tap outside can only dismiss, never
   accidentally hit a chip/button (Juan's lockout rule). */
body.replying .thread-item:not(.reply-target),body.replying .thread-hero,body.replying .day-div{opacity:.22;transition:opacity .22s ease;pointer-events:none}
body.replying #pin-bar{pointer-events:none}
body.replying .thread-item.reply-target{opacity:1;scroll-margin:88px} /* never tucked under chrome */
/* links present as links — tappable, domain-first (og-rich cards = future unfurl) */
.t-link{color:var(--ink);font-weight:700;text-decoration:none;border-bottom:1.5px solid rgba(250,250,250,.32);word-break:break-all}
.t-link:hover{border-bottom-color:currentColor}
.thread-item.masa > div{min-width:0;flex:1} /* body can shrink so the chip row scrolls INSIDE the bubble, not past it */
.thread-item.masa .t-face{width:34px;height:32px;gap:5px;margin-top:2px}
.thread-item.masa .t-face .m-eye{width:5.6px;height:5.6px}
.t-what{font-weight:700}
/* EMPTY STATE — the mascot hosts (brand-center pattern): big face, greeting under */
.thread-hero{list-style:none;display:grid;justify-items:center;gap:14px;padding:26px 10px;text-align:center}
.thread-hero p{margin:0;max-width:340px;color:var(--ink-soft)}
/* scoped: must OUTRANK the base .masa-face sizing below — same-specificity
   selectors lose on source order (that bug shipped once, Jul 8 2026) */
.thread-hero .hero-face{width:92px;height:88px;gap:14px}
.thread-hero .hero-face .m-eye{width:15px;height:15px}
/* APPLE MEDIA RULING (Jul 9 2026, supersedes media-in-bubble): media is NOT
   wrapped by bubble chrome — it stands alone (rounded, soft shadow), and the
   words get their own bubble beneath. Matches how iMessage floats photos. */
.thread-item.mine.bare-media{background:transparent;border:none;padding:0;overflow:visible;display:grid;gap:6px;justify-items:end}
.bare-media .t-thumbs{width:100%}
.bare-media .t-thumbs[data-n="1"] .thumb-btn{border-radius:16px;box-shadow:var(--shadow-media)}
.bare-media .m-bubble{
  background:var(--linen);border:1px solid var(--line);border-radius:var(--r-lg);
  border-bottom-right-radius:6px;padding:10px 14px;max-width:100%;
}
.bare-media > .t-meta{padding:0 2px}
/* video playing right in the thread (play badge tapped) */
.inline-vid{width:100%;max-height:360px;border-radius:16px;display:block;background:#000}
/* filenames = version handles for media people — they live in the long-press
   menu (.mm-file), never on the bubble (ruled Jul 9 2026, replacing the
   beside-media label that looked off) */
.t-thumbs{position:relative}
/* the voice note rides IN a bubble (Apple: the player lives inside the
   message bubble — supersedes the fully-bare pass, which fit the old glyph
   but left the real player unanchored) */
.voice-pill{
  display:flex;align-items:center;gap:10px;justify-self:end;max-width:100%;
  background:var(--linen);border:1px solid var(--line);border-radius:var(--r-lg);
  border-bottom-right-radius:6px;padding:9px 12px;
}
.vmsg-play{
  flex:none;width:34px;height:34px;padding:0;border:none;border-radius:50%;
  background:var(--ink);color:var(--linen);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;
}
.vmsg-play svg{fill:currentColor}
.vmsg-play:hover{filter:brightness(1.05)}
.vmsg-play:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.vmsg-wave{width:168px;height:34px;touch-action:none;cursor:pointer;border-radius:8px}
.vmsg-wave:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
/* r97 (Juan) — voice-note TRANSCRIPT under the player (Apple idiom), with a
   show more / less toggle for the long ones; wraps below the controls row. */
.voice-pill{flex-wrap:wrap;max-width:min(90vw,360px)}
.vmsg-transcript{flex-basis:100%;margin:7px 2px 0;font-size:.9rem;line-height:1.42;color:var(--ink);white-space:pre-wrap;overflow-wrap:anywhere}
.vmsg-transcript.clamped{display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.vmsg-more{flex-basis:100%;align-self:flex-start;margin:3px 2px 0;background:none;border:0;padding:2px 0;color:var(--ink);font:inherit;font-size:.82rem;font-weight:700;cursor:pointer;text-align:left}
/* masa voice note floats bare (no bubble/face), pill left-aligned */
.thread-item.masa.bare-voice{background:transparent;border:none;padding:0;display:block}
.thread-item.masa.bare-voice .t-face{display:none}
.thread-item.masa.bare-voice .voice-pill{justify-self:start;border-bottom-right-radius:var(--r-lg);border-bottom-left-radius:6px}
.vmsg-time{font-variant-numeric:tabular-nums;font-size:.78rem;font-weight:700;color:var(--ink-soft)}
/* THE 4+ STACK (Apple): top card full, edges peek behind; swipe shuffles the
   deck; the label splays it into the cascade */
.stack-label{
  justify-self:end;background:none;border:none;cursor:pointer;font:inherit;
  font-size:.72rem;font-weight:700;color:var(--ink-soft);padding:2px 4px;
}
.stack-label:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
/* Apple stack proportions: a portrait card (~3:4) at ~2/3 width — the iMessage deck shape.
   r78 (Juan) — back to the ORIGINAL 3:4 (r76's 4:3 changed how the photos cropped). The
   in-place FLIP-splay that forced matching aspects is GONE: tapping the deck now opens the
   fullscreen swipe gallery (Apple's own idiom) instead of splaying, so there's no aspect
   conflict + no warp, and the deck keeps its natural shape. Swipe still shuffles the deck. */
.t-thumbs[data-n="stack"]:not(.splayed){aspect-ratio:3/4;width:68%;justify-self:end;margin-top:6px}
.t-thumbs[data-n="stack"]:not(.splayed) .thumb-btn{position:absolute;inset:0;border-radius:14px;box-shadow:var(--shadow-media);transition:transform .26s cubic-bezier(.22,.61,.36,1)}
/* during a splay/collapse FLIP the WAAPI owns the transform — this suppresses
   the .26s CSS transition (kept for the shuffle reorder) so the two never fight
   on the same property (the collapse jump Juan flagged, r59) */
.t-thumbs[data-n="stack"].flipping .thumb-btn{transition:none}
.t-thumbs[data-n="stack"] .thumb-btn img{width:100%;height:100%;aspect-ratio:auto;object-fit:cover} /* r79 — BOTH states fill (was :not(.splayed)); splayed now same 3:4 so no aspect mismatch */
.t-thumbs[data-n="stack"]:not(.splayed) .thumb-btn:nth-child(1){z-index:5}
.t-thumbs[data-n="stack"]:not(.splayed) .thumb-btn:nth-child(2){z-index:4;transform:rotate(2deg) translate(7px,-7px)}
.t-thumbs[data-n="stack"]:not(.splayed) .thumb-btn:nth-child(3){z-index:3;transform:rotate(-1.6deg) translate(-6px,-11px)}
.t-thumbs[data-n="stack"]:not(.splayed) .thumb-btn:nth-child(n+4){z-index:2;transform:rotate(.9deg) translate(3px,-14px)}
/* splayed: the deck spreads into the offset cascade, all visible */
.t-thumbs[data-n="stack"].splayed .thumb-btn{
  width:68%;aspect-ratio:3/4;border-radius:14px;box-shadow:var(--shadow-media);overflow:hidden; /* r79 — SAME size+aspect as the deck → pure-translate FLIP, no warp */
}
.t-thumbs[data-n="stack"].splayed .thumb-btn:nth-child(odd){align-self:flex-start}
.t-thumbs[data-n="stack"].splayed .thumb-btn:nth-child(even){align-self:flex-end}
.t-thumbs[data-n="stack"].splayed .thumb-btn+.thumb-btn{margin-top:-22%} /* r74: tighter overlap → shorter splayed column → less card travel + smaller thread-shove (calmer) */
/* PACKAGE PREVIEW CARD (chat link-preview idiom): the deliverable's real
   media + headline; one tap opens the shelf */
.pkg-card{
  display:block;width:100%;margin-top:10px;padding:0;text-align:left;cursor:pointer;
  color:var(--ink);font:inherit; /* buttons DON'T inherit color — the headline sat UA-black on navy (Juan's catch Jul 12 2026) */
  background:var(--linen);border:1px solid var(--line);border-radius:14px;overflow:hidden;
}
/* the audit trail breathes below the preview (Juan: was too tight) */
.pkg-card + .t-meta{margin-top:14px}
/* live status cue: the waiting state pulses quietly (static under reduced motion) */
.chip-status.live{display:inline-flex;align-items:center;gap:6px}
.chip-status.live::before{
  content:"";width:7px;height:7px;border-radius:50%;background:var(--terra); /* cyan = the "needs your ok" attention signal (cyan pass) */
  animation:live-dot 1.8s ease-in-out infinite;
}
@keyframes live-dot{0%,100%{opacity:.35}50%{opacity:1}}
@media (prefers-reduced-motion:reduce){.chip-status.live::before{animation:none}}
/* done/approved = a static GREEN dot (status-colour pass: cyan=needs you, green=done) */
.chip-status.done{display:inline-flex;align-items:center;gap:6px;color:var(--ok)}
.chip-status.done::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--ok)}
.pkg-card:hover{border-color:var(--ink-soft)}
.pkg-card:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.pkg-card-media{width:100%;height:130px;object-fit:cover;display:block;pointer-events:none}
.pkg-card-body{display:grid;gap:2px;padding:9px 12px;font-size:.88rem}
.pkg-card-body span{font-size:.74rem;color:var(--ink-soft)}
/* composer link hint: a URL in the field is acknowledged before sending */
.link-hint{margin:0;padding:4px 10px;font-size:.78rem;font-weight:700;color:var(--ink-soft)}
.link-hint[hidden]{display:none}
/* media presentation (Apple family, web-verified Jul 9 2026):
   1 item = natural aspect · 2–3 = the iMessage OFFSET COLLAGE (staggered,
   overlapping cards — never a flat grid) · 4+ = stack (future, cap is 3) */
.t-thumbs{display:flex;flex-direction:column;margin:0}
.t-thumbs[data-n="2"] .thumb-btn,.t-thumbs[data-n="3"] .thumb-btn{
  width:84%;border-radius:14px;box-shadow:var(--shadow-media);
  overflow:hidden; /* no borders on media (Juan, Jul 9 2026) — shadow does the lift */
}
.t-thumbs[data-n="2"] .thumb-btn:nth-child(odd),.t-thumbs[data-n="3"] .thumb-btn:nth-child(odd){align-self:flex-start}
.t-thumbs[data-n="2"] .thumb-btn:nth-child(even),.t-thumbs[data-n="3"] .thumb-btn:nth-child(even){align-self:flex-end}
.t-thumbs[data-n="2"] .thumb-btn+.thumb-btn,.t-thumbs[data-n="3"] .thumb-btn+.thumb-btn{margin-top:-13%}
.thumb-btn{position:relative;padding:0;border:none;background:none;cursor:pointer;overflow:hidden;border-radius:10px}
/* NO harsh focus box on media (Juan disliked the blue square) — a soft ring */
.thumb-btn:focus-visible{outline:none;box-shadow:0 0 0 3px var(--cta)}
.t-thumbs img{width:100%;height:100%;aspect-ratio:4/3;object-fit:cover;display:block}
/* single media = its own proportions (messaging norm), gently capped */
.t-thumbs[data-n="1"] img{aspect-ratio:auto;height:auto;max-height:340px;object-fit:cover}
/* video thumbs say so: a quiet play badge (media always previews + labels itself) */
.thumb-play{
  position:absolute;top:50%;left:50%;width:42px;height:42px;transform:translate(-50%,-50%);
  border-radius:50%;background:rgba(10,16,69,.55);cursor:pointer;
  /* media chrome is THEME-INDEPENDENT (rule Jul 12 2026): light glyph + dark scrim in both
     themes — the old var(--linen) glyph went dark-on-dark at night (Juan's catch) */
}
.thumb-play:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.thumb-play::before{
  content:"";position:absolute;top:50%;left:50%;transform:translate(-38%,-50%);
  width:0;height:0;border-left:14px solid #fff; /* play ▶ over media — white is standard (was off-palette cream) */
  border-top:9px solid transparent;border-bottom:9px solid transparent;
}
/* voice + photo in one bubble: the voice note sits in its own soft tray under
   the photo, clearly separated (Juan: too cramped otherwise) */
.mine .t-thumbs + .t-voice{margin-top:8px}
/* lightbox — Apple Photos idiom: the image scales up, the room fades away */
.lightbox{border:none;padding:0;background:transparent;max-width:94vw;max-height:90vh;overflow:visible}
.lightbox::backdrop{background:rgba(34,52,46,0);transition:background .28s ease}
.lightbox[open]::backdrop{background:rgba(34,52,46,.78)}
.lightbox img,.lightbox video{max-width:94vw;max-height:90vh;display:block;border-radius:14px;box-shadow:var(--shadow-deep)}
.lightbox img[hidden],.lightbox video[hidden]{display:none}
.lightbox[open]:not(.vt) img,.lightbox[open]:not(.vt) video{animation:zoom-in .34s var(--spring) both}
@keyframes zoom-in{from{opacity:0;transform:scale(.82)}to{opacity:1;transform:none}}
/* the Apple morph (View Transitions): the tapped thumb grows into fullscreen */
::view-transition-group(lb-photo){animation-duration:.34s;animation-timing-function:var(--spring)}
/* gallery chrome: NO counter, no arrows on touch — swipe IS the interface
   (Apple Photos). Arrows exist only for mouse users, who can't swipe. */
.lb-nav{
  position:fixed;top:50%;transform:translateY(-50%);width:40px;height:40px;border-radius:50%;
  border:none;background:rgba(34,52,46,.45);color:var(--linen);font-size:1.5rem;line-height:1;cursor:pointer;
}
.lb-prev{left:10px}.lb-next{right:10px}
.lb-nav:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.lightbox.single .lb-nav{display:none} /* single item = no chrome */
@media (hover:none){.lb-nav{display:none}}
/* bubble actions — tiny icon-only buttons at the tail (Claude idiom;
   the glyph is the label, aria carries the meaning) */
.b-actions{display:flex;gap:2px;margin-top:6px}
.icon-act{
  width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;
  border:none;background:none;color:var(--ink-soft);cursor:pointer;border-radius:8px;
  transition:color .15s ease,background .15s ease,transform .18s var(--spring);
}
.icon-act svg{fill:none;stroke:currentColor}
.icon-act:hover{color:var(--ink);background:var(--linen)}
.icon-act:active{transform:scale(.86)}
.icon-act.done{color:var(--ok)}
.icon-act:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
/* quick-reply chips — smaller, ONE line, never wrap (Juan): scroll sideways
   if they overflow instead of stacking. Personalized copy stays (warm > y/n). */
.choice-row{
  display:flex;gap:6px;margin-top:8px;max-width:100%;flex-wrap:nowrap;overflow-x:auto;
  /* horizontal-ONLY: overflow-x:auto silently computes overflow-y to auto too (CSS spec),
     which let a diagonal drag scroll the row vertically; pin y hidden + touch-action:pan-x
     so a vertical/diagonal gesture pans the THREAD, not this chip row (Juan's catch Jul 13) */
  overflow-y:hidden;touch-action:pan-x;
  scrollbar-width:none;-webkit-overflow-scrolling:touch;padding-right:14px;
  /* a soft right-edge fade says "more to the side" instead of a hard cut-off */
  -webkit-mask-image:linear-gradient(90deg,#000 88%,transparent);
  mask-image:linear-gradient(90deg,#000 88%,transparent);
}
.choice-row::-webkit-scrollbar{display:none}
/* JS toggles these: fade only the edge content has actually scrolled past */
.choice-row.no-fade-r{-webkit-mask-image:none;mask-image:none}
.choice-row.fade-l{
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
}
.choice-row.fade-l.no-fade-r{
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 12%);
  mask-image:linear-gradient(90deg,transparent,#000 12%);
}
.chip-choice{
  flex:none;white-space:nowrap;
  font:inherit;font-size:.78rem;font-weight:700;color:var(--ink);cursor:pointer;
  background:var(--linen);border:1.5px solid var(--line);border-radius:999px;padding:5px 12px;
}
.chip-choice:hover{background:var(--sunk);color:var(--ink)}
.chip-choice:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.chip-choice:active{transform:scale(.94)}
.chip-choice[disabled]{opacity:.45;pointer-events:none} /* pressed = visibly in flight */
/* r68 — FULFILL button on a need-target masa ask: a one-tap attach/reply, matched
   to the chip scale, terra so it reads as an inline action not a card CTA (mango) */
.need-fulfill{display:inline-flex;align-items:center;gap:6px;margin-top:8px;
  font:inherit;font-size:.78rem;font-weight:700;color:var(--ink);cursor:pointer;
  background:var(--linen);border:1.5px solid var(--line);border-radius:999px;padding:5px 12px}
.need-fulfill svg{fill:none;stroke:currentColor;flex:none}
.need-fulfill:hover{background:var(--sunk);color:var(--ink)}
.need-fulfill:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.need-fulfill:active{transform:scale(.94)}
/* package actions match the reply-chip scale (Juan: same size as masa's chips) */
.pkg-actions{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}
.pkg-ok,.pkg-change{font-size:.82rem;font-weight:700;padding:6px 14px;border-radius:999px}
.pkg-ok{background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));color:#191919;border-color:transparent;box-shadow:0 0 0 .5px #09090b8c,0 1px 2px #00000059;} /* approve = the site's light-gradient (cohesion) */
.pkg-ok:hover:not([disabled]){filter:brightness(1.03)}
/* tapback (iMessage): the chosen reaction on the bubble's bottom corner */
.thread-item.masa{position:relative}
.tapback{
  position:absolute;bottom:-9px;left:10px;min-width:24px;height:24px;display:inline-flex;
  align-items:center;justify-content:center;background:var(--surface);
  border-radius:12px;border:1px solid var(--line);font-size:.82rem;padding:0 4px;
  animation:pop-in .34s var(--spring) both;
}
@keyframes pop-in{from{opacity:0;transform:scale(.3)}to{opacity:1;transform:none}}
/* long-press affordance: masa bubbles are pressable (no text selection flash) */
.has-menu{cursor:default;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}
/* MESSAGE CONTEXT MENU — floats near the pressed bubble (iMessage/G-Messages) */
.msg-menu{
  position:fixed;z-index:60;display:flex;flex-direction:column;gap:2px;
  background:var(--bg);border:1px solid var(--line);border-radius:16px;padding:6px;
  box-shadow:var(--shadow-float);animation:pop-in .18s var(--spring) both;min-width:170px;
}
.msg-menu[hidden]{display:none}
.mm-reacts{display:flex;gap:4px;justify-content:space-between;padding:2px 2px 6px;border-bottom:1px solid var(--line);margin-bottom:4px}
.mm-react{font-size:1.3rem;line-height:1;background:none;border:none;cursor:pointer;padding:5px;border-radius:50%;transition:transform .15s var(--spring),background .15s ease}
.mm-react:hover{background:var(--surface);transform:scale(1.15)}
.mm-react:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.mm-act{display:flex;align-items:center;gap:10px;font:inherit;font-weight:600;color:var(--ink);background:none;border:none;cursor:pointer;padding:8px 10px;border-radius:8px;text-align:left}
/* an author `display` beats the UA [hidden] rule, so contextual hiding (copy
   only with text, names only with files, r58's element-scoped scoping) needs
   this explicit override — without it every action always shows */
.mm-act[hidden]{display:none}
.mm-act svg{fill:none;stroke:currentColor;color:var(--ink-soft)}
.mm-act:hover{background:var(--surface)}
.mm-act:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
/* file details (version handles) — quiet header inside the menu */
.mm-file{margin:0 0 4px;padding:6px 10px;font-size:.72rem;color:var(--ink-soft);border-bottom:1px solid var(--line);max-width:230px;overflow-wrap:break-word}
.mm-file[hidden]{display:none}
/* own messages: copy + reply (Apple parity Jul 9 2026 — self-reply allowed;
   reacting to yourself stays off) */
.msg-menu[data-mode="mine"] .mm-reacts{display:none}
/* DELIVERY CELEBRATION — Apple-style: confetti rains from the top, drifts and
   spins as it falls, fades near the bottom. Softer, smoother than a flat drop. */
.confetti{position:fixed;inset:0;z-index:70;pointer-events:none;overflow:hidden}
.confetti[hidden]{display:none}
.confetti i{
  position:absolute;top:-16px;width:8px;height:13px;border-radius:2px;
  opacity:.95;animation:fall 1.55s cubic-bezier(.4,.1,.6,1) forwards;will-change:transform,opacity;
}
@keyframes fall{
  0%{opacity:0;transform:translateY(-16px) translateX(0) rotate(0)}
  12%{opacity:.95}
  60%{transform:translateY(58vh) translateX(14px) rotate(320deg)}
  100%{opacity:0;transform:translateY(104vh) translateX(-10px) rotate(680deg)}
}
@media (prefers-reduced-motion:reduce){.confetti{display:none !important}}
/* quote reply — the message being answered, above the field + on the bubble */
.quote-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--surface);border-left:3px solid var(--ink-soft);border-radius:8px;font-size:.82rem;color:var(--ink-soft)}
.quote-bar[hidden]{display:none}
.quote-bar span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.quote-bar svg{fill:none;stroke:var(--ink-soft);flex:none}
/* element-scoped reply (r58): the pressed photo's thumbnail, in the compose bar */
.quote-thumb{flex:none;width:30px;height:30px;object-fit:cover;border-radius:6px;display:block}
.quote-thumb[hidden]{display:none}
#quote-x{flex:none;width:24px;height:24px;border:none;background:none;color:var(--ink-soft);cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
#quote-x svg{fill:none;stroke:currentColor}
.t-quote{font-size:.8rem;color:var(--ink-soft);border-left:2px solid var(--line);padding-left:8px;margin-bottom:4px;text-align:left}
/* APPLE THREADED REPLY: the original as its own quiet pill above the reply,
   a thin connector dropping to the bubble (the iMessage thread look) */
.t-quote-pill{
  justify-self:end;position:relative;max-width:82%;margin:0;
  font-size:.78rem;color:var(--ink-soft);text-align:left;
  border:1.5px solid var(--line);border-radius:14px;padding:6px 12px;
}
.t-quote-pill::after{
  content:"";position:absolute;top:100%;right:20px;width:1.5px;height:7px;background:var(--line);
}
/* r58 — a visual (element-scoped) reply quote: the media thumbnail sits inside
   the pill beside the label; label-less (media-only) quote is just the thumb */
/* r62 — a tappable quote (jumps to the original): a quiet affordance, no button
   chrome; the press feedback is a slight dim (reduced-motion safe, no transform) */
.t-quote-jump{cursor:pointer}
.t-quote-jump:active{opacity:.6}
.t-quote-jump:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.t-quote-pill.has-thumb{display:flex;align-items:center;gap:8px;padding:5px 10px 5px 6px}
.t-quote-pill .t-quote-thumb{flex:none;width:34px;height:34px;object-fit:cover;border-radius:9px;display:block}
.t-quote-pill.has-thumb span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* day dividers — a quiet date line when the day changes (messenger norm) */
.day-div{
  list-style:none;justify-self:center;text-align:center;color:var(--ink-soft);
  font-size:.72rem;font-weight:700;padding:2px 10px;
}
/* voice item — its own soft tray (Juan: too cramped floating under the photo);
   reads as a distinct playable element, right-aligned pill */
.t-voice{
  display:inline-flex;gap:8px;align-items:center;font-weight:700;
  background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:6px 12px;
}
.mine .t-thumbs + .t-voice{margin-top:10px}
.voice-bars{display:inline-flex;gap:3px;align-items:center;height:18px}
.voice-bars i{display:block;width:3px;border-radius:2px;background:var(--ink-soft)}
.voice-bars i:nth-child(1){height:8px}.voice-bars i:nth-child(2){height:14px}
.voice-bars i:nth-child(3){height:18px}.voice-bars i:nth-child(4){height:11px}
.voice-bars i:nth-child(5){height:15px}
/* PACKAGE card — the per-issue tracker in the thread: accent spine, versions
   as an audit-trail list. Wider than a bubble; still a thread citizen. */
.thread-item.package{display:block;max-width:100%;width:100%;border-left:3px solid var(--line);text-align:left}
.pkg-versions{list-style:none;margin:8px 0 0;padding:8px 0 0;border-top:1px solid var(--line);display:grid;gap:6px;font-size:.88rem}
.pkg-versions strong{color:var(--ink)}
/* MINIMAL CLIENT CARD (Juan's call Jul 12 2026): version history is kitchen
   chrome — the current version rides INSIDE the status chip ("waiting on your
   ok · v2"), only when a revision exists; the standalone .pkg-vtag pill
   retired r42 (one chip, one home for state) */
.demo-chip{border-style:dashed;color:var(--ink-soft);margin-left:6px}
.t-note{color:var(--ink);margin-top:2px}
.t-meta{color:var(--ink-soft);font-size:.75rem;margin-top:4px}
/* r108 — the CHANNEL LABEL on a mirrored-in post ("from your instagram"). Ported from the
   homepage hero's own `.hd-via` (site/assets/css/azul.css:189) so the promise and the product
   read the same. Quiet by intent: it answers "how did this get here?" and then gets out of
   the way — the post is the content, this is a footnote. `.mine` is right-aligned + a grid
   with justify-items:end when bare, so the label needs no alignment of its own.
   ⚑ LEADING: --lh-body (1.6), matching the hero — NOT --lh-ui. r105's leading fix put
   --lh-ui on `.chip-status` because a PILL has a height contract (--pin-h) and 1.6 made
   every deliverable card 5px taller. This label is a standalone caption with no height
   contract, so that rule never applied to it; r108 applied it anyway and Juan caught the
   cramping by eye (line box 11px vs the hero's 13.6px). Don't "fix" this back to --lh-ui. */
.t-via{display:block;font-size:.58rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-soft);opacity:.75;line-height:var(--lh-body);margin-bottom:3px}
/* status = a real PILL everywhere (shelf head, cards, pins) — bare colored
   text read amateur (Juan, Jul 9 2026); the pill is the product-grade norm */
.chip-status{
  display:inline-flex;align-items:center;gap:6px;width:fit-content;
  font-size:.72rem;line-height:var(--lh-ui);font-weight:700;color:var(--ink-soft);letter-spacing:.01em;
  background:var(--surface);border:1px solid var(--line);border-radius:999px;
  padding:3px 11px;white-space:nowrap;
}

/* THE COMPOSER — one multimodal box: textarea on top, controls below
   (attach · record-pill-that-morphs · send), chips/preview in between */
.composer{display:grid;gap:8px} /* fade above the composer tried + removed (Juan, Jul 11) */
.composer[hidden]{display:none}
/* the field is a soft pill, one line tall — THIN (Juan: less tall). The whole
   composer row is 38px; recording + preview states match this exact height. */
/* the pill GROWS with the textarea (min-height, not a fixed 38px) so wrapped
   text — incl. a long unbroken word — stays INSIDE its rounded border, never
   spilling above/below the pill (r45 fix, Juan's screenshot). Symmetric 7px
   pad keeps the resting single-line box at exactly 38px. */
.field-pill{
  flex:1;min-width:0;display:flex;align-items:center;min-height:38px;
  background:var(--surface);border:1px solid var(--line);border-radius:19px;padding:7px 14px;
}
.field-pill:focus-within{border-color:var(--terra)}
.composer textarea{
  width:100%;border:none;background:transparent;resize:none;font:inherit;color:var(--ink);
  font-size:16px; /* never smaller — prevents the iOS focus zoom-jump */
  /* break-anywhere: a 120-char unbroken 'word' wraps inside the pill on EVERY
     engine — Safari's textarea UA default is overflow-wrap:normal (no wrap →
     horizontal spill over the attach button); Chromium's is break-word. Set
     both properties so the wrap is ours, not the browser's. (r45) */
  line-height:1.35;padding:0;min-height:20px;max-height:112px;overflow-y:auto;
  overflow-wrap:anywhere;word-break:break-word;
}
.composer textarea:focus{outline:none}
/* flex-end: when the pill grows tall, attach + send bottom-align to it (the
   messenger convention) — identical to center while everything is one 38px row */
.composer-row{display:flex;gap:8px;align-items:flex-end}
.attach-btn{
  flex:none;width:38px;height:38px;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--line);border-radius:50%;color:var(--ink);cursor:pointer;background:transparent;
}
.attach-btn:hover{border-color:var(--ink-soft);color:var(--ink)}
.attach-btn:focus-within{outline:3px solid var(--terra);outline-offset:2px}
.attach-btn svg{fill:none;stroke:currentColor}
.attach-btn input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.composer-row .rec-row{flex:none;min-width:0}
.composer-row .btn-send{flex:none;height:38px;padding:0 18px;border-radius:19px;font-size:.95rem}
.composer-row .btn-send[hidden]{display:none}
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* drag-and-drop overlay (Slack pattern) — warm veil, dashed drop card */
.drop-zone{
  position:fixed;inset:0;z-index:50;display:flex;align-items:center;justify-content:center;
  background:rgba(239,233,222,.85); /* --bg veil; color-mix upgrade below */
  background:color-mix(in srgb, var(--bg) 82%, transparent);
}
.drop-zone[hidden]{display:none}
.drop-card{
  border:2px dashed var(--line);border-radius:var(--r-lg);background:var(--linen);
  padding:26px 34px;font-weight:700;color:var(--ink);
}
.a2hs{display:flex;gap:10px;align-items:center;justify-content:center;flex-wrap:wrap;text-align:center;margin:0}
.a2hs[hidden]{display:none}
.a2hs .btn-ghost{padding:6px 14px;font-size:.9rem}
.a2hs .dismiss{background:none;border:none;color:var(--ink-soft);font:inherit;font-size:.9rem;text-decoration:underline;cursor:pointer;padding:4px}
.a2hs .dismiss:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
@media (prefers-reduced-motion:reduce){
  .btn-rec,.rec-wave{transition:none}
  .btn-rec[aria-pressed="true"] .rec-stop{animation:none}
}

/* THE mascot recipe — one construction everywhere (motion-lab standard):
   terra blob (the one border-radius) + eyes at the LAB RATIO — eye = 16.5%
   of blob width (7.5px @ 46px), centers 32% apart (gap 7px) — no strokes.
   Every depiction — receipt face, daydreaming record pill, daydreaming play
   button — uses exactly these two classes. */
.masa-face{
  display:inline-flex;align-items:center;justify-content:center;gap:7px;flex:none;
  width:46px;height:44px;background:var(--masa-dough,#3F3F3F);
  border-radius:62% 38% 55% 45% / 58% 52% 48% 42%;
}
.masa-face[hidden]{display:none}
.m-eye{display:none;width:7.5px;height:7.5px;border-radius:50%;background:var(--linen);flex:none}
.masa-face .m-eye{display:block;animation:blink 6.4s ease-in-out infinite}
@keyframes blink{0%,95.5%,100%{transform:scaleY(1)}97.5%{transform:scaleY(.12)}}

/* (daydream-on-controls retired Jul 8 2026 — mascot-as-host ruling: the mascot
   lives in the THREAD now: hero empty-state + avatar on its bubbles.) */
@media (prefers-reduced-motion:reduce){
  .masa-face .m-eye{animation:none}
}

/* RECORDING TAKEOVER (Gemini/voice-memo pattern): the recorder REPLACES the
   composer row at the SAME 38px height (Juan: no bigger than the text box) */
.composer.recording .field-pill,
.composer.recording .attach-btn,
.composer.recording .btn-send,
.composer.recording .file-list,
.composer.recording .rec-preview{display:none}
.composer.recording .rec-row{flex:1;width:100%}
.composer.recording .rec-wave{height:38px}
.status-row{display:flex;align-items:center;gap:8px}
.status:empty{display:none} /* the row costs zero space until it speaks */

/* POST-RECORD PREVIEW = one compact 38px row (Juan: same as the text box):
   play · waveform track · time · re-record · discard, all inline */
.rec-preview{margin:0;display:flex;align-items:center;gap:8px}
/* post-record order (Apple's voice-draft bar, per Juan's reference shots):
   ✕/redo far left · play · the track · duration right */
.rec-preview .player{display:flex;align-items:center;gap:8px;width:100%}
.rec-preview #rec-discard{order:1}
.rec-preview #rerecord-btn{order:2}
.rec-preview .btn-play{order:3}
.rec-preview .player-track{order:4}
.rec-preview #play-time{order:5}
.rec-preview[hidden]{display:none}
.player{display:contents} /* its children join the single preview row */
.take-actions{display:contents}
.take-actions .icon-ghost{width:34px;height:34px}
/* the play button is a mini masa: dough-shaped, terracotta, its "eyes" are the glyph */
.btn-play{
  flex:none;width:38px;height:38px;border:none;cursor:pointer;
  background:var(--ink);display:inline-flex;align-items:center;justify-content:center;
  border-radius:50%; /* plain control — the mascot speaks in the thread, not here */
}
.btn-play:hover{filter:brightness(1.05)}
.btn-play:focus-visible{outline:3px solid var(--cta);outline-offset:3px}
.btn-play .g-play,.btn-play .g-pause{fill:var(--linen);transform-origin:center;transition:opacity .18s ease,transform .24s var(--spring)}
.btn-play .g-pause{opacity:0;transform:scale(.55)}          /* eyes tucked away */
.btn-play.playing .g-pause{opacity:1;transform:scale(1)}    /* eyes stand up = pause */
.btn-play.playing .g-play{opacity:0;transform:scale(.55)}   /* fused triangle rests */
@media (prefers-reduced-motion:reduce){
  .btn-play .g-play,.btn-play .g-pause{transition:none}
}
/* Telegram-style track: the bars ARE the progress; the range is an invisible,
   fully accessible overlay (no thumb chrome fighting the bars) */
.player-track{position:relative;flex:1;min-width:0;height:38px}
.player-track canvas{position:absolute;inset:0;width:100%;height:100%;background:var(--sunk);border-radius:var(--r-md)}
.seek-overlay{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}
.player-track:has(.seek-overlay:focus-visible){outline:3px solid var(--terra);outline-offset:2px;border-radius:var(--r-md)}

.fld{display:grid;gap:6px;font-weight:700;font-size:.9rem}
.fld input,.fld textarea{
  font:inherit;font-weight:400;color:var(--ink);background:var(--linen);
  border:1px solid var(--line);border-radius:var(--r-md);padding:10px 12px;width:100%;
}
.fld input:focus-visible,.fld textarea:focus-visible{outline:3px solid var(--cta);outline-offset:1px}
/* file input: style the NATIVE button (::file-selector-button — no JS, a11y intact,
   browser auto-translates the label text). Matches .btn / design language. */
.fld input[type=file]{padding:8px;font-weight:400;color:var(--ink-soft);cursor:pointer}
.fld input[type=file]::file-selector-button{
  font:inherit;font-weight:700;color:var(--linen);background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));
  border:none;border-radius:var(--r-md);padding:10px 16px;margin-right:12px;cursor:pointer;
}
.fld input[type=file]::file-selector-button:hover{filter:brightness(1.05)}
/* pre-send tray shows the CONTENT (iMessage/WhatsApp norm): photos/videos as
   thumbnail tiles with an ✕; only non-visual files keep the name pill */
.file-list{list-style:none;margin:10px 0 0;padding:0 4px;display:flex;flex-wrap:wrap;gap:10px;align-items:center}
.file-list li{
  display:flex;justify-content:space-between;align-items:center;gap:8px;
  background:var(--sunk);border-radius:var(--r-md);padding:8px 12px;font-size:.9rem;
}
.file-list .rm{background:none;border:none;color:var(--ink-soft);font:inherit;font-weight:700;cursor:pointer;padding:4px}
.file-list .rm:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.file-list .f-tile{position:relative;width:58px;height:58px;padding:0;background:none;border-radius:12px}
.f-tile img{width:100%;height:100%;object-fit:cover;border-radius:12px;border:1px solid var(--line);display:block}
.f-tile .rm-tile{
  position:absolute;top:-7px;right:-7px;width:21px;height:21px;padding:0;border-radius:50%;
  background:var(--ink);color:var(--linen);font-size:.68rem;line-height:1;
  display:flex;align-items:center;justify-content:center;box-shadow:0 1px 4px rgba(0,0,0,.3);
}
.f-tile.is-vid::before{
  content:"";position:absolute;bottom:7px;left:7px;z-index:1;width:0;height:0;
  border-left:10px solid var(--linen);border-top:6px solid transparent;border-bottom:6px solid transparent;
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.45));
}

.send-row{display:grid;gap:8px}
.send-row[hidden]{display:none} /* token gate hides the composer wholesale */
.btn-send{background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));color:#191919;font-size:1.05rem;padding:14px}
.btn-send:hover:not([disabled]){filter:brightness(1.03)}
.status{margin:0;font-weight:700}
.status.ok{color:var(--ok)}
.status.err{color:var(--err)}
.foot{color:var(--ink-soft);font-size:.8rem;text-align:center}
/* micro-footer lines: quiet single-liners under the composer (space budget);
   once the conversation exists they TUCK away — progressive disclosure */
.micro{font-size:.72rem;line-height:1.35}
.foot.micro p{margin:0}
.micro.tucked{display:none}
/* session voice replay — play back the take you just sent (until reload) */
.t-replay{
  font:inherit;font-size:.8rem;font-weight:700;color:var(--ink);cursor:pointer;
  background:none;border:1px solid var(--line);border-radius:999px;padding:3px 12px;margin-top:4px;
}
.t-replay:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
/* the pkg title becomes a button when a draft can open */
.pkg-open{background:none;border:none;font:inherit;font-weight:700;color:var(--ink);cursor:pointer;padding:0;text-align:left}
.pkg-open:hover{color:var(--ink)}
.pkg-open:focus-visible{outline:3px solid var(--terra);outline-offset:2px;border-radius:4px}
/* explicit 'see the full draft' button — the obvious way into the shelf */
.pkg-view{
  display:inline-flex;align-items:center;margin-top:12px;padding:9px 16px;border-radius:999px;
  font:inherit;font-weight:700;font-size:.9rem;color:var(--linen);background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));
  border:none;cursor:pointer;transition:background .15s ease,transform .18s var(--spring);
}
.pkg-view:hover{background:var(--ink)}
.pkg-view:active{transform:scale(.96)}
.pkg-view:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
/* iOS A2HS guide — a small illustrated sheet (the gesture has no prompt) */
.ios-guide{
  border:none;background:var(--bg);color:var(--ink);width:min(420px,90vw);
  border-radius:18px;padding:22px;margin:auto;
}
.ios-guide::backdrop{background:rgba(34,52,46,.6)}
.ios-guide[open]{animation:sheet-up .38s var(--spring) both}
.ig-title{font-weight:800;font-size:1.05rem;margin:0 0 14px}
.ig-steps{list-style:none;margin:0 0 18px;padding:0;display:grid;gap:12px}
.ig-steps li{display:flex;align-items:center;gap:12px;line-height:1.4}
.ig-ic{flex:none;width:38px;height:38px;border-radius:10px;background:var(--surface);border:1px solid var(--line);display:inline-flex;align-items:center;justify-content:center;color:var(--ink-soft)}
.ig-ic svg{fill:none;stroke:currentColor}
.ios-guide .btn{width:100%;background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));border-radius:12px}
.ios-guide .btn:hover{filter:brightness(1.05)}
/* P4 beta-feedback reporter (r82) — reuses .ios-guide + .chip-choice + the
   canonical .ho-actions/.pkg-* action pair; only these three rules are new. */
.br-types{display:flex;gap:8px;margin:0 0 14px}
.br-type[aria-pressed=true]{background:var(--ink);color:var(--linen)}
.beta-report .fld{margin:0 0 8px}
.br-status{margin:0 0 12px;min-height:1.1em;font-size:.85rem;color:var(--ink-soft)}
/* THE SHELF — full deliverable view; slides up (Apple sheet idiom) */
/* SAFETY (Jul 9 2026 blank-page bug): author display/height on a dialog's
   BASE class overrides the browser's dialog:not([open]){display:none} and
   renders closed dialogs as ghost blocks. Layout ONLY under [open]. */
dialog:not([open]){display:none !important}
.shelf{
  border:none;padding:0;background:var(--bg);color:var(--ink);
  width:min(560px,94vw);border-radius:18px;overflow:hidden;margin:auto;
}
.shelf[open]{
  display:flex;flex-direction:column;
  /* explicit height: flex children size deterministically — content whose
     height arrives late (video metadata) can't collapse the dialog */
  height:min(88dvh,860px);
}
/* the decision never scrolls away — sticky footer for the whole review */
.shelf-foot{
  flex:none;background:var(--bg);border-top:1px solid var(--line);
  padding:10px 16px calc(10px + env(safe-area-inset-bottom));
}
.shelf-foot[hidden]{display:none}
.shelf-foot .pkg-actions{margin-top:0;justify-content:center}
.shelf::backdrop{background:rgba(34,52,46,0);transition:background .3s ease}
.shelf[open]::backdrop{background:rgba(34,52,46,.6)}
.shelf[open]:not(.vt){animation:sheet-up .38s var(--spring) both}
/* the Linktree-style expand: the shelf IS the preview card, grown */
.shelf.vt{view-transition-name:shelf-zoom}
::view-transition-group(shelf-zoom){animation-duration:.38s;animation-timing-function:var(--spring)}
@keyframes sheet-up{from{opacity:0;transform:translateY(24px) scale(.98)}to{opacity:1;transform:none}}
/* sheet header, 2026 grammar: grabber · tight title/status block · ✕ */
.shelf-head{
  position:relative;display:flex;align-items:center;justify-content:space-between;
  gap:12px;padding:20px 16px 12px;border-bottom:1px solid var(--line);background:var(--bg);
}
.shelf-head::before{ /* the universal sheet affordance */
  content:"";position:absolute;top:8px;left:50%;transform:translateX(-50%);
  width:36px;height:4px;border-radius:2px;background:var(--line);
}
.shelf-head > div{display:grid;gap:6px;min-width:0}
.shelf-head .t-what{font-size:1.12rem;line-height:1.25;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.shelf-body{padding:14px 16px 20px;overflow-y:auto;flex:1;min-height:0;display:grid;gap:10px}
.shelf-media{width:100%;border-radius:12px;display:block;cursor:zoom-in} /* borderless media rule */
img.shelf-media{max-height:200px;object-fit:cover} /* reading scale — layout belongs to the send tool (ruling Jul 12 2026) */
/* portrait video can't swallow the sheet: capped + letterboxed, never cropped */
video.shelf-media{max-height:44dvh;object-fit:contain;background:var(--ink)}
/* the draft is READ-ONLY until approved: captions can't be selected/copied —
   honest friction, not DRM (screens can always be screenshotted) */
.caption-block.locked{user-select:none;-webkit-user-select:none}
video.shelf-media{cursor:default}
.shelf-body h3{font-size:.95rem;margin:6px 0 0;color:var(--ink)}
.shelf-body p{margin:0;line-height:1.55}
.caption-block{
  display:flex;align-items:flex-start;gap:8px;justify-content:space-between;
  background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:10px 12px;
}
.caption-block p{flex:1;min-width:0}

.fp-cap{margin:14px 0 12px;font-size:.85rem;line-height:1.5}
.fp-copy{font:inherit;font-size:.78rem;font-weight:700;background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));color:var(--bg);
  border:none;border-radius:999px;padding:6px 14px;cursor:pointer}
.fp-strip{display:grid;grid-template-columns:repeat(auto-fill,minmax(84px,1fr));gap:6px}
.fp-strip img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:8px;display:block}
.fp-k{margin:2px 0 0;font-family:ui-monospace,Menlo,monospace;font-size:.66rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-soft)}

/* shared final-package (fp-*) v3 — the approved deliverable in dashboard grammar */
.fp{display:grid;gap:12px;margin:12px 0 4px}
.fp-linkcard{display:block;background:var(--sunk);border-radius:var(--r-md);overflow:hidden;
  text-decoration:none;color:var(--ink)}
.fp-linkcard img{width:100%;max-height:170px;object-fit:cover;display:block}
.fp-lc-meta{display:block;padding:10px 12px}
.fp-lc-t{display:block;font-size:.88rem;line-height:1.35}
.fp-lc-f{display:flex;justify-content:space-between;margin-top:6px;font-size:.72rem;color:var(--ink-soft)}
.fp-lc-f .fp-go{color:var(--ink);font-weight:700}
/* r94 (Juan) — inline link PREVIEW card inside a message bubble (Apple idiom;
   reuses .fp-linkcard). Constrain width + align to the bubble's side. */
.t-linkcard{max-width:min(80vw,300px);margin-top:4px;border:1px solid var(--line)}
.thread-item.masa .t-linkcard{margin-right:auto}
.thread-item.mine .t-linkcard{margin-left:auto}
/* r95 (Juan) — a LINK-ONLY message floats its card ALONE: no chat bubble, no
   mascot face (Apple idiom — link previews aren't wrapped in a speech balloon). */
.thread-item.bare-link{background:transparent;border:none;padding:0;overflow:visible}
.thread-item.masa.bare-link{display:block}
.thread-item.masa.bare-link .t-face{display:none}
.thread-item.mine.bare-link{justify-self:end}
.bare-link .t-linkcard{margin:0;max-width:min(82vw,320px)}
.thread-item.mine.bare-link .t-linkcard{margin-left:auto}
/* r96 (Juan) — text + link: the words keep their own small bubble, the card
   floats bare below (Apple parity). */
.bare-link .msa-textbubble{display:inline-block;max-width:min(82vw,340px);
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-lg);border-bottom-left-radius:6px;padding:9px 13px;margin-bottom:5px}
.bare-link .msa-textbubble p{white-space:pre-wrap;overflow-wrap:anywhere}
.fp-post{background:var(--sunk);border-radius:var(--r-md);padding:12px}
.fp-kind{display:inline-block;font-family:ui-monospace,Menlo,monospace;font-size:.62rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-soft);margin:0 0 8px}
.fp-media{width:100%;max-height:300px;object-fit:cover;border-radius:8px;display:block;background:#000}
.fp-slides{display:flex;gap:8px;overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;
  scrollbar-width:none;border-radius:8px}
.fp-slides::-webkit-scrollbar{display:none}
.fp-slides img{flex:0 0 88%;scroll-snap-align:start;width:88%;aspect-ratio:4/5;object-fit:cover;border-radius:8px}
.fp-cap{margin:6px 0 0;font-size:.85rem;line-height:1.5}
.fp-copyblock{position:relative;background:var(--bg);border:1px solid var(--line);border-radius:10px;
  padding:10px 48px 10px 12px;margin-top:10px}
.fp-copyblock .fp-icon{position:absolute;right:8px;top:8px}
.fp-cap-l{display:block;font-family:ui-monospace,Menlo,monospace;font-size:.6rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-soft)}
.fp-actions{display:flex;gap:8px;margin-top:12px}
.fp-icon{display:grid;place-items:center;width:36px;height:32px;border-radius:10px;background:var(--ink);
  color:var(--on-cta);border:none;cursor:pointer;text-decoration:none} /* dark-on-accent (flask reskin) */
.fp-icon.done{background:color-mix(in srgb,var(--ok) 20%,var(--bg));color:var(--ok)} /* was #fff — white on the light cyan failed contrast */
.fp-icon svg{stroke:currentColor;fill:none}
a.fp-icon{background:var(--surface);color:var(--ink-soft);border:1px solid var(--line)}
.fp-strip{display:grid;grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:8px}
.fp-strip figure{margin:0}
.fp-strip img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:8px;display:block}
.fp-strip figcaption{font-size:.64rem;color:var(--ink-soft);margin-top:3px}
.fp-k{margin:2px 0 -4px;font-family:ui-monospace,Menlo,monospace;font-size:.66rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-soft)}

/* r55 — approved card meta row: the status pill sits LEFT, the 'full package →'
   link right-aligned; the title keeps its OWN line above (never crowded). The
   link is a real <a> (it navigates to another surface — your space), styled
   subtle/flat: terra + weight + the arrow carry the affordance, underline only
   on hover/focus. NEVER mango — mango is reserved for the approve action. */
/* r113 (Juan's B2) — the status pill rides the TITLE row. `align-items:baseline` sits it on
   the title's FIRST-line baseline rather than centering it against a title that wrapped.
   The pill is nowrap + flex:none, so if a title ever does run long the TITLE takes the wrap
   and the pill keeps its width — the state must never truncate. */
.pkg-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.pkg-head .t-what{min-width:0}
.pkg-head .chip-status{flex:none;margin:0}
.pkg-meta{display:flex;align-items:center;justify-content:space-between;gap:10px;
  flex-wrap:wrap;margin:2px 0}
.pkg-meta .chip-status{margin:0}
/* r64 — status + timing grouped on the left of the meta row (timing no longer
   eats its own line); the 'full package →' link stays pushed right by space-between */
.pkg-meta-left{display:flex;align-items:center;gap:8px;min-width:0;flex-wrap:wrap}
/* posting-timing (r60): a quiet when-to-post cue from the deliverable's `due` date —
   informational metadata, inline beside the status; soft, never mango, not a pill */
.pkg-timing{display:inline-flex;align-items:center;gap:4px;margin:0;font-size:.74rem;font-weight:600;color:var(--ink-soft)}
.pkg-timing svg{flex:none;fill:none;stroke:currentColor}
.fp-more{flex:none;margin:0;font-size:.78rem;font-weight:700;color:var(--ink);
  text-decoration:none;white-space:nowrap}
.fp-more:hover{text-decoration:underline;text-underline-offset:3px}
.fp-more:focus-visible{outline:3px solid var(--terra);outline-offset:2px;border-radius:4px}

/* fp copy fold (Juan's call Jul 12 2026): compact by default in the thread; slide copies
   grouped, each field keeps its own button */
.fp-copies{margin-top:10px}
.fp-copies>summary{cursor:pointer;list-style:none;font-family:ui-monospace,Menlo,monospace;
  font-size:.68rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-soft);
  padding:8px 10px;background:var(--bg);border:1px solid var(--line);border-radius:10px}
.fp-copies>summary::-webkit-details-marker{display:none}
.fp-copies>summary::after{content:" ▸";}
.fp-copies[open]>summary::after{content:" ▾";}
/* slide groups separate by SPACING alone (r43 — the r41 hairline was still
   decoration without meaning; the group padding keeps slides reading as groups) */
.fp-sg{margin-top:12px;padding:10px 0 2px}
.fp-sg-k{display:block;font-family:ui-monospace,Menlo,monospace;font-size:.62rem;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink-soft);margin:0 0 2px 2px}

/* audit Jul 12 2026 — reduced-motion coverage for the keyframes JS doesn't gate
   (.ios-guide by CLASS: the handoff confirm sheet shares the sheet grammar) */
@media (prefers-reduced-motion:reduce){
  .tapback,.msg-menu,.shelf[open]:not(.vt),.ios-guide[open],
  .lightbox[open]:not(.vt) img,.lightbox[open]:not(.vt) video{animation:none !important}
}
/* the chip row's pan must never chain into the page (fp-slides already contains) */
.choice-row{overscroll-behavior-x:contain}

/* HORIZONTAL CLAMP (Juan's call Jul 12 2026): the page can never pan sideways — any
   overflowing element used to let iOS drag the whole app left/right during swipe
   gestures ('the page trips out'). clip ≠ hidden: no scroll container is created. */
html,body{overflow-x:clip;overscroll-behavior-x:none;max-width:100vw}

/* the kind label IS the fold in the thread (Juan's correction). Restyle
   (Juan's review of r40): the summary row centers vertically, sits at a
   consistent height on every card, and wears the THREAD's label type —
   system family, .82rem/700 — not the dashboard's mono micro-caps */
details.fp-post>summary.fp-kind{
  cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;
  min-height:28px;margin:0;
  font-family:inherit;font-size:.82rem;font-weight:700;
  letter-spacing:normal;text-transform:none;
  color:var(--ink); /* thread ink, not terra (Juan's r42 call) — dark theme resolves to the soft white */
}
details.fp-post[open]>summary.fp-kind{margin-bottom:8px}
details.fp-post>summary.fp-kind::-webkit-details-marker{display:none}
details.fp-post>summary.fp-kind::after{content:"▸";color:var(--ink-soft)} /* the affordance keeps the tint */
.fp-kind-n{color:var(--ink-soft)} /* the piece count stays muted beside the ink label */
details.fp-post[open]>summary.fp-kind::after{content:"▾"}
.fp-copies-body{margin-top:2px}

/* r56 — MINI MEDIA PREVIEW: one small thumb per lean post piece, pinned to the far
   right of the kind-label summary row (margin-left:auto). Its OWN fixed size — the
   .t-thumbs sizing rules never reach it. The fold affordance (▸/▾) still sits after
   it in the pseudo-element, so the thumb reads as media, not the toggle. */
/* r63 — one uniform thumb size for EVERY collapsed piece (single mini + peek
   thumbs), so every deliverable-piece row is the same height (Juan: the first/
   reel card read taller). 34px splits the old 40 (single) / 30 (peek). */
.fp-mini{flex:none;margin-left:auto;width:34px;height:34px;border-radius:8px;box-shadow:0 0 0 1px var(--line)}
.fp-mini img{width:100%;height:100%;object-fit:cover;border-radius:8px;display:block}
/* MULTI-MEDIA PEEK: a strip of up to 4 thumbs (a carousel/story reads as multi-
   part at a glance), right-pinned like the single mini; same 34px so rows match.
   The last thumb of a bigger set wears a "+N". Tapping any of them expands the
   piece's fold (r63) — full media lives in your space. */
.fp-peek{display:flex;gap:4px;margin-left:auto;flex:none}
.fp-peek .fp-mini{margin-left:0}
/* r65 — a VIDEO piece with NO poster shows a neutral placeholder tile with a play
   glyph so it reads as "video", not broken. (A video WITH a poster just shows the
   poster.) Tapping any piece EXPANDS its fold — playback lives in your space. */
.fp-mini-empty{position:relative;background:var(--surface)}
.fp-mini-play{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:16px;height:16px;border-radius:50%;background:rgba(0,0,0,.45)}
.fp-mini-play::after{content:"";position:absolute;top:50%;left:53%;transform:translate(-50%,-50%);
  border-style:solid;border-width:3.5px 0 3.5px 6px;border-color:transparent transparent transparent #fff}
.fp-mini-more{position:relative}
.fp-mini-more .fp-mini-n{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  background:rgba(0,0,0,.52);color:#fff;font-size:.68rem;font-weight:700;border-radius:8px}

/* r75 — deliverable CHIP ROW + panel (lean thread, Juan's mockup). One chip per
   piece (glyph + type word); tap → the panel shows THAT piece's preview + captions;
   ONE open at a time. Active chip = the app accent (matches the active-tab idiom;
   NOT the mockup's raw blue), warm-neutral otherwise. The panel preview is a real
   review tile (bigger than the old 34px summary dot) and opens the lightbox on tap. */
.fp-chips{margin-top:2px}
/* r109 (Juan) — LEFT-aligned, superseding r78's "pills centered in the bubble". Every other
   element in the card starts at the same left edge (title, status, pairs, copy blocks), so a
   centered row was the odd one out and its start moved with the chip count. r78 centered them
   back when the fan below was centered too; the pair layout left-aligns the media, so centering
   the picker now misaligns it against the very thing it picks. */
.fp-chiprow{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 14px;justify-content:flex-start}
.fp-chip{display:inline-flex;align-items:center;gap:7px;min-height:40px;padding:8px 14px;
  border:1px solid var(--line);border-radius:999px;background:transparent;color:var(--ink-soft);
  font:inherit;font-size:.84rem;font-weight:650;cursor:pointer;
  transition:background .15s ease,color .15s ease,border-color .15s ease}
.fp-chip-g{flex:none;color:var(--ink-soft)}
.fp-chip:active{background:var(--sunk)}
.fp-chip.on{background:var(--ink);border-color:var(--ink);color:var(--bg)}
.fp-chip.on .fp-chip-g{color:var(--bg)}
.fp-chip:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.fp-chip{transition:none}}
.fp-chippane{display:block}
.fp-chippane[hidden]{display:none}
.fp-chippane .fp-copyblock:first-child,.fp-chippane .fp-sg:first-child{margin-top:0}
.fp-cap-none{margin-top:0;color:var(--ink-soft);opacity:.75;font-style:italic}
/* r78 — per-pill MEDIA FAN (Juan): the ACTIVE piece's frames as a small, CENTERED fan of
   overlapping, gently-rotated ~76px cards; tap any → the lightbox gallery. Shows EVERY frame
   (their fanned edges — no "+N"). The thread's wireMediaFan() TIGHTENS the overlap (margin-
   left) so many frames still fit within the bubble instead of overflowing. Single-frame
   pieces are one tile (no fan). Hover/focus lifts + straightens a card. */
/* r80's FIXED 100px height stands (the first caption always sits the same distance below, no
   matter how many frames — Juan). r109 changes only the horizontal: flex-start, not center.
   The fan survives in exactly one case now (multi-frame + one whole-post caption); with the
   chips and the pairs both left-aligned it was the last centered element on the card, so it
   floated away from the column everything else shares. */
.fp-mediafan{display:flex;justify-content:flex-start;align-items:flex-end;margin:2px 0 14px;padding:6px 0 4px;height:100px;box-sizing:border-box}
.fp-mediafan.single{height:100px} /* r81 — single-frame fan same fixed height as multi, so the caption distance matches (Juan) */
.fp-mslide{flex:0 0 76px;width:76px;height:76px;margin-left:-20px;padding:0;border:none;position:relative;
  border-radius:12px;overflow:hidden;background:var(--surface);cursor:pointer;
  box-shadow:0 3px 10px rgba(0,0,0,.28); /* r79 (Juan) — border/ring removed off the fan tiles */
  transform:rotate(calc((var(--i) - (var(--n) - 1) / 2) * 5deg));transform-origin:bottom center;
  transition:transform .16s ease}
.fp-mslide:first-child{margin-left:0}
/* r79 — a small slide NUMBER top-left ties each fan tile to its "slide N caption" */
.fp-mnum{position:absolute;top:3px;left:3px;min-width:15px;height:15px;padding:0 3px;line-height:1;
  display:flex;align-items:center;justify-content:center;font-size:.58rem;font-weight:800;
  color:#fff;background:rgba(0,0,0,.6);border-radius:7px;pointer-events:none}
.fp-mediafan.single .fp-mslide{transform:none;margin-left:0}
.fp-mslide img{width:100%;height:100%;object-fit:cover;display:block;border-radius:12px}
.fp-mslide:hover,.fp-mslide:focus-visible{transform:translateY(-3px) rotate(0deg);z-index:6}
.fp-mslide:active{transform:scale(1.03)}
.fp-mslide.fp-mini-empty{display:grid;place-items:center}
@media (prefers-reduced-motion:reduce){.fp-mslide{transition:none}}
/* r107 — TRUE ASPECT per piece. Every tile rendered 76x76 SQUARE regardless of kind, so a
   9:16 story and a 4:5 feed post looked identical and the client approved a square crop of
   something that publishes in another shape — discovering the real framing only after, in
   the native app. `data-kind` has been set on the fan by final-package.js since r78 and used
   by ZERO rules; this is that hook, finally read.
   ⚑ HEIGHT STAYS 76px — r80/r81 (Juan) fix the fan height so the first caption is always the
   same distance below; varying height would break that. The WIDTH varies instead.
   ⚑ SCOPE: geometry only, NOT platform chrome. 4:5 / 9:16 are stable platform facts; chrome
   churns. This is the futureproof half of "true-to-platform" without the treadmill half —
   and masa's client posts natively, so they see the real preview seconds later anyway. */
/* ⚑ r109 — these MUST match the tile in BOTH layouts. The fan and the pair build the same
   `.fp-mslide` element, but the pair puts it under `.fp-pair[data-kind]` instead of
   `.fp-mediafan[data-kind]`. Scoping to the fan alone silently reverted every paired tile
   to the 76x76 square r107 exists to kill — caught by measuring aspect, not by looking. */
/* ⚑ DESCENDANT, not `>` — r116 wrapped the pair's tile in `.fp-pairtile` so the tie line can
   live in the tile's column. A direct-child selector would have stopped matching and silently
   reverted every paired tile to the 76x76 square r107 exists to kill. That exact break already
   happened once at r109 and only measuring aspect caught it; the fan's rules were descendant
   all along, so this just makes the two consistent. */
.fp-mediafan[data-kind="story"] .fp-mslide, .fp-pair[data-kind="story"] .fp-mslide,
.fp-mediafan[data-kind="reel"] .fp-mslide,  .fp-pair[data-kind="reel"] .fp-mslide{flex:0 0 auto;width:auto;aspect-ratio:9/16}
.fp-mediafan[data-kind="post"] .fp-mslide, .fp-pair[data-kind="post"] .fp-mslide,
.fp-mediafan[data-kind="carousel"] .fp-mslide, .fp-pair[data-kind="carousel"] .fp-mslide{flex:0 0 auto;width:auto;aspect-ratio:4/5}
/* the deck overlap is tuned for a 76px-wide tile; a 43px-wide 9:16 tile would be nearly
   buried by it, so tighten the tuck proportionally for the narrow kinds */
.fp-mediafan[data-kind="story"] .fp-mslide,
.fp-mediafan[data-kind="reel"] .fp-mslide{margin-left:-12px}
.fp-mediafan[data-kind="story"] .fp-mslide:first-child,
.fp-mediafan[data-kind="reel"] .fp-mslide:first-child{margin-left:0}

/* ---- THE PAIR (r109, Juan) -------------------------------------------------------
   Each frame sits BESIDE its own copy instead of above a stack of "slide N" blocks.
   WHY: measured, a single 9:16 tile was 43px of media in a 307px row — 86% EMPTY. And it
   can't be solved by growing the tile: r80/r81 fix the row at 100px, so a 9:16 tile maxes
   out ~50px wide and would need to be ~546px TALL to fill 307px. Portrait media can never
   fill a wide row — only TEXT is wide enough, so the text moves INTO the row.
   Fulfils what r78 already asked for in words ("the caption sits WITH its media") but
   stacked; retires r79's slide numbers, because the pairing IS the mapping. */
.fp-pairs{margin:2px 0 12px}
.fp-pair{display:flex;align-items:flex-start;gap:10px}
.fp-pair + .fp-pair{margin-top:10px}
/* ⚑ JUAN'S CONSTRAINT (Jul 15 2026), and the whole point of the layout: the TOP of the
   copy box must sit at exactly the same y as the TOP of the media it belongs to. Two
   things enforce it and BOTH are required — align-items:flex-start on the row, and killing
   .fp-copyblock's default margin-top:10px on the first block. Miss the second and every
   box drops 10px below its tile. Verified by render: delta 0 on every pair. */
.fp-pair > .fp-paircopy > .fp-copyblock:first-child,
.fp-pair > .fp-paircopy > .fp-sg:first-child{margin-top:0}
.fp-pair > .fp-paircopy{flex:1 1 auto;min-width:0}
/* the tile is the SAME element the fan builds (so the lightbox wiring + r107's aspect
   apply unchanged) — it just loses the fan's rotation and tuck. */
.fp-pair .fp-mslide{flex:0 0 auto;transform:none;margin-left:0}
.fp-pair .fp-mslide:hover,.fp-pair .fp-mslide:focus-visible{transform:translateY(-2px)}
.fp-pair .fp-mslide:active{transform:scale(1.03)}
/* r116 — the tile's own column: the tile, then the tie line running down the rest of the
   group's height. `align-self:stretch` overrides the row's align-items:flex-start so the
   column spans the full group; the line is flex:1 so it fills whatever the copy is tall. */
.fp-pairtile{display:flex;flex-direction:column;align-items:center;flex:0 0 auto}
.fp-pair.fp-tied > .fp-pairtile{align-self:stretch}
.fp-tieline{flex:1 1 auto;width:2px;min-height:8px;background:var(--line);border-radius:1px;margin:6px 0 2px}
/* a frame with no copy of its own: the tile still holds its row, the note sits beside it */
.fp-pair .fp-cap-none{margin:0;color:var(--ink-soft);opacity:.8}
/* ⚑ THE TIE (r115, Juan) — a group of 2+ copy blocks gets a connector down its left edge,
   binding every block to the tile beside it. The pair binds by PROXIMITY, and proximity is
   the WEAK cue: on a slide carrying caption + poll + 2 options the last block sits ~200px
   below its tile. Gestalt's uniform connectedness is "one of the strongest grouping
   principles, often overriding proximity and similarity" — the line does work proximity
   can't. Only at 2+ blocks: one block is already unambiguous, and masa's rule is that chrome
   is purposeful only.
   ⚑ Chosen over a common region (the research's other form) because every .fp-copyblock
   already carries its own background + border — a pad around them would nest pads in pads.
   The line is lighter, and it's the flat idiom masa already uses.
   ⚑ r116 (Juan) — the line lives in the TILE's column, NOT on the copy's left border.
   r115 bordered .fp-paircopy, which cost the caption ~22px (2px rule + 10px padding + 10px
   margin) — taking back the width the pair layout exists to give it. The line now descends
   from the tile inside .fp-pairtile, so the copy keeps every pixel. Nothing here touches a
   first block's top, so r109's alignment (top of copy == top of media) still survives. */
/* review mode (waiting): the caption reads but can't be copied/selected — no button,
   so the block drops its right gutter (mirrors .caption-block.locked's honest friction) */
.fp-copyblock.locked{padding-right:12px;user-select:none;-webkit-user-select:none}
/* the quiet 'unlocks on approval' line under a waiting/review card's pieces */
.fp-lock-hint{margin:10px 2px 2px;font-size:.76rem;color:var(--ink-soft);font-style:italic}

/* r118 — THE DESTINATION LINE (blast radius). Ported from the site hero's own
   `.hd-chain` onto capture's tokens — the hero said this from r1 and the product
   could not back it up, so this is the promise finally built.
   ⚑ THE HERO NO LONGER DRAWS IT (Juan, Jul 15 2026), so don't go looking for
   `.hd-chain` in site/assets/css/azul.css — it's retired, and the note where it
   used to live points here. It was cut from the HERO, not repudiated: measured on
   that one screen, the gate was stated 4× and the list 3×, and the `approve ✓`
   button says it better by DEMONSTRATING the gate rather than asserting it.
   ⚑ WHICH IS EXACTLY WHY IT STAYS HERE. The same fact cuts both ways: blast radius
   is a DECISION aid, and it earns its row at the moment a real person decides
   something with real consequences. Nobody decides anything in a hero — a marketing
   mock had it as redundant copy; this card has it as the one element naming the
   CONSEQUENCE of the yes. Retiring it there is not an argument for retiring it here.
   (Surviving copies: this, and threads.html's `.dchain`, where the destination is
   still new information.)
   Chosen shape = option B of
   design-studies/masa-newsletter-card-options.html: the ONLY new atom.
   ⚑ WHY IT EARNS A ROW on the review card: the human-in-the-loop literature says
   an approver needs a review-context packet — what the agent wants to do, why,
   and above all the BLAST RADIUS: who sees this if I say yes. Every other element
   on the card describes the WORK; this is the only one that describes the
   CONSEQUENCE, which is the thing actually being consented to. Best-evidenced
   element on the card.
   REVIEW-ONCE, not decoration: rendered only while the card is waiting (beside
   the approve buttons, at the decision moment). An approved card's blast radius
   is spent — it already went out. */
.fp-chain{margin:9px 0 0;padding-top:8px;border-top:1px solid var(--line);
  font-size:.69rem;color:var(--ink-soft);line-height:var(--lh-ui)}
.fp-chain b{color:var(--ink);font-weight:700}

/* audit Jul 12 2026 — 44px HIT AREAS: tiny controls keep their visual size,
   an invisible ::after pad catches the finger (the parent is the positioning
   box). Insets are tuned so no expanded area overlaps a neighboring control —
   the tight rows (chips, reacts, pkg actions) expand mostly vertically. */
.icon-act,#quote-x,.stack-label,.vmsg-play,.chip-choice,.mm-react,.pkg-ok,.pkg-change{position:relative}
.icon-act::after,#quote-x::after,.vmsg-play::after{content:"";position:absolute;inset:-8px}
.f-tile .rm-tile::after{content:"";position:absolute;inset:-6px} /* smaller pad: the next tile sits 10px away */
.stack-label::after{content:"";position:absolute;inset:-8px -8px -4px} /* the deck's top card sits right below */
.chip-choice::after{content:"";position:absolute;inset:-8px -3px}      /* chip row gap is 6px */
.mm-react::after{content:"";position:absolute;inset:-7px -2px}         /* react row gap is 4px */
.pkg-ok::after,.pkg-change::after{content:"";position:absolute;inset:-7px -4px} /* action row gap is 8px */

/* REVISION HANDOFF (r45): the client's explicit "that's everything" is now a
   suggestion bubble at the FOOT of the thread — the .handoff-row <li> is a grid
   citizen like any message and RIGHT-ALIGNS to the client's side (justify-self:
   end); it scrolls WITH history and rides the thread's own just-in entrance
   (no bespoke animation). The pill keeps the r43 look: content-hugging, mango
   CTA fill + .pkg-ok --linen label (NEVER pure white — the shared recipe holds
   contrast in both themes), on the .pkg-ok action scale (~36px visible) with a
   44px tap target via the invisible ::after pad. Chip exists ONLY when there's
   something to hand off (renderThread omits the bubble otherwise). */
.handoff-row{list-style:none;justify-self:end;max-width:86%}
.handoff-chip{
  position:relative;width:fit-content;max-width:100%;min-width:0;
  display:flex;align-items:center;justify-content:center;
  font:inherit;font-size:.82rem;font-weight:600;color:#191919;cursor:pointer;
  background:var(--masa-btn-grad,linear-gradient(to top,#cfdef6 20%,#fafafa 80%));border:.5px solid transparent;border-radius:999px;padding:6px 14px;
  box-shadow:0 0 0 .5px #09090b8c,0 1px 2px #00000059;
  transition:filter .15s ease,transform .18s var(--spring);
}
.handoff-chip::after{content:"";position:absolute;inset:-6px -8px} /* 44px hit pad */
.handoff-chip:hover{filter:brightness(1.03)}
/* pressed = background deepens (reduced-motion safe); the scale is gated below */
.handoff-chip:active{filter:brightness(.98);transform:scale(.98)}
.handoff-chip:focus-visible{outline:3px solid var(--terra);outline-offset:2px}
.handoff-chip span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* rise-in still serves the composer chrome below (quote-reply, link hint, a2hs) */
@keyframes rise-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){
  .handoff-chip{transition:none}
  .handoff-chip:active{transform:none} /* the background shift alone says "pressed" */
}
/* micro-entrances (r43): composer chrome that mounts mid-session eases in
   instead of popping — quote-reply fallback bar, link hint, the a2hs nudge.
   :not([hidden]) starts the animation the moment JS unhides; all three begin
   life hidden, so nothing animates on first paint. Compositor-only. */
.quote-bar:not([hidden]),.link-hint:not([hidden]),.a2hs:not([hidden]){animation:rise-in .2s ease both}
@media (prefers-reduced-motion:reduce){
  .quote-bar:not([hidden]),.link-hint:not([hidden]),.a2hs:not([hidden]){animation:none}
}

/* the round boundary in the thread — a quiet centered rule, like the day line */
.handoff-div{
  list-style:none;justify-self:center;text-align:center;color:var(--ink-soft);
  font-size:.72rem;font-weight:700;padding:2px 10px;
}
/* the confirm sheet shares the ios-guide grammar; two buttons side by side.
   The pair wears the CANONICAL action recipe (.pkg-ok/.pkg-change — Juan's
   review of r40: the old 12px slabs didn't match the page). The pill radius
   must outrank .ios-guide .btn's 12px (0,2,0 vs 0,2,0 — source order wins). */
.ho-body{margin:0 0 16px;color:var(--ink-soft);line-height:1.5}
.ho-actions{display:flex;gap:8px}
.ho-actions .btn{flex:1;min-height:44px;border-radius:999px}
.ho-actions .btn-ghost,.ho-actions .btn-ghost:hover{background:transparent;color:var(--ink)}

/* PIN BAR = the needs disclosure (r50/r51). Each pin row is wrapped in a
   .pin-slot inside the .pin-float layer. A row with open needs gains a chevron;
   tapping opens its dropdown IN-FLOW below it — so sibling slots reflow DOWN
   (never overlapping the open options, #6) while the float, being out of the
   flex flow, grows over the thread without moving it. */
.pin-slot{min-width:0}
.pin-chev{flex:none;color:var(--terra);transition:transform .2s var(--spring)}
.pin-row[aria-expanded="true"] .pin-chev{transform:rotate(180deg)}
/* r68/r69 — the "N more ⌄" collapse toggle: a quiet full-width row under the first
   pin. The label is OPTICALLY CENTERED (Juan) — the chevron floats absolute-right so it
   doesn't pull the text off-center; the text alone sits at the true middle. */
/* r103 (Juan: "it looks a little bit too thick") — the thickness was NOT the fill:
   this control inherited the BODY's 1.6 line-height, so an 11.8px label carried an
   18.9px line box (30.9px row). line-height:1.2 is the UI-control value and still
   clears the ⌄ glyph's descender. The row now reads ~26px and keeps its 44px tap
   target via the ::after pad — the same thin-visual/generous-hit idiom as .pin-row
   and .handoff-chip, so slimming the pill costs no reachability. */
/* r104 (Juan) — OPTICAL, not box, symmetry. This row is TRANSPARENT, so its own
   padding is invisible ink-spacing that STACKS on the panel's: 6px here + 8px on
   .pin-float read as 14px under the label, against 8px above the first pill —
   the panel measured symmetric and looked bottom-heavy. The pills are opaque, so
   their box edge IS the ink; this row's isn't. Zero the bottom padding and let the
   panel's 8px be the whole gap; keep 4px on top which, with the 4px grid gap,
   makes the label sit 8px under the pill. Every gap in the panel is now 8px:
   edge→pill, pill→label, label→edge. */
.pin-more{position:relative; /* anchors the hit pad below */
  display:flex;align-items:center;justify-content:center;gap:5px;width:100%;
  font:inherit;font-size:.74rem;line-height:1.2;font-weight:700;color:var(--ink-soft);cursor:pointer;
  background:none;border:none;padding:4px 4px 0;border-radius:var(--r-md)}
/* the hit pad claims ONLY dead space: the 4px grid gap above + the panel's 8px
   bottom padding. r103's -9px reached 5px INTO the pill's own box and, being later
   in the DOM, stole taps off the pill's bottom edge — a real regression, fixed here.
   ~30px hit height, clear of WCAG 2.2 §2.5.8 (24px) and matching what this control
   has always had. */
.pin-more::after{content:"";position:absolute;inset:-4px 0 -8px}
/* OPTICAL CENTER (Juan): an invisible mirror chevron on the LEFT balances the real one
   on the right, so the "N more ⌄" pair sits at the true visual middle, not pushed right */
.pin-more::before{content:"⌄";visibility:hidden;flex:none}
.pin-more .pin-chev{color:var(--ink-soft)}
.pin-more[aria-expanded="true"] .pin-chev{transform:rotate(180deg)}
.pin-more:active{background:var(--sunk)}
@media (prefers-reduced-motion:reduce){.pin-chev{transition:none}}
/* the ROUTE affordance (r53): → on a row that jumps straight to its target (a
   0/1-need project row, a single-need to-do row) — the read-before-tap contrast
   to the chevron's "expands". Mirrors .pin-need-arr so both themes match. */
.pin-arr{flex:none;color:var(--terra);font-weight:700}
/* THE DROPDOWN (r51: in-flow, not the r50 absolute overlay): it takes layout
   space WITHIN the float, so opening it pushes the sibling rows below it down;
   the float sits over the thread, so the thread never moves. The height +
   opacity animate open AND closed from JS (reduced-motion = instant);
   overflow:hidden clips the height tween. */
/* r52 — the dropdown is ONE surface; its items are plain rows ON it (Juan: "just
   drop down to reveal the button itself without a different container surrounding
   it"). The shadow moved to .pin-float (which now backs the whole region), so the
   drop keeps only its bg + border to read as a nested panel — no double shadow. */
/* r91 (Juan) — an OPEN row + its dropdown read as ONE connected card, not a header
   plus a separate floating block: the drop drops the top gap, adopts the header's
   linen fill + terra left-accent, squares its top corners, and a single hairline
   splits header from needs. The open row squares its bottom to meet it. */
.pin-drop{
  margin-top:0;overflow:hidden;
  background:var(--linen);border:1px solid var(--line);border-left:3px solid var(--terra);
  border-radius:0 0 var(--r-md) var(--r-md);
}
.pin-row[aria-expanded="true"]{border-radius:var(--r-md) var(--r-md) 0 0;border-bottom-color:transparent}
.pin-row.seen[aria-expanded="true"] + .pin-drop{border-left-color:var(--line)}
.pin-drop[hidden]{display:none}
/* r92 (Juan) — the needs read as CHILDREN of the header: each is a recessed
   (darker) pill, indented in from the header's edge, separated by a small gap,
   with a bit of panel fill below the last one so it never sits flush. */
.pin-drop-list{list-style:none;margin:0;padding:5px 6px 9px;display:grid;gap:5px}
.pin-drop-list li{min-width:0}
.pin-drop-list li:not(:first-child){border-top:0}
/* r52 — a plain tappable row on the drop surface: no per-item bg/border/radius
   box (the r51 box-in-a-box Juan flagged), and no left accent tag (the terra/line
   left edge encoded an internal actionable-vs-composer split meaningless to the
   client — Juan: "what does the light blue tag stand for?"). Keep 44px tap height,
   the text + arrow, and the :active/:focus-visible press feedback. */
.pin-need{
  position:relative;box-sizing:border-box;width:100%;
  display:flex;align-items:center;gap:8px;justify-content:space-between;
  min-height:var(--pin-h);padding:6px 12px;text-align:left;
  font:inherit;font-size:.82rem;line-height:var(--lh-ui);font-weight:600;color:var(--ink);
  background:var(--sunk);border:0;border-radius:9px;cursor:pointer;
  margin-left:10px;width:calc(100% - 10px); /* r92: recessed child pill, indented from the header edge */
}
/* r105 — reach without an inflated box: the .pin-row idiom, so a sub-item is no
   longer TALLER than the row it hangs under. Claims only the 5px list gap. */
.pin-need::after{content:"";position:absolute;inset:-4px 0}
/* SOFT-MARK (r51, #7; re-expressed r52 WITHOUT a colored left edge): a
   message-need answered this session — a UI signal only (Juan/the engine
   authoritatively clears it). Struck + dimmed + a teal ✓ arrow read as done on
   their own. Still tappable (re-reply), just visibly done. */
.pin-need-answered{opacity:.6}
.pin-need-answered .pin-need-txt{text-decoration:line-through}
.pin-need-answered .pin-need-arr{color:var(--ok)}
.pin-need-txt{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pin-need-arr{flex:none;color:var(--terra);font-weight:700}
.pin-need:active{background:var(--line)}
.pin-need:focus-visible{outline:3px solid var(--terra);outline-offset:2px}

/* FOCUS-AND-HIGHLIGHT (r50): focusItem spotlights the card it routes to — a
   single terra/accent tint (::after over the card's own surface, so it never
   fights the card background) that pulses once and fades over ~1.5s. Reduced
   motion holds a STATIC tint for the same window (no pulse). One at a time. */
.focus-flash::after,.focus-hold::after{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:var(--accent);
}
.focus-flash::after{opacity:0;animation:focus-pulse 1.5s ease-out forwards}
.focus-hold::after{opacity:.14}
@keyframes focus-pulse{0%{opacity:0}16%{opacity:.16}100%{opacity:0}}
@media (prefers-reduced-motion:reduce){
  .focus-flash::after{animation:none;opacity:.14} /* belt-and-suspenders: JS already picks focus-hold */
}
