/* ============ PYRA MOCKS — shared platform-card CSS =======================
   Every rule PyraPiecesStudio's render() functions (studio-editor.js) depend
   on: the platform mock cards themselves (X/Threads/Bluesky/LinkedIn/
   Instagram/Pinterest/YouTube/Reddit/Newsletter/quote-graphic/video-frame/
   platform-chrome/story), plus the showcase-only mocks (blog/landing-page/
   case-study/campaign) — extracted out of studio.css so a page other than
   studio.html/content-studio.html can render the SAME markup with the SAME
   styling, without inheriting studio.css's own app chrome (rail, hero,
   interview, echoes grid, vault, inspector, palette — none of which a card
   renderer needs, several of which collide by class name with an unrelated
   page's OWN chrome — see the header note in whichever page links this file).

   studio.html and content-studio.html both still load studio.css alongside
   this one; nothing here depends on load order relative to it — every
   --m-bg/--m-ink/--m-mut/--m-line/--m-soft/--m-brand custom property a mock
   needs is declared locally inside its own .mock-<family> rule (see the
   "Per-family theme variables" note below), and the few :root-level tokens
   these rules DO reach for (--gold/--muted/--text/--line/--serif/--mono/
   --sans) are generic enough that any page defining its own theme root can
   supply them — that is what lets this file mean the same thing wherever
   it loads, not a coincidence to preserve carefully.

   PyraMockClass(mock, theme) (studio-editor.js) is what makes any of this
   apply at all — it builds the `mock mock-<mock> mock-<family> [m-<theme>]`
   class string every render() output must be wrapped in. A page that renders
   PyraPiecesStudio output without that wrapper gets unstyled markup: no
   --m-* variables resolve, so every mock renders as unthemed black-on-black
   text. Check for this before assuming a blank card is a CSS bug. */

/* ───── MOCKUPS : shared ───── */
.mock{padding:14px 16px;font-family:var(--sys);-webkit-font-smoothing:antialiased}
.mock .hrow{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.ava{width:40px;height:40px;border-radius:50%;object-fit:cover;flex-shrink:0;display:block;background:#222}
.vb{width:16px;height:16px;flex-shrink:0;display:block}
.mtxt{font-size:15px;line-height:20px;white-space:pre-wrap;word-wrap:break-word}
.abar{display:flex;align-items:center;justify-content:space-between;max-width:430px;margin-top:12px}
.ab{display:flex;align-items:center;gap:7px;font-size:13px;opacity:.6;transition:opacity .2s,color .2s}
.ab .ic{width:18px;height:18px}
.ab i{font-style:normal;font-variant-numeric:tabular-nums}

/* ── Per-family theme variables ──
   Each family root sets --m-bg/--m-ink/--m-mut/--m-line (plus --m-soft
   where the family has filled chips) to the platform's REAL 2026 default;
   a `.mock-<fam>.m-<theme>` variant re-sets only the variables. Brand
   colours (X blue, LinkedIn blue, Reddit orange, Pinterest red) stay
   literal — the platforms keep them constant across themes. The variant
   class comes from PyraMockClass(mock, theme); every one-arg caller (board
   tiles, the /b share page) renders the platform default by construction. */

/* X — dark-first */
.mock-x{--m-bg:#000;--m-ink:#E7E9EA;--m-mut:#71767B;--m-line:#2F3336;
  background:var(--m-bg);color:var(--m-ink);padding:12px 16px}
.mock-x.m-light{--m-bg:#fff;--m-ink:#0F1419;--m-mut:#536471;--m-line:#CFD9DE}
.mock-x .hrow b{font-weight:700;font-size:15px}
.mock-x .hdl{color:var(--m-mut);font-size:15px}
.mock-x .mtxt{margin:3px 0 0}
.mock-x .abar{color:var(--m-mut)}
.mock-x .ab:hover{opacity:1;color:#1D9BF0}
.mock-x .ab.hl:hover{color:#F91880}
.xt{display:flex;gap:12px}
.xt+.xt{margin-top:2px}
.xt-l{display:flex;flex-direction:column;align-items:center}
.xt-conn{flex:1;width:2px;background:var(--m-line,#2F3336);margin-top:4px;min-height:16px}
.xt-last .xt-conn{display:none}
.xt-r{flex:1;min-width:0;padding-bottom:12px}
/* Poll, pre-vote state: rounded option rows in X's action blue, votes line
   beneath — the shape a just-published poll shows a non-voter. */
.mock-x .xpl-opts{margin-top:10px;display:flex;flex-direction:column;gap:8px}
.mock-x .xpl-opt{border:1px solid var(--m-line);border-radius:999px;padding:10px 16px;
  font-size:14px;font-weight:600;color:#1D9BF0;overflow-wrap:anywhere}
.mock-x .xpl-meta{font-size:13px;color:var(--m-mut);margin-top:10px}

/* Threads — dark-first */
.mock-th{--m-bg:#101010;--m-ink:#F5F5F5;--m-mut:#8E8E8E;
  background:var(--m-bg);color:var(--m-ink)}
.mock-th.m-light{--m-bg:#fff;--m-ink:#000;--m-mut:#999}
.mock-th .hrow b{font-weight:700;font-size:15px}
.mock-th .th-more{margin-left:auto;color:var(--m-mut)}
.mock-th .mtxt{margin-top:4px;font-size:15px;line-height:21px}
/* Time sits in the header row now, beside the name — where Threads puts it. */
.mock-th .th-time{color:var(--m-mut);font-size:15px}
.mock-th .th-sum{color:var(--m-mut);font-size:13px;margin-top:9px}
.mock-th .abar{margin-top:9px;justify-content:flex-start;gap:0}
.mock-th .ab{margin-right:18px;opacity:.9}
.mock-th .ab .ic{width:24px;height:24px}
.mock-th .ab:hover{opacity:1}

/* Bluesky — dark-first */
.mock-bs{--m-bg:#161E27;--m-ink:#E6E6E6;--m-mut:#8D939B;--m-line:#2A3542;
  background:var(--m-bg);color:var(--m-ink)}
.mock-bs.m-light{--m-bg:#fff;--m-ink:#0B1013;--m-mut:#5A6470;--m-line:#D8DEE4}
.mock-bs .hrow{align-items:center}
.mock-bs .hrow b{font-weight:600;font-size:15px}
.mock-bs .bs-hdl{color:var(--m-mut);font-size:14px}
.mock-bs .mtxt{margin:5px 0 0;font-size:15px;line-height:21px}
/* Reply / link card / custom-feed pill — real generator output the homepage
   adapter now surfaces (previously invisible in the preview though it was
   already editable and shipped in copyText). Ported from index.css's own
   .bs-second/.bs-link/.bs-feed, themed with the card's own vars so they
   follow light/dark instead of carrying a fixed dark palette. */
.mock-bs .bs-second{font-size:12.5px;color:var(--m-mut);border-left:2px solid var(--m-line);padding-left:10px;margin-top:8px;overflow-wrap:anywhere}
.mock-bs .bs-link{display:flex;align-items:center;gap:9px;border:1px solid var(--m-line);border-radius:11px;padding:8px 11px;margin-top:9px;font-size:11px;color:var(--m-mut);overflow-wrap:anywhere}
.mock-bs .bs-link .lk{width:26px;height:26px;border-radius:7px;background:linear-gradient(135deg,#16243a,#0d1622);display:grid;place-items:center;color:#7cc0ff;flex:none;font-size:12px}
.mock-bs .bs-feed{display:inline-flex;align-items:center;gap:7px;font:500 10px var(--mono);letter-spacing:.1em;color:#7cc0ff;border:1px solid rgba(124,192,255,.35);background:rgba(124,192,255,.08);border-radius:99px;padding:4px 11px;margin-top:9px}
.mock-bs .abar{color:var(--m-mut);margin-top:11px}
.mock-bs .ab:hover{opacity:1;color:#1185FE}
.mock-bs .ab.hl:hover{color:#EC4899}

/* LinkedIn — light-first (its real default); .m-dark is LinkedIn's own
   dark mode. --m-brand lightens the action blue in dark, as the platform
   does for text-on-dark contrast. */
.mock-li{--m-bg:#fff;--m-ink:#191919;--m-mut:#666;--m-line:#e6e6e6;
  --m-soft:#f3f2ef;--m-brand:#0A66C2;
  background:var(--m-bg);color:var(--m-ink)}
.mock-li.m-dark{--m-bg:#1B1F23;--m-ink:#E7E9EA;--m-mut:#A0A6AB;
  --m-line:#3A3F44;--m-soft:#2D3237;--m-brand:#71B7FB}
.mock-li .hrow{align-items:center;gap:9px}
.mock-li .ava{width:48px;height:48px}
.mock-li .li-name{font-weight:600;font-size:14px;line-height:1.25}
.mock-li .li-sub{font-size:12px;color:var(--m-mut);line-height:1.35}
.mock-li .li-time{font-size:12px;color:var(--m-mut);display:flex;align-items:center;gap:4px;margin-top:1px}
.mock-li .li-time .ic{width:13px;height:13px}
.mock-li .li-more{margin-left:auto;color:var(--m-mut)}
.mock-li .mtxt{margin:11px 0 0;font-size:14px;line-height:20px;color:var(--m-ink)}
/* A real <button> now (it expands the text like the platform's own), so it
   needs the inline-flow resets; the hidden tail spans get an explicit
   display rule rather than trusting the UA default to survive resets. */
.mock-li .li-moretxt{color:var(--m-brand);font-weight:600;cursor:pointer;
  background:none;border:0;padding:0;font-size:inherit;font-family:inherit}
.li-rest,.ig-rest{overflow-wrap:anywhere}
.li-rest[hidden],.ig-rest[hidden]{display:none}
.ig-cap .ig-more-btn{color:#8E8E8E;background:none;border:0;padding:0;
  font:inherit;cursor:pointer}
.mock-li .li-link{border:1px solid var(--m-line);border-radius:8px;overflow:hidden;margin:11px 0 2px}
/* The creative slot carries a dark brand canvas so the white overlay title
   reads — it stands in for the operator-supplied image, same palette as the
   quote tile. Theme-invariant: it represents the image, not UI. */
.mock-li .li-link-img{aspect-ratio:1.91/1;display:flex;align-items:center;justify-content:center;text-align:center;padding:16px;background:linear-gradient(145deg,#1d1810,#0c0a07)}
.mock-li .li-link-tt{font-weight:700;font-size:16px;color:#fff;text-shadow:0 1px 10px rgba(0,0,0,.55);line-height:1.2;overflow-wrap:anywhere}
.mock-li .li-link-meta{padding:10px 12px;background:var(--m-soft);display:flex;align-items:center;gap:10px}
.mock-li .li-link-copy{flex:1;min-width:0}
.mock-li .li-link-t{font-size:14px;font-weight:600;color:var(--m-ink);line-height:1.3}
.mock-li .li-link-d{font-size:12px;color:var(--m-mut);margin-top:2px;overflow-wrap:anywhere}
/* The CTA pill every sponsored single-image ad carries. */
.mock-li .li-cta{flex-shrink:0;border:1.5px solid var(--m-brand);color:var(--m-brand);
  font-weight:600;font-size:13px;border-radius:999px;padding:5px 14px;white-space:nowrap}
.mock-li .li-react{display:flex;align-items:center;gap:7px;margin:11px 0 8px;font-size:13px;color:var(--m-mut)}
.mock-li .li-bubbles{display:flex}
.mock-li .li-bubbles span{width:18px;height:18px;border-radius:50%;border:1.5px solid var(--m-bg);margin-left:-4px;display:flex;align-items:center;justify-content:center}
.mock-li .li-bubbles span:first-child{margin-left:0}
.mock-li .li-b1{background:#0A66C2}.mock-li .li-b2{background:#C37D16}.mock-li .li-b3{background:#DF704D}
.mock-li .li-react .li-r{margin-left:auto}
.mock-li .li-bar{display:flex;border-top:1px solid var(--m-line);padding-top:5px}
/* nowrap: flex:1 splits the bar into equal quarters, and "Comment" (7ch +
   a 20px icon + 6px gap) just exceeds one quarter of a phone-width card,
   so the label wrapped mid-word ("Commen / t"). */
.mock-li .li-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;font-size:13px;font-weight:600;color:var(--m-mut);padding:8px 0;border-radius:6px;white-space:nowrap}
.mock-li .li-btn:hover{background:var(--m-soft)}
.mock-li .li-btn .ic{width:20px;height:20px}
/* …and on the narrowest cards the four labels plus icons at full size would
   overflow instead of wrap, so they tighten. */
@media (max-width:480px){
  .mock-li .li-btn{gap:4px;font-size:12px}
  .mock-li .li-btn .ic{width:17px;height:17px}
}

/* Instagram post / carousel — LIGHT-first (the feed's real default; the
   old dark card asserted a theme most of the audience never sees). The
   media area (.ig-img) keeps its own dark canvas in both themes: it
   represents the image, not UI. */
.mock-ig{--m-bg:#fff;--m-ink:#262626;--m-mut:#8E8E8E;
  background:var(--m-bg);color:var(--m-ink);padding:0}
.mock-ig.m-dark{--m-bg:#000;--m-ink:#F5F5F5}
.mock-ig.m-dark .igq-tags span{color:#5AB4F5}
.ig-head{display:flex;align-items:center;gap:9px;padding:9px 12px}
.ig-head .ava{width:32px;height:32px}
.ig-ring{padding:2px;border-radius:50%;background:linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888)}
.ig-ring img{border:2px solid var(--m-bg,#000)}
.ig-un{font-weight:600;font-size:14px}
.ig-follow{color:#0095F6;font-weight:600;font-size:14px}
.ig-head .ig-more{margin-left:auto}
.ig-img{position:relative;aspect-ratio:4/5;overflow:hidden;background:#000}
.ig-slide{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;text-align:center;padding:30px;opacity:0;transition:opacity .4s}
.ig-slide.on{opacity:1}
.ig-st{font-family:var(--serif);font-size:clamp(22px,5.4vw,34px);line-height:1.18;color:#fff;text-shadow:0 2px 24px rgba(0,0,0,.45)}
.ig-st.tiny{font-size:15px;font-family:var(--sans);font-weight:700;letter-spacing:.04em}
.ig-wm{position:absolute;bottom:16px;left:0;right:0;font-family:var(--mono);font-size:9px;letter-spacing:.24em;color:rgba(255,255,255,.55)}
.ig-cnt{position:absolute;top:12px;right:12px;background:rgba(0,0,0,.5);color:#fff;font-size:12px;border-radius:999px;padding:3px 10px}
.ig-nav{position:absolute;top:50%;transform:translateY(-50%);width:28px;height:28px;border-radius:50%;background:rgba(255,255,255,.85);color:#000;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .2s}
.ig-img:hover .ig-nav{opacity:1}
.ig-nav.prev{left:8px}.ig-nav.next{right:8px}
.ig-nav .ic{width:15px;height:15px;stroke:#000}
.ig-dots{position:absolute;bottom:12px;left:0;right:0;display:flex;gap:4px;justify-content:center}
.ig-dots i{width:5px;height:5px;border-radius:50%;background:rgba(255,255,255,.4);transition:all .25s}
.ig-dots i.on{background:#0095F6;width:14px;border-radius:3px}
.ig-bar{display:flex;align-items:center;padding:9px 12px 4px;gap:15px}
.ig-bar .ic{width:25px;height:25px}
.ig-bar .ig-bm{margin-left:auto}
.ig-likes{padding:2px 12px;font-weight:600;font-size:14px}
.ig-likes i{font-style:normal}
.ig-cap{padding:5px 12px 3px;font-size:14px;line-height:19px}
.ig-cap b{font-weight:600}
.ig-cap .ig-more{color:#8E8E8E}
.ig-cmts{padding:1px 12px 4px;color:#8E8E8E;font-size:14px}
.ig-time{padding:0 12px 12px;color:#8E8E8E;font-size:11px;letter-spacing:.01em}
/* IG quote card (igq) — the overline inside the media slot's .ig-st and the
   hashtag row under the caption. Everything else reuses igc's own feed
   chrome (.ig-head/.ig-img/.ig-bar/.ig-likes/.ig-cmts/.ig-time) so the
   quote card cannot drift from the carousel's platform truth.

   The quote itself used to render as bare .ig-st text, a DIRECT child of
   .ig-img with no .ig-slide wrapper — igc's slides all get that wrapper's
   position:absolute;inset:0 + flex centering + padding, so skipping it
   left the quote pinned to the top-left corner of the media box with no
   centering at all, reading as a mostly-empty black square (measured:
   content filled ~23% of the box height). Now wrapped in .ig-slide.on like
   any other slide — one wrapper, always on since there is no carousel to
   switch between.

   Inside that, .ig-frame/.ig-inner is the homepage's OWN gradient-card
   graphic (index.css) — the actual composed image a creator would post,
   not a redesign of it. Same class names on purpose and safe to reuse:
   neither collides with anything studio.css already defines (unlike
   .ig-top/.ig-cap/.ig-tags, which DO collide with studio meanings and so
   stay as .igq-top/.igq-tags below), so the two are already one rule
   whenever the pages end up sharing a stylesheet. */
.ig-frame{border-radius:16px;padding:1.6px;background:linear-gradient(45deg,#f9ce34,#ee2a7b,#6228d7)}
.ig-slide .ig-frame{width:100%}
.ig-inner{background:#0b0b10;border-radius:14.5px;padding:22px 18px;text-align:center}
.ig-inner .ig-st{font-style:italic;text-shadow:none}
.igq-top{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.2em;
  text-transform:uppercase;color:rgba(255,255,255,.65);margin-bottom:10px}
.igq-tags{padding:0 12px 3px;display:flex;flex-wrap:wrap;gap:6px}
.igq-tags span{color:#00376B;font-size:13px;font-weight:500;overflow-wrap:anywhere}

/* vertical video frame (reel / tiktok / story / shorts) */
.mock-reel,.mock-story,.mock-tt,.mock-sh{padding:0;background:#000}
.vframe{position:relative;aspect-ratio:9/16;overflow:hidden;background:#000;color:#fff}
/* S3 — Film-IR-driven cover (board-cover-renderer.js). display:block avoids
   the inline-element baseline gap canvas has by default; width/height:100%
   fills the aspect-ratio box the same way the pre-canvas static markup did.
   (.vf-bg/.vf-mid/.vf-hook/.vf-play/.vf-rail/.vf-audio/.yt-bg/.tt-plus/
   .ig-plus/.marq — the DOM this replaced — were dead CSS by the time this
   comment was written: S3 (PR #96) swapped their markup for this canvas, and
   the rules were never cleaned up. Removed here rather than left beside the
   new platform-chrome CSS below, which now covers the same rail/progress/
   play-button ground for real.) */
.board-cover{position:absolute;inset:0;display:block;width:100%;height:100%}
.video-facts{position:absolute;z-index:7;top:8px;right:8px;max-width:calc(100% - 16px);padding:4px 6px;background:rgba(5,8,12,.78);border:1px solid rgba(255,255,255,.18);color:#fff;font:700 8px/1.2 var(--content-mono,monospace);letter-spacing:0;white-space:normal;overflow-wrap:anywhere;pointer-events:none}
/* Bottom scrim, matching what TikTok/Reels themselves paint under the
   handle/description zone. The mock relied on text-shadow alone, which
   held on dark compositions and vanished on light-brand boards — the
   regenerated b2b board (near-white paper background) rendered the white
   @handle and description effectively invisible. Sits at z-index:1: above
   the composition canvas, below .vf-bottom's text and the chrome rail,
   exactly like the platform's own gradient. */
.vframe::after{content:'';position:absolute;left:0;right:0;bottom:0;height:32%;background:linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.55));z-index:1;pointer-events:none}
.vf-bottom{position:absolute;left:12px;right:62px;bottom:12px;z-index:2}
.vf-un{font-weight:700;font-size:14px;margin-bottom:6px}
.vf-cap{font-size:13px;line-height:18px;margin-bottom:7px;text-shadow:0 1px 6px rgba(0,0,0,.7)}
.mock-tt .vf-un{text-shadow:1.5px 0 #FE2C55,-1.5px 0 #25F4EE}

/* Platform-native chrome (board-platform-chrome.js) — a decorative,
   aria-hidden overlay so a board card previews roughly how the piece will
   actually look live on TikTok/Reels/Shorts, not just its raw composition.
   Reuses the .vf-bottom caption box already reserving right:62px for a
   rail-width gutter. */
.board-chrome{position:absolute;inset:0;z-index:2;pointer-events:none}
.board-rail{position:absolute;right:9px;bottom:44px;display:flex;flex-direction:column;align-items:center;gap:13px}
.board-chrome-item{width:26px;display:flex;flex-direction:column;align-items:center;gap:3px;color:#fff;filter:drop-shadow(0 1px 3px rgba(0,0,0,.55))}
.board-chrome-item svg{width:26px;height:26px;flex:none}
.board-chrome-item i{font-style:normal;font-size:10.5px;font-weight:700;line-height:1;white-space:nowrap}
.board-chrome-item .board-chrome-label{font-size:9.5px;letter-spacing:.01em}
.board-chrome-disc svg{width:19px;height:19px;border-radius:50%;background:#15191f;border:1.5px solid rgba(255,255,255,.5);padding:3px;box-sizing:content-box;animation:board-disc-spin 3.2s linear infinite}
@keyframes board-disc-spin{to{transform:rotate(360deg)}}
.board-chrome-item-avatar{gap:0}
.board-chrome-avatar{position:relative;display:block;width:26px;height:26px;border-radius:50%;overflow:hidden;border:1.5px solid #fff}
.board-chrome-avatar img{width:100%;height:100%;display:block}
.board-chrome-plus{position:absolute;left:50%;bottom:-4px;transform:translateX(-50%);width:14px;height:14px;border-radius:50%;background:#FE2C55;color:#fff;font-style:normal;font-size:11px;font-weight:800;line-height:13px;text-align:center;border:1.5px solid #000}
.board-progress{position:absolute;left:0;right:0;bottom:0;height:2.5px;background:rgba(255,255,255,.22)}
.board-progress i{display:block;height:100%;width:var(--progress,0%);background:#fff}
/* Real per-platform progress-bar tint (TikTok and Shorts both run a color
   gradient, not a plain white bar; Reels' is not visibly tinted). */
.board-chrome-tt .board-progress i{background:linear-gradient(90deg,#25F4EE,#FE2C55)}
.board-chrome-sh .board-progress i{background:linear-gradient(90deg,#FF0033,#ff6b8a)}
.board-chrome-flat{position:static}
.board-chrome-playbtn{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:15%;aspect-ratio:1;min-width:34px;border-radius:50%;background:rgba(0,0,0,.4);color:#fff;display:flex;align-items:center;justify-content:center}
.board-chrome-playbtn svg{width:42%;height:42%}
@media(prefers-reduced-motion:reduce){.board-chrome-disc svg{animation:none}}

/* story */
.st-bars{position:absolute;top:9px;left:9px;right:9px;display:flex;gap:4px;z-index:3}
.st-bars i{flex:1;height:2.5px;border-radius:2px;background:rgba(255,255,255,.35)}
.st-bars i.done{background:#fff}
.st-bars i.cur{background:linear-gradient(90deg,#fff 45%,rgba(255,255,255,.35) 45%)}
.st-head{position:absolute;top:20px;left:11px;right:11px;display:flex;align-items:center;gap:8px;z-index:3}
.st-head .ava{width:30px;height:30px;border:1.5px solid #fff}
.st-head b{font-size:13px;font-weight:600}
.st-head span{font-size:12px;opacity:.8}
.st-head .st-more{margin-left:auto;opacity:.9}
.st-q{position:absolute;left:20px;right:20px;top:40%;transform:translateY(-50%);text-align:center;font-weight:700;font-size:21px;line-height:1.3;z-index:3;text-shadow:0 2px 10px rgba(0,0,0,.5)}
.st-poll{position:absolute;left:20px;right:20px;top:56%;display:flex;flex-direction:column;gap:9px;z-index:3}
.st-poll .po{background:rgba(255,255,255,.16);border:1.5px solid rgba(255,255,255,.55);border-radius:10px;padding:13px;text-align:center;font-weight:600;font-size:14px;backdrop-filter:blur(2px)}
/* The viewer's answer bar at the foot of every story. */
.st-reply{position:absolute;left:14px;right:14px;bottom:12px;z-index:3;
  border:1.5px solid rgba(255,255,255,.5);border-radius:999px;padding:11px 16px;
  font-size:13px;color:rgba(255,255,255,.8)}

/* Pinterest — LIGHT-first (Pinterest's UI is white; the old dark card was
   wrong on the platform's own terms). The pin image (.pin-img/.pin-bg)
   keeps its canvas: it is the pin, not UI. */
.mock-pin{--m-bg:#fff;--m-ink:#211922;--m-mut:#5f5f5f;
  background:var(--m-bg);color:var(--m-ink);padding:0}
.mock-pin.m-dark{--m-bg:#111;--m-ink:#E6E6E6;--m-mut:#bbb}
.pin-img{position:relative;aspect-ratio:2/3;overflow:hidden}
/* The canvas carries its OWN background, like .ig-img and .li-link-img. It
   used to be transparent and borrow the card's dark — so the theme work's
   light flip silently turned it white under white .pin-q text, a ghost
   title on three surfaces. A creative canvas is the pin, not UI, and must
   never follow the card's theme. */
.pin-bg{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;text-align:center;padding:28px;
  background:linear-gradient(160deg,#241A20,#141014 55%,#0D0A0C)}
.pin-q{font-family:var(--serif);font-size:clamp(22px,5.4vw,34px);line-height:1.22;color:#fff;text-shadow:0 2px 18px rgba(0,0,0,.4)}
.pin-q.sm{font-size:17px;font-family:var(--sans);font-weight:700;line-height:1.3}
.pin-wm{position:absolute;bottom:13px;left:0;right:0;text-align:center;font-family:var(--mono);font-size:9px;letter-spacing:.24em;color:rgba(255,255,255,.6)}
.pin-save{position:absolute;top:12px;right:12px;background:#E60023;color:#fff;font-weight:700;font-size:14px;border-radius:999px;padding:9px 17px;opacity:0;transform:translateY(-4px);transition:all .25s}
.mock-pin:hover .pin-save{opacity:1;transform:none}
.pin-body{padding:12px 14px 15px}
.pin-t{font-weight:600;font-size:16px;line-height:1.25;margin-bottom:6px;color:var(--m-ink)}
.pin-d{font-size:13px;line-height:18px;color:var(--m-mut);margin-bottom:10px}
.pin-kw{display:flex;flex-wrap:wrap;gap:9px;margin-bottom:11px}
.pin-kw span{font-size:13px;color:#E60023}
.pin-src{display:flex;align-items:center;gap:8px}
.pin-src .ava{width:24px;height:24px}
.pin-src b{font-size:13px;font-weight:600}
.pin-src .pin-more{margin-left:auto;color:var(--m-mut)}

/* YouTube */
.mock-yt{background:#0F0F0F;color:#F1F1F1;font-family:var(--yt);padding:0}
.yt-thumb{position:relative;aspect-ratio:16/9;overflow:hidden}
.yt-dur{position:absolute;bottom:8px;right:8px;background:rgba(0,0,0,.82);color:#fff;font-size:12px;font-weight:500;border-radius:4px;padding:2px 5px}
.yt-row{display:flex;gap:12px;padding:12px}
.yt-row .ava{width:36px;height:36px}
.yt-meta{min-width:0}
.yt-title{font-size:15px;font-weight:500;line-height:20px;color:#F1F1F1;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.yt-ch{font-size:13px;color:#AAA;margin-top:4px;display:flex;align-items:center;gap:4px}
.yt-ch .vb{width:14px;height:14px}
.yt-stat{font-size:13px;color:#AAA}
.yt-stat i{font-style:normal}
/* Reddit — dark-first */
.mock-rd{--m-bg:#1A1A1B;--m-ink:#fff;--m-mut:#818384;--m-body:#A8AAAB;--m-soft:#272729;
  background:var(--m-bg);color:#D7DADC}
.mock-rd.m-light{--m-bg:#fff;--m-ink:#1A1A1B;--m-mut:#7c7c7c;--m-body:#4A4E52;--m-soft:#F0F2F4;
  color:#1A1A1B}
.rd-h{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--m-mut);margin-bottom:9px;flex-wrap:wrap}
.rd-ava{width:20px;height:20px;border-radius:50%;background:#FF4500;display:flex;align-items:center;justify-content:center;color:#fff;font-size:9px;font-weight:700;font-family:var(--mono)}
.rd-r{color:var(--m-ink);font-weight:600}
.rd-flair{margin-left:auto;background:rgba(255,69,0,.15);color:#FF6B3D;font-size:11px;font-weight:600;border-radius:999px;padding:2px 9px;overflow-wrap:anywhere}
.rd-title{font-size:17px;font-weight:500;line-height:1.3;color:var(--m-ink);margin-bottom:8px}
.rd-body{font-size:13px;line-height:19px;color:var(--m-body);margin-bottom:4px}
.rd-tldr{font-size:12px;color:var(--m-body);background:var(--m-soft);border-left:2px solid #FF4500;padding:8px 12px;border-radius:0 6px 6px 0;margin:8px 0 2px}
.rd-bar{display:flex;align-items:center;gap:6px;margin-top:9px;flex-wrap:wrap}
.rd-vote{display:flex;align-items:center;gap:9px;background:var(--m-soft);border-radius:999px;padding:5px 12px}
.rd-vote .ic{width:18px;height:18px;color:var(--m-mut);transition:color .2s}
.rd-vote .up:hover{color:#FF4500}.rd-vote .dn:hover{color:#7193FF}
.rd-vote b{font-size:12px;font-weight:700}
.rd-btn{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:700;color:var(--m-mut);padding:6px 10px;border-radius:999px}
.rd-btn:hover{background:var(--m-soft)}
.rd-btn .ic{width:18px;height:18px}
.rd-btn i{font-style:normal}

/* Newsletter (email client) — LIGHT-first: the client shell reads as the
   inbox most readers actually use; the paper inside was always white. */
.mock-nl{--m-bg:#F6F8FC;--m-ink:#1F1F1F;--m-mut:#5E5E5E;--m-line:rgba(0,0,0,.08);
  background:var(--m-bg);color:var(--m-ink);padding:0}
.mock-nl.m-dark{--m-bg:#1B1B1F;--m-ink:#fff;--m-mut:#9a9a9a;--m-line:rgba(255,255,255,.06);
  color:#E6E6E6}
.nl-client{padding:10px 14px;border-bottom:1px solid var(--m-line);display:flex;align-items:center;gap:10px;font-size:13px}
.nl-client .ava{width:34px;height:34px}
.nl-client .ic{width:18px;height:18px;color:var(--m-mut)}
.nl-from b{font-weight:600}
.nl-from span{color:var(--m-mut)}
.nl-client .nl-more{margin-left:auto;color:var(--m-mut)}
.nl-subj{padding:13px 16px 4px;font-size:17px;font-weight:700;color:var(--m-ink)}
.nl-prev{padding:0 16px 13px;font-size:13px;color:var(--m-mut);font-style:italic}
.nl-paper{margin:0 12px 14px;background:#fff;color:#1a1a1a;border-radius:8px;overflow:hidden}
.nl-logo{padding:22px 24px 4px;display:flex;align-items:center;gap:8px;font-family:var(--mono);font-size:12px;letter-spacing:.3em;color:#1a1a1a}
.nl-h{padding:8px 24px 14px;font-family:var(--serif);font-size:25px;line-height:1.18;color:#111}
.nl-p{padding:0 24px 12px;font-size:14px;line-height:22px;color:#333}
/* .nl-cta was deliberately deleted, not emitted: a CTA's label and target
   are CONTENT the asset didn't generate — rendering one would put invented
   copy on an approval surface, unlike the illustrative counts. */
.nl-foot{padding:14px 24px;border-top:1px solid #eee;font-size:11px;color:#999;line-height:1.6}
.nl-foot u{cursor:pointer}

/* Quote graphic */
.mock-q{background:#13110D;padding:0}
.q-tile{position:relative;aspect-ratio:1/1;display:flex;flex-direction:column;justify-content:center;padding:32px;background:radial-gradient(120% 120% at 30% 18%,#1d1810,#0c0a07)}
.q-mark{font-family:var(--serif);font-size:62px;line-height:.4;color:var(--gold)}
.q-txt{font-family:var(--serif);font-style:italic;font-size:clamp(19px,4.6vw,27px);line-height:1.3;color:#F0EBDD;margin:14px 0 18px}
.q-attr{font-family:var(--mono);font-size:9px;letter-spacing:.22em;color:var(--muted)}
.q-logo{position:absolute;bottom:18px;right:20px;display:flex;align-items:center;gap:6px;font-family:var(--mono);font-size:9px;letter-spacing:.24em;color:var(--gold)}
/* ===== Showcase-only mocks (example board) =====
   Blog post, landing page, case study and campaign plan. These asset types
   exist in the registry but no live board piece maps to them, so they appear
   only on the pre-built example board. Themed from the page vars like every
   other mock — no hard-coded palette. */
/* These four lay out their own padding per section, so drop .mock's — the two
   would otherwise compound into a visibly inset card. Same override .mock-x
   makes, for the same reason. */
.mock-blog,.mock-lp,.mock-cs,.mock-cmp{padding:0}
.art-head{padding:20px 22px 12px;border-bottom:1px solid var(--line)}
.art-title{font-family:var(--serif);font-size:26px;line-height:1.16;color:var(--text)}
.art-dek{margin-top:8px;font-size:14px;line-height:21px;color:var(--muted)}
.art-body{padding:16px 22px 18px;font-size:14px;line-height:23px;color:var(--text)}
.art-claims{margin:0 22px 20px;padding:14px 16px;border:1px solid var(--line);border-radius:8px;background:var(--gold-soft)}
.art-ck{font-family:var(--mono);font-size:10px;letter-spacing:.28em;color:var(--gold);margin-bottom:8px}
.art-claims ul{margin:0;padding-left:18px}
.art-claims li{font-size:13px;line-height:20px;color:var(--text);margin-bottom:4px}

.lp-hero{padding:34px 22px 28px;text-align:center;background:radial-gradient(120% 120% at 50% 0%,rgba(125,232,224,.10),transparent 70%);border-bottom:1px solid var(--line)}
.lp-h1{font-family:var(--serif);font-size:30px;line-height:1.12;color:var(--text)}
.lp-sub{margin-top:10px;font-size:14px;line-height:21px;color:var(--muted)}
.lp-cta{margin-top:18px;display:inline-block;background:linear-gradient(180deg,var(--gold-hi),#C6913A);color:#1A1305;font-weight:600;font-size:14px;border-radius:999px;padding:11px 26px}
.lp-body{padding:18px 22px;font-size:14px;line-height:23px;color:var(--text)}

.cs-head{padding:20px 22px 14px;font-family:var(--serif);font-size:24px;line-height:1.18;color:var(--text);border-bottom:1px solid var(--line)}
.cs-meta{padding:11px 22px;font-size:13px;line-height:20px;color:var(--text);border-bottom:1px solid var(--line)}
.cs-meta b{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.26em;color:var(--faint);margin-bottom:4px;font-weight:400}
.cs-res{margin:14px 22px 8px;padding-left:18px}
.cs-res li{font-size:14px;line-height:21px;color:var(--text);margin-bottom:7px}
.cs-unv{margin:6px 22px 18px;padding:9px 12px;border:1px dashed var(--line2);border-radius:6px;font-family:var(--mono);font-size:11px;letter-spacing:.06em;color:var(--muted)}

.cmp-obj{padding:20px 22px 16px;font-size:15px;line-height:23px;color:var(--text);border-bottom:1px solid var(--line)}
.cmp-sec{padding:16px 22px 6px;font-family:var(--mono);font-size:10px;letter-spacing:.28em;color:var(--gold)}
.cmp-list{margin:0 22px 10px;padding-left:18px}
.cmp-list li{font-size:13px;line-height:20px;color:var(--text);margin-bottom:5px}
.cmp-list:last-child{margin-bottom:20px}
/* Markdown headings inside a long-form body (see mdBody in studio-editor.js).
   Without this the reader saw a literal "## " prefix. */
.art-h{margin:18px 0 8px;font-family:var(--serif);font-size:19px;line-height:1.25;color:var(--text)}
.art-body .art-h:first-child,.lp-body .art-h:first-child{margin-top:0}

/* ===== Touch targets moved with their mock =====
   Both scoped to mock classes (igc's carousel nav, Pinterest's hover-reveal
   Save button) — split out of studio.css's own M2/hover:none blocks, which
   are otherwise app-chrome (icon-btn/tab/chip/seg/brand/etc, still there).
   Same (pointer:coarse) OR narrow-viewport / (hover:none) OR narrow-viewport
   gates as studio.css uses throughout, for the same reason: a resized
   desktop browser still reports pointer:fine, so a coarse-only or
   hover-only query is untestable in any headless harness. */
@media (pointer:coarse),(max-width:720px){
  /* 28px arrows over a square image, and the only way to change slide. */
  .ig-nav{width:44px;height:44px}
}
@media (hover:none),(max-width:720px){
  .ig-img .ig-nav{opacity:1}
  .mock-pin .pin-save{opacity:1;transform:none}
}

/* ===== Long tokens must be able to break =====
   Exactly one wrap guard (.mtxt, in the shared base above) originally
   covered 34 classes that render model output, so a pasted URL or an
   unbroken token pushed the card — and on a phone the whole board —
   sideways.

   DERIVED, not eyeballed: check-asset-invariants.mjs reads studio-editor.js
   for every class receiving an escaped interpolation and fails the build if
   one is missing here. overflow-wrap:anywhere is safe to apply ungated — it
   changes nothing for a string that already fits. */
.art-body,.art-dek,.art-h,.art-title,.cmp-obj,.cs-head,.cs-unv,.hdl,.ig-st,
.ig-un,.igq-top,.li-link-t,.li-name,.lp-body,.lp-h1,.lp-sub,.nl-foot,.nl-h,
.nl-p,.nl-prev,.nl-subj,.pin-d,.pin-q,.pin-t,.po,.q-txt,.rd-body,.rd-r,
.rd-title,.rd-tldr,.st-q,.vf-cap,.vf-un,.yt-ch,.yt-title{overflow-wrap:anywhere}
