@font-face{font-family:"Fraunces";src:url("fonts/fraunces.woff2") format("woff2");font-weight:400 600;font-style:normal;font-display:swap}
@font-face{font-family:"Manrope";src:url("fonts/manrope.woff2") format("woff2");font-weight:400 800;font-style:normal;font-display:swap}
*{box-sizing:border-box;margin:0}
html{background:#0A0E14;overflow-x:hidden}
:root{
  --bg:#0A0E14; --card:#11161E; --line:#1D242E; --txt:#A8B3BF;
  --bright:#F0F3F7; --dim:#5C6875; --mid:#7C8794; --cyan:#00D4D4; --gold:#E8B23A;
  --serif:Fraunces,"Cormorant Garamond","Iowan Old Style",Georgia,serif;
  --sans:Manrope,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
}
body{background:var(--bg);color:var(--txt);font:16px/1.6 var(--sans);overflow-wrap:anywhere;word-break:break-word}
.page{max-width:640px;margin:0 auto;padding:26px 18px 40px}
.mast{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px}
.studio{font-size:.66rem;font-weight:700;letter-spacing:.2em;color:var(--dim);text-transform:uppercase}
.sitelink{font-size:.72rem;color:var(--cyan);text-decoration:none}
.hubtitle{font-family:var(--serif);font-size:2.1rem;font-weight:500;letter-spacing:-.02em;color:var(--bright);margin-bottom:4px}
.hubtag{font-size:.88rem;color:var(--mid);margin-bottom:18px}
.chips{display:flex;gap:8px;overflow-x:auto;padding-bottom:12px}
.chips a{flex:none;font-size:.8rem;font-weight:600;text-decoration:none;color:var(--txt);
  background:#11161ec7;border:1px solid var(--line);border-radius:999px;padding:7px 14px}
.chips a.on{color:var(--cyan);background:#00d4d414;border-color:#00d4d43d}
/* Icon-only chips: the row of seven fits a phone without scrolling, the
   name is in title/aria-label. Same .on tint as the text chips. */
.chips a.iconchip{padding:5px;border-radius:12px;line-height:0}
.chips a.iconchip .ico{width:32px;height:32px;margin:0;border-radius:8px;vertical-align:middle;display:block}
.chips::-webkit-scrollbar{display:none}
.chips{scrollbar-width:none}
.ico{border-radius:5px;margin-right:7px;vertical-align:-3px}
.sortrow{display:flex;align-items:center;gap:14px;margin:2px 0 14px;font-size:.76rem}
.sortrow .lbl{color:var(--dim);text-transform:uppercase;letter-spacing:.1em;font-size:.65rem;font-weight:600}
.sortrow a{color:var(--mid);text-decoration:none;font-weight:600;padding:3px 2px;border-bottom:2px solid transparent}
.sortrow a.on{color:var(--cyan);border-bottom-color:var(--cyan)}
.card{position:relative;background:var(--card);border:1px solid var(--line);border-radius:10px;margin-bottom:20px;overflow:hidden;transition:border-color .15s ease}
/* Whole-card link: the title anchor is stretched over the card so the text
   block itself opens the story. Text stays selectable, and the expand button
   sits above this layer so it toggles instead of navigating. */
.cardlink::after{content:"";position:absolute;inset:0;z-index:1}
.card:focus-within{border-color:#00d4d45e}
/* Comments affordance sits above the stretched card link (z-index), so
   tapping it goes to the story's #comments anchor, not just the story.
   The JS upgrades the bare "Comments" to "Comments (n)" / "Leave a
   comment" once the API answers; the bare word is the no-API truth. */
.cmtlink{position:relative;z-index:2;color:var(--cyan);font-weight:600;text-decoration:none}
.cmtlink:hover,.cmtlink:focus-visible{text-decoration:underline}
/* Top comment: one quoted line under the byline, revealed by the JS only
   when an approved comment exists. Same z-index reasoning as .cmtlink. */
/* display:block would silently defeat the hidden attribute (display beats
   [hidden]'s UA default) -- so hidden is restated explicitly. */
.topcomment[hidden]{display:none}
.topcomment{position:relative;z-index:2;display:block;margin-top:10px;padding:8px 12px;
  border-left:2px solid #00d4d43a;background:#00d4d408;border-radius:0 8px 8px 0;
  color:var(--txt);font-size:.85rem;line-height:1.5;text-decoration:none;font-family:var(--sans)}
.topcomment b{color:var(--bright);font-weight:600}
.topcomment:hover,.topcomment:focus-visible{background:#00d4d410}
blockquote .topcomment{text-align:left;max-width:34rem;margin:10px auto 0}
blockquote .cmtlink{position:relative;z-index:2}
.card:hover{border-color:#00d4d45e}
img.art{display:block;width:100%;height:auto;max-width:100%}
.pad{padding:16px 20px 18px}
.kick{font-size:.68rem;font-weight:600;text-transform:uppercase;letter-spacing:.15em;color:var(--cyan);margin-bottom:8px}
.kick .stars{color:var(--gold);letter-spacing:.1em;margin-left:8px}
.t{font-family:var(--serif);font-size:1.35rem;font-weight:500;color:var(--bright);line-height:1.28;text-wrap:balance;margin-bottom:9px}
.t a{color:inherit;text-decoration:none}
/* Specificity note: this outranks `blockquote p` (0,1,1 vs 0,0,2). Fine
   while pull-quotes only ever sit as top-level siblings of .card/.story --
   but a pull-quote ever nested inside a card or story needs its own
   scoped selector or it will pick up this rule instead of the italic
   blockquote styling below. */
.card p,.story p{font-family:var(--serif);font-size:1rem;line-height:1.72;color:#C2CAD3}
.lede::first-letter{initial-letter:2;-webkit-initial-letter:2;color:var(--cyan);font-weight:500;margin-right:.35em}
/* flex-start + margin-left:auto on the comments link (not space-between):
   the JS may inject a .reacts tally between author and link, and
   space-between would float it to the card's dead center. */
.by{display:flex;align-items:baseline;justify-content:flex-start;gap:10px;font-size:.74rem;color:var(--dim);margin-top:12px}
.by b{color:var(--txt);font-weight:600}
.by a{color:var(--cyan);text-decoration:none}
.by .cmtlink{margin-left:auto}
.by .reacts{white-space:nowrap;color:var(--mid);letter-spacing:.02em}
/* Translation marker. Deliberately quiet -- it is a disclosure, not a
   badge -- but never dimmer than the byline it sits in, so it stays
   legible rather than becoming fine print nobody can read. */
.xl{font-style:italic;opacity:.85}
.newpill{display:inline-block;font-size:.65rem;font-weight:800;letter-spacing:.08em;color:var(--cyan);
  background:#00d4d414;border-radius:5px;padding:2px 7px;margin-left:8px;text-transform:uppercase;
  vertical-align:3px;font-family:var(--sans)}
blockquote{border:0;text-align:center;padding:24px 10px;margin:0 0 20px}
blockquote p{font-family:var(--serif);font-size:1.4rem;line-height:1.45;font-style:italic;color:#EDF1F5;text-wrap:balance}
blockquote .by{justify-content:center}
blockquote .by .cmtlink{margin-left:0}
.pager{display:flex;justify-content:space-between;align-items:center;font-size:.8rem;margin:18px 0}
.pager a{color:var(--cyan);text-decoration:none;font-weight:600}
/* Story-page Newer/Older neighbour nav -- same visual language as .pager.
   .older carries margin-left:auto so an oldest-story page (no Newer link)
   still right-aligns its single link. */
/* Prev/next: real buttons, not inline links. Each is a >=44px tap target
   (the iOS/Material minimum) and they sit side by side on a phone, where a
   baseline-aligned pair of text links was fiddly to hit. */
.storynav{display:flex;gap:10px;margin:22px 0;align-items:stretch}
.storynav a{flex:1 1 0;min-height:56px;display:flex;flex-direction:column;
  justify-content:center;gap:2px;padding:10px 14px;border:1px solid var(--line);
  border-radius:10px;background:var(--card);color:var(--bright);
  text-decoration:none;transition:border-color .15s ease}
.storynav a:hover,.storynav a:focus-visible{border-color:#00d4d45e}
.storynav .dir{color:var(--cyan);font-size:.7rem;font-weight:700;
  letter-spacing:.08em;text-transform:uppercase}
.storynav .ttl{font-size:.85rem;line-height:1.3;color:var(--txt);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.storynav .older{margin-left:auto;text-align:right;align-items:flex-end}
@media (max-width:420px){.storynav .ttl{-webkit-line-clamp:1}}
/* "Share your experience" CTA on feed pages: noticeable (cyan-tinted panel)
   but built from the same tokens as the chips' active state, so it reads
   as site chrome rather than an ad. */
.sharecta{background:#00d4d40a;border:1px solid #00d4d42e;border-radius:10px;padding:13px 16px;margin:2px 0 16px}
.sharecta p{font-size:.85rem;line-height:1.55;color:#C2CAD3}
.sharecta .btns{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.sharecta .btns a{font-size:.76rem;font-weight:700;color:var(--cyan);text-decoration:none;
  background:#00d4d414;border:1px solid #00d4d43d;border-radius:8px;padding:7px 12px}
.ctabar{position:sticky;bottom:0;display:flex;gap:10px;background:#0A0E14F2;border-top:1px solid var(--line);
  padding:12px 0 4px;margin-top:18px;backdrop-filter:blur(4px)}
.ctabar a{flex:1;text-align:center;font-weight:700;font-size:.85rem;text-decoration:none;border-radius:9px;padding:12px 10px}
.ctabar .share{border:1px solid #273140;color:#DCE3EA}
/* The official badges are fixed-aspect images and far wider than the drawn
   ones they replaced, so on a phone they squeezed the text CTA into a
   one-letter-per-line column. Badges keep their intrinsic size (both brand
   guidelines forbid distorting them); the flexible text button wraps to its
   own full-width row underneath instead. */
.ctabar{flex-wrap:wrap;justify-content:center}
/* Prominent route back to the feed for readers who landed on a story from
   search -- the accent treatment marks it as the page's primary action. */
.ctabar .allreports{border:1px solid #00d4d45e;color:var(--cyan);background:#00d4d40d}
/* App promo card: the weaseldev.com app-card treatment, compact. Badges
   live here on story pages (the sticky bar carries navigation instead). */
.appcard{display:flex;flex-wrap:wrap;gap:14px;align-items:flex-start;background:var(--card);
  border:1px solid var(--line);border-radius:12px;padding:16px 18px;margin:20px 0 4px}
.appicon{flex:none;border-radius:12px}
.appmeta{min-width:0}
.appname{color:var(--bright);font-weight:700;font-size:1rem;line-height:1.3}
.appstats{color:var(--gold);font-size:.78rem;margin-top:2px}
.apptag{font-size:.85rem;line-height:1.5;color:var(--txt);margin:6px 0 10px;
  font-family:var(--sans)}
.appbadges{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
/* Store screenshots, same imagery as weaseldev.com's app cards. flex-basis
   100% drops the row below the icon+meta line (the card is flex-wrap for
   exactly this). Fixed 9:16 crop from the top: mixed source aspects would
   otherwise give the row a ragged bottom edge. */
.appshots{flex:1 1 100%;display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:2px}
.appshots img{width:100%;height:auto;aspect-ratio:9/16;object-fit:cover;object-position:top;
  border-radius:8px;border:1px solid var(--line);display:block}
/* Only hub.js adds .clickable, and only on a phone whose store it named. */
.appcard.clickable{cursor:pointer;transition:border-color .15s ease}
.appcard.clickable:hover{border-color:#00d4d45e}
.appcard.clickable:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}
@media (max-width:420px){.appcard{padding:14px}.appicon{width:48px;height:48px}}
.ctabar .badge-gp,.ctabar .badge-as{flex:0 0 auto}
.ctabar .share{flex:1 1 100%}
@media (min-width:560px){.ctabar .share{flex:1 1 auto;min-width:150px}}
/* Store badges: a recognizable two-line "get it on the store" affordance
   built entirely from this site's own type + color tokens -- no Apple/Google
   logo artwork, which keeps the site brand-guideline-free and self-contained
   (see experience-hub CLAUDE.md badge decision note). */
/* Official vendor artwork: no border, no background, no filter -- both
   brand guidelines require the badge be shown unaltered. Only the box it
   sits in is ours. Heights are matched optically (Google's PNG carries its
   mandated transparent clear space, Apple's SVG does not). */
.badge-gp,.badge-as{display:inline-flex;align-items:center;border:0;background:none;padding:0}
.badge-gp img{height:52px;width:auto;display:block;margin:-6px}
.badge-as img{height:40px;width:auto;display:block}
.badge-gp:focus-visible,.badge-as:focus-visible{outline:2px solid var(--cyan);outline-offset:3px;border-radius:8px}
.badge-legacy{display:flex;align-items:center;justify-content:flex-start;gap:9px;
  background:var(--card);border:1px solid var(--line);color:var(--bright)}
.badge-legacy:hover{border-color:#00d4d45e}
.badge-ico{flex:none;color:var(--cyan)}
.badge-txt{display:flex;flex-direction:column;line-height:1.2;text-align:left}
.badge-txt small{font-size:.6rem;font-weight:600;letter-spacing:.07em;color:var(--mid);text-transform:uppercase}
.badge-txt strong{font-size:.88rem;font-weight:700;color:var(--bright)}
/* Interactive elements are <a> tags plus the .interact layer's buttons
   and form fields. Introduce any other focusable control and it needs
   adding to this selector too. */
a:focus-visible,button:focus-visible{outline:2px solid var(--cyan);outline-offset:2px;border-radius:4px}
.crumb{margin-bottom:14px;font-size:.8rem}
.crumb a{color:var(--cyan);text-decoration:none}
.story .t{font-size:1.7rem;margin:10px 0}
.story img.art{border-radius:10px;margin-bottom:14px}
/* .story p forces the serif "reading" treatment on every <p> in the article
   -- override it here so the provenance line reads as chrome/meta info
   (sans, dim, small) rather than narrative story text. */
.story p.provenance{font-family:var(--sans);font-size:.82rem;line-height:1.5;color:var(--mid);margin-top:10px}
.story p.provenance time{color:var(--txt)}
.promo{margin-top:26px}
.promo .lbl{font-size:.68rem;color:var(--dim);text-transform:uppercase;letter-spacing:.12em;margin-bottom:10px}
.promorow{display:flex;gap:10px;overflow-x:auto;scrollbar-width:none}
.promorow::-webkit-scrollbar{display:none}
.promorow a.iconchip{padding:6px;border-radius:12px;line-height:0}
.promorow a.iconchip .ico{width:40px;height:40px;margin:0;border-radius:9px;vertical-align:middle;display:block}
.promorow a{flex:none;font-size:.78rem;font-weight:600;color:var(--txt);text-decoration:none;
  background:var(--card);border:1px solid var(--line);border-radius:10px;padding:10px 14px}
.foot{margin-top:32px;font-size:.8rem;line-height:1.55;color:var(--mid)}
@media (prefers-reduced-motion: reduce){.card,.expand,.storynav a{transition:none}}
/* Reactions + comments (.interact) -- ships hidden, revealed by
   hub-interact.js only when the API answers. Buttons reuse the chips'
   pill language so they read as tappable site chrome. */
.interact{margin-top:24px}
.interact .lbl{font-size:.68rem;color:var(--dim);text-transform:uppercase;letter-spacing:.12em;margin-bottom:10px}
.reactbar{display:flex;flex-wrap:wrap;gap:8px}
.react{font:inherit;font-size:1.05rem;line-height:1;color:var(--txt);background:var(--card);
  border:1px solid var(--line);border-radius:999px;padding:9px 14px;cursor:pointer;transition:border-color .15s ease}
.react:hover{border-color:#00d4d45e}
.react.on{color:var(--cyan);background:#00d4d414;border-color:#00d4d43d;cursor:default}
.react .cnt{font-family:var(--sans);font-size:.76rem;font-weight:700;color:var(--mid);margin-left:6px;vertical-align:2px}
.react .cnt:empty{margin-left:0}
.react.on .cnt{color:var(--cyan)}
/* Feed-card bar: compact, and above the card's stretched story link
   (z-index) so a tap reacts instead of navigating. display:flex would
   defeat the hidden attribute, hence the explicit [hidden] rule. */
.reactbar[hidden]{display:none}
/* Centered under the byline, tucked close to it (Zee, 2026-09-02). */
.reactbar.mini{position:relative;z-index:2;justify-content:center;margin-top:8px;gap:8px}
.reactbar.mini .react{font-size:.9rem;padding:5px 9px}
.reactbar.mini .react .cnt{font-size:.7rem;margin-left:5px}
blockquote .reactbar.mini{justify-content:center}
.comments{margin-top:26px}
/* Posted comments read as things people SAID -- flat quote strips, not
   boxes: on this page boxed panels are chrome (nav, app card, inputs), and
   three kinds of identical dark box in a row blur together (Zee,
   2026-08-31). */
.clist{list-style:none;padding:0;margin:0 0 22px}
.clist li{border-left:2px solid #00d4d43d;padding:2px 0 2px 14px;margin-bottom:16px}
.clist .who{font-size:.74rem;color:var(--dim);margin-bottom:4px}
.clist .who b{color:var(--bright);font-weight:600}
.clist p{font-size:.92rem;line-height:1.6;color:#C2CAD3;white-space:pre-wrap}
/* The form is its own zone: rule above, label, and inputs on the page
   ground (--bg) instead of the card ground the nav boxes use. */
.cform{display:flex;flex-direction:column;gap:8px;border-top:1px solid var(--line);padding-top:16px}
.cform .lbl{margin-bottom:2px}
.cform input,.cform textarea{font:inherit;font-size:.9rem;color:var(--bright);background:var(--bg);
  border:1px solid #2A3340;border-radius:8px;padding:9px 12px}
.cform textarea{resize:vertical}
.cform input::placeholder,.cform textarea::placeholder{color:var(--dim)}
.cform input:focus-visible,.cform textarea:focus-visible{outline:2px solid var(--cyan);outline-offset:1px}
.cform button{align-self:flex-start;font:inherit;font-size:.8rem;font-weight:700;color:var(--cyan);
  background:#00d4d414;border:1px solid #00d4d43d;border-radius:8px;padding:8px 14px;cursor:pointer}
/* Post-submit confirmation must be unmissable -- it replaces the whole form,
   so it is the only thing left to see (Zee, 2026-08-31). */
.cnote{font-size:.95rem;color:var(--bright);background:#00d4d414;border:1px solid #00d4d43a;
  border-radius:10px;padding:14px 18px}
.cnote::before{content:"✓";color:var(--cyan);font-weight:700;margin-right:9px}
@media (prefers-reduced-motion: reduce){.react{transition:none}}
