/* Content calendar review app. Palette and type carried over from the creative boards
   so the two surfaces read as one system. Three self hosted families, no remote asset.
   Brand and platform artwork is held in logos/ beside this file and loaded from there.

   This file is not bundled. Vite copies public/ verbatim and never parses it, which is
   deliberate: it keeps the relative url() forms below resolving, keeps the filename
   unhashed so eval C12 reads the file that was actually written, and guarantees no
   minifier rewrites the pillar token comment. Cache busting is a query the prerender
   appends to the href, which carries no scheme and so is invisible to C9's URL scan. */

@font-face{font-family:"Instrument Sans";src:url("fonts/InstrumentSans-Variable.woff2") format("woff2");font-weight:400 700;font-style:normal;font-display:swap}
@font-face{font-family:"Geist";src:url("fonts/Geist-Variable.woff2") format("woff2");font-weight:100 900;font-style:normal;font-display:swap}
@font-face{font-family:"Geist Mono";src:url("fonts/GeistMono-Variable.woff2") format("woff2");font-weight:100 900;font-style:normal;font-display:swap}

:root{
  --paper:#FAFAF8; --ink:#16161A; --sec:#45454A; --mut:#6C6C68;
  --hair:#D4D4CE; --hair2:#E7E7E1; --fill:#FFFFFF; --edge:#16161A;
  --mark:#A82F26; --calm:#2C5F7C;

  /* Pillar accents. Four widely separated hues, every one measured at 5.5 to 1 or
     better against the paper and against the white card, so the same value works as
     a border, as a solid block, and as paper colored text sitting on that block.
     Smallest pairwise separation between the four is 71.8 in CIE76 deltaE. */
  --p-product:#0A50F5;
  --p-routine:#C10588;
  --p-education:#007627;
  --p-organic:#9B5300;
  /* A pillar this file has never seen takes a neutral rather than borrowing one of
     the four. The old code fell through to organic, which drew an unknown pillar in
     a real pillar's color and said nothing about it. */
  --p-unknown:#6C6C68;

  --brand:#16161A;
  --head:"Instrument Sans",system-ui,sans-serif;
  --body:"Geist",system-ui,sans-serif;
  --mono:"Geist Mono",ui-monospace,monospace;

  --card-h:214px;      /* one card, thumbnail included */
  --stack-step:6px;    /* how far each card behind the top one sits down the page */
  --panel-w:min(420px,34vw);
}
body[data-brand="alphamale"]{--brand:#A82F26}
body[data-brand="prodigy-life"]{--brand:#2C5F7C}

*{box-sizing:border-box}
body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--body);
  font-size:16px;line-height:1.55;-webkit-font-smoothing:antialiased;overflow-x:hidden}

/* Form controls do not inherit font-family. C9 requires the first computed family on
   every text bearing element under body to be one of the three shipped faces, so an
   unstyled textarea in the comment box would fail the check on a panel nobody thought
   to look at. */
input,textarea,select,button{font-family:var(--body)}

.chip,.vbtn,.sortbtn,.rowbtn,.close,.reset,.tbtn,.pin,.regionbox{cursor:pointer}

.mono{font-family:var(--mono);font-variant-numeric:tabular-nums}
.sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}

a{color:var(--ink)}
:focus-visible{outline:3px solid var(--brand);outline-offset:2px}
img{max-width:100%}

/* ---------- page shell ----------
   Two tracks. The thread panel opens by growing the second one, so the artifact
   being discussed shrinks and stays visible rather than being covered. */
.shell{display:grid;grid-template-columns:minmax(0,1fr) 0;transition:grid-template-columns .24s ease}
.shell.panel-open{grid-template-columns:minmax(0,1fr) var(--panel-w)}
.main{min-width:0}
.wrap{max-width:1760px;margin:0 auto;padding:0 30px}

#topbar{border-bottom:2px solid var(--brand);background:var(--paper);
  position:sticky;top:0;z-index:15}
#topbar .wrap{display:flex;align-items:center;gap:18px;height:64px}

/* brand identity, carried by the real logo file */
.brandlock{display:flex;align-items:center;gap:12px;min-width:0}
.brandlogo{display:block;flex:0 0 auto;width:auto}
.brandlogo.am{height:22px}
.brandname{font-family:var(--head);font-weight:700;font-size:18px;letter-spacing:-.015em;
  color:var(--brand);white-space:nowrap;line-height:1.2}
.brandsub{font-family:var(--body);font-weight:400;font-size:14px;color:var(--mut);
  padding-left:12px;border-left:1px solid var(--hair);line-height:1.2;white-space:nowrap}
.top-spacer{flex:1}

/* switch between the two brand calendars */
.brandswitch{display:flex;align-items:stretch;border:1px solid var(--hair);flex:0 0 auto}
.bsw{display:flex;align-items:center;gap:8px;font-family:var(--body);font-weight:500;
  font-size:14px;color:var(--sec);text-decoration:none;padding:7px 13px;line-height:1.2;
  border-left:1px solid var(--hair);white-space:nowrap}
.bsw:first-child{border-left:0}
.bsw[aria-current="page"]{color:var(--ink);font-weight:600;background:var(--hair2)}
.bmk{flex:0 0 auto;display:block;width:20px;height:20px;background-repeat:no-repeat}
.bmk-am{background-image:url("logos/alphamale-mark.png");background-size:20px 20.6px;
  background-position:center}
/* Prodigy Life carries no mark file. The one that used to sit here was the Prodigy
   Media agency mark, which belongs to a different company, and it is not shipped to a
   hosted URL. The brand is set in type until a real file arrives. */
.bmk-pl{width:auto;font-family:var(--head);font-weight:700;font-size:13px;
  letter-spacing:-.01em;color:var(--calm);line-height:20px}

/* ---------- view switch and the legend beside it ---------- */
.switchbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:30px 0 20px}
.vbtn{font-family:var(--body);font-weight:500;font-size:14px;color:var(--sec);background:var(--paper);
  border:1px solid var(--hair);padding:8px 15px;line-height:1}
.vbtn[aria-pressed="true"]{background:var(--ink);border-color:var(--ink);color:var(--paper);font-weight:600}
.switchbar .count{font-family:var(--mono);font-size:13px;color:var(--mut);margin-left:4px}
.switchbar .spacer{flex:1}

.tbtn{font-family:var(--body);font-weight:500;font-size:14px;color:var(--sec);
  background:var(--paper);border:1px solid var(--hair);padding:8px 13px;line-height:1;
  display:inline-flex;align-items:center;gap:8px}
.tbtn[aria-pressed="true"]{background:var(--calm);border-color:var(--calm);color:var(--paper);font-weight:600}
.threadcount{font-family:var(--mono);font-size:13px;color:var(--sec);white-space:nowrap}

[data-view][hidden]{display:none}

/* ---------- month view ---------- */
.monthscroll{overflow-x:auto;padding-bottom:4px}
table.month{width:100%;min-width:940px;border-collapse:collapse;table-layout:fixed}
table.month th.wk{width:74px}
table.month thead th{font-family:var(--body);font-weight:600;font-size:14px;color:var(--sec);
  text-align:left;padding:0 0 9px;border-bottom:2px solid var(--edge)}
table.month tbody th.wk{font-family:var(--mono);font-weight:500;font-size:12px;color:var(--mut);
  text-align:left;vertical-align:top;padding:13px 10px 0 0;border-top:1px solid var(--hair2)}
table.month td{vertical-align:top;padding:13px 7px 20px 0;border-top:1px solid var(--hair2);width:auto}
.dnum{display:flex;align-items:baseline;gap:6px;margin:0 0 9px;height:20px}
.dnum .d{font-family:var(--mono);font-weight:500;font-size:14px;color:var(--sec)}
.dnum .mo{font-size:12px;color:var(--mut)}
td.empty .dnum .d{color:var(--mut);font-weight:400}

/* ---------- the stack ----------
   A day carrying more than one post reads as a stack before anything is clicked: the
   top card whole, the ones behind it stepped down and scaled back. Every card stays in
   the document whatever the state, because C9 counts [data-slot-id] nodes and a
   collapsed stack that rendered only its top card would hold fewer slots than the
   payload. The cards behind are inert instead, which takes them out of the tab order
   without taking them out of the DOM. */
/* The isolation is load bearing. Without it the stack's children compete with the
   panel, the lightbox and the author gate in the root stacking context, and the post
   count badge sat above all three because it carried a higher number than any of them.
   A cell's z-indexes are its own business, so the cell becomes their ceiling. */
.stack{position:relative;isolation:isolate;transition:height .26s cubic-bezier(.2,.7,.3,1)}
.stack .stackcard{transition:transform .26s cubic-bezier(.2,.7,.3,1)}

.stack.collapsed{height:calc(var(--card-h) + (var(--n) - 1) * var(--stack-step))}
.stack.collapsed .stackcard{position:absolute;left:0;right:0;top:0;
  transform:translateY(calc(var(--i) * var(--stack-step))) scale(calc(1 - var(--i) * 0.02));
  z-index:calc(var(--n) - var(--i));transform-origin:top center}

.stack.expanded .stackcard{position:relative;transform:none}
.stack.expanded .stackcard + .stackcard{margin-top:10px}

/* Above the cards in its own cell, which top out at --n, and nothing beyond that. */
.stackmore{position:absolute;right:6px;bottom:6px;z-index:20;font-family:var(--mono);
  font-size:11px;color:var(--paper);background:var(--edge);padding:2px 6px;line-height:1.3}

/* ---------- chip ----------
   One fixed height for every chip, so a week row sits level and the rows keep the
   same rhythm down the page. The hook is the only elastic part and it clips. */
.chip{display:flex;flex-direction:column;width:100%;height:var(--card-h);overflow:hidden;
  text-align:left;background:var(--fill);border:3px solid var(--p-organic);
  padding:9px 10px 10px;font-family:var(--body);color:var(--ink)}
.chip.pl-product{border-color:var(--p-product)}
.chip.pl-routine{border-color:var(--p-routine)}
.chip.pl-education{border-color:var(--p-education)}
.chip.pl-organic{border-color:var(--p-organic)}
.chip.pl-unknown{border-color:var(--p-unknown)}
.chip.on{box-shadow:4px 4px 0 var(--edge)}

.chip-head{display:flex;align-items:center;gap:6px;margin:0 0 7px;flex:0 0 auto}
/* the pillar reads twice, as a solid block of its color and as its own name */
.chip-tag{display:block;background:var(--p-organic);color:var(--paper);
  font-family:var(--head);font-weight:600;font-size:11px;line-height:1.25;
  padding:2px 6px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chip.pl-product .chip-tag{background:var(--p-product)}
.chip.pl-routine .chip-tag{background:var(--p-routine)}
.chip.pl-education .chip-tag{background:var(--p-education)}
.chip.pl-organic .chip-tag{background:var(--p-organic)}
.chip.pl-unknown .chip-tag{background:var(--p-unknown)}
.chip-time{margin-left:auto;font-family:var(--mono);font-size:12px;color:var(--sec);
  font-variant-numeric:tabular-nums;flex:0 0 auto}

.chip-top{display:flex;align-items:center;gap:6px;margin:0 0 6px;flex:0 0 auto;height:16px}
.chip-top svg{flex:0 0 auto;display:block}
.chip-fmt{font-family:var(--head);font-weight:600;font-size:13px;line-height:1.2;color:var(--ink)}
.chip-prod{display:block;flex:0 0 auto;font-size:12.5px;line-height:1.3;color:var(--sec);
  margin:0 0 5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* the hook sits one level in, because a direct flex child is blockified and a
   blockified box cannot carry the line clamp */
.chip-body{flex:1 1 auto;min-height:0;overflow:hidden}
.chip-hook{font-size:13px;line-height:1.38;color:var(--ink);
  display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.chip.has-flag .chip-hook{-webkit-line-clamp:2}
.chip.has-art .chip-hook{-webkit-line-clamp:2}
.chip.has-art.has-flag .chip-hook{-webkit-line-clamp:1}
.chip-flag{flex:0 0 auto;align-self:flex-start;margin-top:auto;max-width:100%;
  display:flex;align-items:center;gap:5px;font-size:11.5px;line-height:1.2;color:var(--mark);
  border:1px dashed var(--mark);padding:3px 6px}
.chip-flag .flagname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---------- artwork on the card ---------- */
.chip-art{flex:0 0 auto;margin:0 0 6px;height:52px;overflow:hidden;position:relative;
  background:var(--hair2)}
.chip-art .thumb{height:52px;width:100%;aspect-ratio:auto}
.chip-art img{width:100%;height:52px;object-fit:cover;display:block}
.chip-art .framecount{position:absolute;right:4px;bottom:4px;font-family:var(--mono);
  font-size:10px;line-height:1.3;color:var(--paper);background:rgba(22,22,26,.78);padding:1px 5px}
.chip-noart{flex:0 0 auto;margin:0 0 6px;height:52px;border:1px dashed var(--hair);
  display:flex;align-items:center;justify-content:center;font-size:11.5px;color:var(--mut);
  text-align:center;padding:0 6px;line-height:1.25}

/* ---------- platform marks ----------
   Every mark carries a text label beside it. The TikTok mark is the real logo file.
   The Instagram and Facebook marks are simplified in house glyphs drawn as inline
   SVG, monochrome so they take the color of the text they sit with. */
.pmk{flex:0 0 auto;display:block;width:13px;height:13px}
.pmk-file{background-repeat:no-repeat;background-position:center;background-size:contain}
.pmk-tiktok{width:12px;height:13px;background-image:url("logos/tiktok.svg")}

/* ---------- legend, now in the topbar row ----------
   The words travel with the swatches at every width. A pillar is never signalled by
   color alone, so the names cannot be dropped to save space and the legend wraps to
   its own strip instead. That rule decides the breakpoint, not a layout preference. */
.legend{display:flex;flex-wrap:wrap;gap:6px 14px;margin:0;padding:0;list-style:none}
.legend li{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--sec);white-space:nowrap}
.legend .sw{width:22px;height:14px;background:var(--p-organic);flex:0 0 auto;display:block}
.legend .sw.pl-product{background:var(--p-product)}
.legend .sw.pl-routine{background:var(--p-routine)}
.legend .sw.pl-education{background:var(--p-education)}
.legend .sw.pl-organic{background:var(--p-organic)}

/* ---------- table view ---------- */
.filters{display:flex;flex-wrap:wrap;gap:14px 20px;align-items:flex-end;margin:0 0 16px}
.field{display:flex;flex-direction:column;gap:5px}
.field label{font-size:13px;color:var(--mut);line-height:1.2}
.field select{font-size:14px;color:var(--ink);background:var(--fill);
  border:1px solid var(--hair);padding:7px 9px;min-width:168px;line-height:1.3}
.reset{align-self:flex-end;font-size:14px;color:var(--ink);
  background:var(--paper);border:1px solid var(--hair);padding:8px 13px;line-height:1.3}

.gridscroll{overflow-x:auto;border-top:2px solid var(--edge)}
table.grid{width:100%;min-width:1140px;border-collapse:collapse;font-size:14px}
table.grid caption{caption-side:bottom;text-align:left;font-size:15px;line-height:1.55;
  color:var(--sec);padding:16px 0 0;max-width:82ch}
table.grid th{text-align:left;font-weight:600;color:var(--sec);padding:0;
  border-bottom:1px solid var(--hair);background:var(--paper)}
.sortbtn{display:flex;align-items:center;gap:6px;width:100%;background:none;border:0;
  font-weight:600;font-size:13.5px;color:var(--sec);
  padding:11px 12px 10px 0;text-align:left;line-height:1.3}
th[aria-sort] .sortbtn{color:var(--ink)}
.sortmark{font-size:10px;color:var(--mut)}
th.plain{font-size:13.5px;padding:11px 12px 10px 0;font-weight:600}
table.grid td{padding:11px 12px 11px 0;border-bottom:1px solid var(--hair2);
  vertical-align:top;line-height:1.42;color:var(--sec)}
table.grid tr.on td{background:#F2F2EC}
td.c-date{white-space:nowrap}
td.c-time{font-family:var(--mono);font-size:13px;white-space:nowrap;font-variant-numeric:tabular-nums}
td.c-hook{color:var(--ink);min-width:280px}
td.c-fmt{white-space:nowrap;color:var(--ink)}
td.c-pillar{white-space:nowrap}
.pdot{display:inline-block;width:11px;height:11px;background:var(--p-organic);
  margin-right:8px;vertical-align:-1px}
.pdot.pl-product{background:var(--p-product)}
.pdot.pl-routine{background:var(--p-routine)}
.pdot.pl-education{background:var(--p-education)}
.pdot.pl-organic{background:var(--p-organic)}
.pdot.pl-unknown{background:var(--p-unknown)}
.rowbtn{font-size:14px;font-weight:500;color:var(--ink);background:none;
  border:0;padding:0;text-align:left;line-height:1.42;text-decoration:underline;
  text-underline-offset:3px;text-decoration-color:var(--hair);white-space:nowrap}
.chan{display:flex;flex-wrap:wrap;gap:5px}
.chan span.ch{display:inline-flex;align-items:center;gap:6px;font-size:12px;line-height:1.3;
  border:1px solid var(--hair);padding:3px 7px;color:var(--sec);white-space:nowrap}
.chan span.ch.wait{border:1px dashed var(--mark);color:var(--mark)}
.empty-row td{padding:26px 0;color:var(--mut);font-size:15px}

/* ---------- options gallery ---------- */
.optgroup{margin:0 0 34px}
.optgroup h3{font-family:var(--head);font-weight:700;font-size:17px;margin:0 0 4px;color:var(--ink)}
.optgroup .optnote{font-size:14px;color:var(--sec);margin:0 0 14px;max-width:82ch}
.optrow{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:14px}
.optcard{border:2px solid var(--edge);background:var(--fill);padding:8px;position:relative}
.optcard .optimg{display:block;width:100%;background:var(--hair2)}
.optcard .optimg img{height:auto}
.optcard .optlabel{font-size:12.5px;color:var(--sec);margin:8px 0 0;line-height:1.3}
.optcard .verdicts{display:flex;flex-wrap:wrap;gap:4px;margin:6px 0 0}
.vd{font-family:var(--mono);font-size:10.5px;line-height:1.3;padding:1px 5px;border:1px solid var(--hair);color:var(--sec)}
.vd.fail{border-color:var(--mark);color:var(--mark)}
.vd.pass{border-color:var(--p-education);color:var(--p-education)}

/* ---------- detail panel ---------- */
#scrim{position:fixed;inset:0;background:rgba(20,20,18,.2);opacity:0;visibility:hidden;
  transition:opacity .2s ease,visibility 0s linear .2s;z-index:19}
#scrim.on{opacity:1;visibility:visible;transition:opacity .2s ease}
#panel{position:fixed;top:0;right:0;width:470px;max-width:100%;height:100vh;background:var(--paper);
  border-left:2px solid var(--edge);padding:28px 30px 48px;overflow-y:auto;z-index:20;
  transform:translateX(100%);visibility:hidden;
  transition:transform .22s ease,visibility 0s linear .22s}
#panel.open{transform:none;visibility:visible;transition:transform .22s ease}
#panel h2{font-family:var(--head);font-weight:700;font-size:23px;line-height:1.18;
  letter-spacing:-.018em;margin:0 0 10px;padding-right:74px;color:var(--ink)}
.close{position:absolute;top:22px;right:26px;background:var(--paper);border:1px solid var(--hair);
  font-size:14px;padding:6px 12px;color:var(--ink);line-height:1.2}
.panel-when{font-family:var(--mono);font-size:13px;color:var(--sec);margin:0 0 20px;
  font-variant-numeric:tabular-nums}
.block{border:2px solid var(--edge);padding:14px 16px 15px;margin:0 0 14px;background:var(--fill)}
.block.tint{border-color:var(--brand)}
.block h3{font-family:var(--head);font-weight:700;font-size:15px;margin:0 0 10px;color:var(--ink)}
dl.kv{display:grid;grid-template-columns:auto 1fr;gap:8px 18px;margin:0;font-size:14px}
dl.kv dt{color:var(--mut);line-height:1.4}
dl.kv dd{margin:0;color:var(--ink);line-height:1.4;text-align:right}
dl.kv dd .mono{font-size:13px}
.pilltag{display:inline-block;background:var(--p-organic);color:var(--paper);
  font-weight:600;font-size:13px;line-height:1.3;padding:2px 8px}
.pilltag.pl-product{background:var(--p-product)}
.pilltag.pl-routine{background:var(--p-routine)}
.pilltag.pl-education{background:var(--p-education)}
.pilltag.pl-organic{background:var(--p-organic)}
.pilltag.pl-unknown{background:var(--p-unknown)}
.chanlist{list-style:none;margin:0;padding:0;font-size:14px}
.chanlist li{display:flex;align-items:center;gap:10px;padding:8px 0;border-top:1px solid var(--hair2)}
.chanlist li:first-child{border-top:0;padding-top:0}
.chanlist .who{display:flex;align-items:center;gap:8px;color:var(--ink);min-width:104px}
.chanlist .handle{font-family:var(--mono);font-size:12.5px;color:var(--mut);flex:1;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chanlist .state{font-size:13px;color:var(--sec);text-align:right;white-space:nowrap}
.chanlist .state.wait{color:var(--mark)}
.panel-note{font-size:14px;line-height:1.55;color:var(--sec);margin:0 0 8px}
.panel-note.tight{margin-bottom:0}
.pathline{font-family:var(--mono);font-size:12.5px;line-height:1.5;color:var(--sec);
  margin:0;word-break:break-all}

/* The trace block. Everything a reviewer does not need is behind this one closed
   disclosure, identifiers included. C12 runs its identifier scan on the text outside
   a details element, because a closed disclosure is not the surface. */
details.trace{border:2px solid var(--hair);background:var(--fill);margin:0 0 14px}
details.trace > summary{font-family:var(--head);font-weight:700;font-size:14px;color:var(--sec);
  padding:11px 14px;cursor:pointer;list-style:none;display:flex;align-items:center;gap:8px}
details.trace > summary::-webkit-details-marker{display:none}
details.trace > summary::after{content:"+";margin-left:auto;font-family:var(--mono);color:var(--mut)}
details.trace[open] > summary::after{content:"\2212"}
details.trace .tracebody{padding:0 14px 14px}

/* ---------- frame strip and lightbox ---------- */
.framestrip{display:flex;flex-wrap:wrap;gap:8px;margin:0}
.framestrip button{padding:0;border:2px solid var(--hair);background:var(--fill);display:block;cursor:pointer}
.framestrip .thumb{width:76px;height:76px;aspect-ratio:auto}
.framestrip img{display:block;width:76px;height:76px;object-fit:cover}
.lightbox{position:fixed;inset:0;background:rgba(16,16,18,.88);z-index:40;
  display:flex;align-items:center;justify-content:center;padding:32px}
/* The post drives the width and the viewport drives the height. The media box is the
   only elastic part, so the chrome around it always stays on screen and the image
   shrinks to whatever is left rather than pushing the caption off the bottom. The
   previous rule set height:auto on the image, which made a 1080 by 1350 frame render
   about 1475px tall inside a 92vh box and scroll. */
.lightbox .lbinner{display:flex;gap:20px;align-items:flex-start;
  max-width:min(96vw,1180px);max-height:92vh}
/* The column takes its width from the frame's own aspect ratio, so at whatever height
   the image ends up the post is exactly that image wide. Sizing the column to a fixed
   468px instead left a height capped portrait frame sitting in black side gutters.
   --arn is the frame's width over its height, set inline; --post-chrome is everything
   the platform draws above and below the picture. */
.lightbox .lbpost{flex:0 0 auto;display:flex;flex-direction:column;
  max-height:92vh;overflow-y:auto;min-height:0;
  width:min(62vw, 468px, calc((92vh - var(--post-chrome)) * var(--arn)))}
.lightbox .lbplain{flex:0 0 auto;display:flex;flex-direction:column;
  max-height:92vh;overflow-y:auto;min-height:0;width:min(62vw,468px)}

.lbpost.pf-facebook{--post-chrome:318px}
.lbpost.pf-instagram{--post-chrome:330px}
/* The allowance is what sizes the column, so a collapsed post lands inside the
   viewport. An expanded caption lengthens the post past it and the column scrolls,
   which is what a feed does. */
.lightbox .lbplain{background:var(--paper);padding:12px}
.lightbox .lbside{flex:1 1 auto;min-width:0;max-width:340px;background:var(--paper);
  padding:14px 16px;max-height:92vh;overflow-y:auto}
.lightbox .lbmeta{font-size:13px;color:var(--sec);margin:12px 0 0;line-height:1.5}

.postswitch{display:flex;gap:0;flex:0 0 auto;margin:0 0 8px}
.pbtn{font-family:var(--head);font-size:13px;padding:5px 13px;background:var(--paper);
  border:1px solid var(--edge);color:var(--ink);cursor:pointer}
.pbtn + .pbtn{border-left:0}
.pbtn[aria-selected="true"]{background:var(--edge);color:var(--paper)}

/* ---------- post chrome ----------
   Two platforms, drawn plainly. This is a layout study of where the caption sits and
   where it cuts, not a pixel copy of either product's interface. */
.post{background:#FFFFFF;color:#050505;display:flex;flex-direction:column;
  flex:0 0 auto;border:1px solid var(--hair)}
.post-head{flex:0 0 auto;display:flex;align-items:center;gap:9px;padding:11px 13px}
.avatar{width:38px;height:38px;flex:0 0 auto;border-radius:50%;background:var(--edge);
  color:var(--paper);font-family:var(--head);font-size:14px;font-weight:600;
  display:flex;align-items:center;justify-content:center;letter-spacing:.02em;
  overflow:hidden}
.avatar-img{background:#000}
.avatar-img img{width:26px;height:auto;display:block}
.post-who{display:flex;flex-direction:column;font-size:14px;line-height:1.3;min-width:0}
.post-who b{font-weight:600}
.post-sub{font-size:12px;color:#65676B}
/* The image is capped against the viewport rather than against its parent, and the
   media box then wraps whatever the image became.

   Two earlier forms both failed. height:100% resolved against an indefinite flex
   height, fell back to the intrinsic ratio, and painted a 583px image through a 456px
   box and over the reaction rows. max-height:100% did not resolve either, so the box
   cropped the artwork instead, which is worse than overflowing it in a tool whose job
   is reviewing that artwork.

   Capping in vh sidesteps the percentage entirely. The allowance covers the tallest
   chrome either platform draws around the image, so the whole post lands inside the
   viewport with nothing cropped and nothing scrolled. The media box takes its height
   from the image, so a pin's percentages address the picture rather than a letterbox
   around it. */
.post-media{position:relative;flex:0 0 auto;display:flex;justify-content:center;
  background:#000;line-height:0}
.post-media img{display:block;width:100%;height:auto;
  max-height:calc(92vh - var(--post-chrome))}
.post-cap{margin:0;font-size:14px;line-height:1.45}
/* An open caption grows to its full length, the way it does in a real feed. The post
   gets taller and the column scrolls. An earlier version held the caption box at one
   height and scrolled inside it, which kept the chrome still but is not what either
   platform does. */
.cap-text{display:block;white-space:pre-wrap;overflow-wrap:anywhere}
.cap-text.clamped{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden}
.cap-more{background:none;border:0;padding:0;margin:2px 0 0;font-size:14px;
  color:#65676B;cursor:pointer;font-weight:600}
.post-ig .cap-more{color:#8E8E8E;font-weight:400}
.post-cap-fb{flex:0 0 auto;padding:0 13px 11px}
/* No scroller of its own. The column is the one thing that scrolls, so an expanded
   caption lengthens the post and the reader scrolls the post, the way a feed works. */
.post-body{flex:0 0 auto;padding:2px 13px 13px}
.post-likes{margin:0 0 5px;font-size:14px;font-weight:600}
.post-more{margin:5px 0 0;font-size:14px;color:#8E8E8E}
.post-counts{flex:0 0 auto;display:flex;gap:14px;padding:9px 13px;
  font-size:13px;color:#65676B;border-bottom:1px solid #E4E6EB}
.post-acts{flex:0 0 auto;display:flex;padding:3px 8px}
.act{flex:1 1 0;display:flex;align-items:center;justify-content:center;gap:7px;
  background:none;border:0;padding:8px 0;font-size:14px;font-weight:600;
  color:#65676B;cursor:pointer;line-height:1}
.act .ico{width:19px;height:19px;flex:0 0 auto;display:block}
.rx{width:18px;height:18px;display:block;flex:0 0 auto}
.rxset{display:flex;align-items:center;gap:0}
.rxset .rx + .rx{margin-left:-5px}
.rxset .rx:last-of-type{margin-right:6px}
.rxgap{margin-left:auto}

/* Instagram puts three controls on the left and the save control hard right, with no
   labels beside any of them. */
.post-ig .post-acts{padding:8px 11px 4px;justify-content:space-between;align-items:center}
.post-ig .act-left{display:flex;align-items:center;gap:14px}
.post-ig .act{flex:0 0 auto;padding:0;color:#050505}
.post-ig .act .ico{width:24px;height:24px}
.lbclose{position:fixed;top:20px;right:24px;background:var(--paper);border:1px solid var(--hair);
  font-size:14px;padding:7px 13px;color:var(--ink);cursor:pointer;z-index:41}

/* ---------- annotation layer ----------
   A pin and a region are both stored as percentages of the element they belong to, so
   nothing here reads a pixel at render time. That is what lets a pin survive a resize,
   a reflow, a stack expanding and the thread panel opening without any recomputation. */
.anno-host{position:relative}
.pinlayer{position:absolute;inset:0;pointer-events:none;z-index:12}
.pin{position:absolute;transform:translate(-50%,-50%);width:22px;height:22px;
  border-radius:50%;background:var(--calm);color:var(--paper);border:2px solid var(--paper);
  font-family:var(--mono);font-size:11px;line-height:1;display:flex;align-items:center;
  justify-content:center;pointer-events:auto;padding:0;box-shadow:0 1px 3px rgba(0,0,0,.35)}
.pin.resolved{background:var(--mut)}
.pin.stale{background:var(--mark)}
.pin.active{outline:3px solid var(--ink);outline-offset:1px}
.regionbox{position:absolute;border:2px solid var(--calm);background:rgba(44,95,124,.14);
  pointer-events:auto;padding:0}
.regionbox.stale{border-color:var(--mark);background:rgba(168,47,38,.14)}
.regiondraft{position:absolute;border:2px dashed var(--calm);background:rgba(44,95,124,.1);
  pointer-events:none}

/* With the tool armed, anything that can take a note says so. */
body.armed [data-annotate-target]{outline:1px dashed var(--calm);outline-offset:2px;cursor:crosshair}
body.armed .chip{cursor:crosshair}

/* While the tool is armed a click means one thing only, which is placing a note.
   Every control inside an annotatable element stops taking the pointer, so clicking a
   card to annotate it no longer also opens its panel and clicking a thumbnail no
   longer also opens the lightbox underneath the pin being placed. The delegated
   handler reads the event target's nearest annotate host, so removing the inner
   control from the hit test hands it the host instead of breaking it.
   Existing pins and regions stay live, because clicking one opens its thread. */
body.armed [data-annotate-target] button:not(.pin),
body.armed [data-annotate-target] a,
body.armed [data-annotate-target] input,
body.armed [data-annotate-target] select,
body.armed [data-annotate-target] textarea{pointer-events:none}
body.armed .pin,
body.armed .regionbox{pointer-events:auto}

/* ---------- image skeleton ----------
   The wrapper holds the frame's real aspect ratio before the file arrives, so the
   space is reserved and shaded rather than reserved and blank. */
.thumb{position:relative;display:block;overflow:hidden;background:var(--hair2);line-height:0}
.thumb::before{content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(90deg,var(--hair2) 0%,#F3F3EF 50%,var(--hair2) 100%);
  background-size:200% 100%;animation:sk 1.15s linear infinite}
.thumb.ready::before{display:none}
.thumb img{display:block;width:100%;height:100%;opacity:0;transition:opacity .3s ease}
.thumb.ready img{opacity:1}
@keyframes sk{from{background-position:200% 0}to{background-position:-200% 0}}
@media (prefers-reduced-motion: reduce){
  .thumb::before{animation:none}
  .thumb img{transition:none}
}

.stalebadge{display:inline-block;font-size:11.5px;line-height:1.3;color:var(--mark);
  border:1px dashed var(--mark);padding:2px 6px;margin:0 0 8px}

/* ---------- thread panel ---------- */
.threadpanel{border-left:2px solid var(--edge);background:var(--paper);overflow-y:auto;
  height:100vh;position:sticky;top:0;padding:22px 22px 40px;min-width:0}
.threadpanel h2{font-family:var(--head);font-weight:700;font-size:18px;margin:0 0 4px;color:var(--ink)}
.threadpanel .tsub{font-size:13px;color:var(--mut);margin:0 0 16px}
.thread{border:2px solid var(--hair);background:var(--fill);padding:12px;margin:0 0 12px}
.thread.active{border-color:var(--edge)}
.thread.resolved{opacity:.62}
.thread .thead{display:flex;align-items:center;gap:8px;margin:0 0 8px}
.thread .tnum{font-family:var(--mono);font-size:11px;background:var(--calm);color:var(--paper);
  width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;flex:0 0 auto}
.thread .ttarget{font-size:12px;color:var(--mut);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.comment{border-top:1px solid var(--hair2);padding:8px 0 0;margin:8px 0 0}
.comment:first-of-type{border-top:0;margin-top:0;padding-top:0}
.comment .cwho{font-size:12px;color:var(--mut);margin:0 0 3px}
.comment .cbody{font-size:14px;line-height:1.5;color:var(--ink);margin:0;white-space:pre-wrap}
.replybox{display:flex;flex-direction:column;gap:8px;margin:10px 0 0}
.replybox textarea{font-size:14px;line-height:1.5;color:var(--ink);background:var(--fill);
  border:1px solid var(--hair);padding:8px;min-height:64px;resize:vertical;width:100%}
.replybox .rowbtns{display:flex;gap:8px}
.btn{font-size:13.5px;padding:7px 12px;line-height:1.2;border:1px solid var(--hair);
  background:var(--paper);color:var(--ink);cursor:pointer}
.btn.primary{background:var(--ink);border-color:var(--ink);color:var(--paper);font-weight:600}
.btn.quiet{color:var(--sec)}
.emptythreads{font-size:14px;color:var(--mut);line-height:1.55}

/* ---------- author gate and login ---------- */
.gate{position:fixed;inset:0;background:rgba(20,20,18,.42);z-index:60;
  display:flex;align-items:center;justify-content:center;padding:24px}
.gatecard{background:var(--paper);border:2px solid var(--edge);padding:26px 28px;max-width:420px;width:100%}
.gatecard h2{font-family:var(--head);font-weight:700;font-size:20px;margin:0 0 8px}
.gatecard p{font-size:14px;color:var(--sec);line-height:1.55;margin:0 0 16px}
.gatecard input{font-size:15px;color:var(--ink);background:var(--fill);border:1px solid var(--hair);
  padding:9px 10px;width:100%;line-height:1.3;margin:0 0 14px}

/* ---------- hover, off touch ----------
   (hover:hover) and (pointer:fine) is the gate, so a sticky hover state cannot appear
   on a touch device, where hover does not exist and a stuck state reads as a bug. */
@media (hover:hover) and (pointer:fine){
  .chip:hover{box-shadow:4px 4px 0 var(--hair)}
  .stack.collapsed:hover .stackcard{
    transform:translateY(calc(var(--i) * 10px)) rotate(calc(var(--i) * 0.4deg))}
  .chip-art:hover img{filter:brightness(1.04)}
  .framestrip button:hover{border-color:var(--edge)}
  .optcard:hover{box-shadow:4px 4px 0 var(--hair)}
  .vbtn:hover{border-color:var(--edge);color:var(--ink)}
  .tbtn:hover{border-color:var(--edge);color:var(--ink)}
  .bsw:hover{color:var(--ink);background:var(--hair2)}
  .sortbtn:hover{color:var(--ink)}
  .rowbtn:hover{text-decoration-color:var(--ink)}
  .field select:hover,.reset:hover,.close:hover,.btn:hover{border-color:var(--edge)}
  .pin:hover{transform:translate(-50%,-50%) scale(1.14)}
}

@media (max-width:1180px){
  #panel{width:420px}
}
@media (max-width:1080px){
  /* No room to sit beside, so the thread panel goes back to being an overlay. A
     squeezed grid at this width is worse than covering the artifact. */
  .shell,.shell.panel-open{grid-template-columns:minmax(0,1fr)}
  .threadpanel{position:fixed;top:0;right:0;width:min(420px,100%);z-index:22}
}
@media (max-width:900px){
  .switchbar .legend{width:100%;order:9;padding-top:4px}
}
@media (max-width:820px){
  #topbar .wrap{gap:12px}
  .brandsub{display:none}
  #panel{width:100%;border-left:0}
}

/* Reduced motion is honored twice, here and in the hook that drives the expansion, so
   the guarantee holds even if the hook is ever missed. An instant change, not a
   shortened animation. */
@media (prefers-reduced-motion:reduce){
  .shell,.stack,.stack .stackcard,#panel,#scrim,.pin{transition:none}
  .stack.collapsed:hover .stackcard{
    transform:translateY(calc(var(--i) * var(--stack-step))) scale(calc(1 - var(--i) * 0.02))}
}
