/* ===========================================================================
   Achilles Modding Studio

   Single-theme by intent: the studio identity is black and weathered gold,
   with crimson as the ACTIVE colour — the selected tab, the live filter, the
   designation on a card. Every colour is painted explicitly; there is no
   light variant.

   The shape of it: one cinematic full-bleed hero, counters in a band beneath
   it, then a card grid. Not a dense technical table — an earlier design was
   exactly that, and this replaced it.

   Two things worth knowing before editing:

     · The hero's words are in NORMAL FLOW, pulled up over the image with a
       negative margin. They were absolutely positioned once and collided the
       moment the text wrapped to three lines.

     · Contrast is audited, not eyeballed. Nav tabs use --ink-2 rather than
       --ink-3 because at --ink-3 they measured 3.79:1, under the 4.5:1 that
       text of that size needs.
   =========================================================================== */
:root{
  color-scheme:dark;
  --bg:#050506; --surface:#0E0E11; --surface-2:#16161A; --sunk:#0A0A0C;
  --ink:#F2EFE8; --ink-2:#A6A199; --ink-3:#6E6A63;
  --rule:#232329; --rule-2:#141418;
  --accent:#C89A2E; --accent-ink:#0A0A0B; --accent-soft:rgba(200,154,46,.10);
  --gold-2:#E7C263; --gold-dim:#6A5316;
  --plate-rule:rgba(200,154,46,.22);
  /* Crimson is the ACTIVE colour, not a warning one: the selected tab, the
     live filter, the designation on a card. */
  --hot:#D8412B; --hot-soft:rgba(216,65,43,.12);
  --live:#6F9E5F; --dev:#D08A28; --core:#7E8A94;
  --disp:"Archivo Black","Arial Black",system-ui,sans-serif;
  --cond:"Saira Condensed","Arial Narrow",system-ui,sans-serif;
  --body:"IBM Plex Sans",system-ui,-apple-system,Segoe UI,sans-serif;
  --mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Consolas,monospace;
  padding-top:env(safe-area-inset-top,0px);
  padding-bottom:env(safe-area-inset-bottom,0px);
}
*{box-sizing:border-box}
html,body{margin:0}
body{background:var(--bg);color:var(--ink);font-family:var(--body);font-size:15.5px;line-height:1.55;-webkit-font-smoothing:antialiased}
img{max-width:100%}
[hidden]{display:none!important}
.wrap{max-width:1180px;margin:0 auto;padding-inline:16px}
h1,h2{font-family:var(--disp);font-weight:400;text-wrap:balance;margin:0;letter-spacing:-.015em}
main > h2{font-size:clamp(30px,5vw,46px)!important;line-height:.98}
h3{font-family:var(--cond);font-weight:700;text-wrap:balance;margin:0;letter-spacing:.01em}
a{color:var(--accent)}
a:hover{color:var(--gold-2)}
:focus-visible{outline:2px solid var(--hot);outline-offset:2px;border-radius:2px}
button{font:inherit;color:inherit}
.mono{font-family:var(--mono);font-variant-numeric:tabular-nums}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* `.hud` used to paint eight corner brackets around a box. They were the
   framing device of an earlier design and are gone; the class survives only
   as a positioning context, because the markup still uses it. */
.hud{position:relative}

.panel{
  position:relative;
  background-color:var(--surface);
  border:1px solid var(--rule);
  border-left:2px solid var(--plate-rule);
  padding:22px 24px;
}

/* Section headings: an angled tab, cut on the same shallow diagonal the rest
   of the page uses, rather than a rule running the full width. */
.tick{
  display:block;
  width:fit-content;
  margin:52px 0 20px;
  padding:7px 20px 7px 13px;
  font-family:var(--mono);
  font-size:10px;
  letter-spacing:.3em;
  text-transform:uppercase;
  color:var(--ink-2);
  background:var(--surface);
  border-left:2px solid var(--hot);
  clip-path:polygon(0 0,calc(100% - 14px) 0,100% 100%,0 100%);
}

/* ---------- hero ----------
   Full-bleed: the image runs the width of the screen and the words are pulled
   up over the bottom of it.

   The words are in normal flow, not absolutely positioned. An earlier version
   placed the tagline and the paragraph with `bottom:` offsets and they
   collided the moment the text wrapped to three lines. Flow cannot do that:
   the text stacks itself and the image sits behind whatever height it needs.

   Deliberately absent: a giant ghosted wordmark behind the hero. The artwork
   already contains the wordmark; a second copy in outline turned the middle
   of the page to mud. */
.plate{position:relative;background:var(--bg);border-bottom:0;overflow:hidden}
.plate .wrap{padding-block:0;max-width:none;padding-inline:0}

.hero{position:relative;padding:0;margin-bottom:clamp(-210px,-24vh,-130px);isolation:isolate}
/* height wins over the img's HTML height attribute, which is a presentational
   hint that would otherwise fight this. */
.hero img{
  display:block;width:100%;height:min(80vh,820px);
  object-fit:cover;object-position:center 38%;
  filter:saturate(.92) contrast(1.06);
}
/* Scrim. The type sits on this, so it has to hold at every width. */
.hero::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    linear-gradient(180deg, rgba(5,5,6,.70) 0%, rgba(5,5,6,.06) 30%,
                            rgba(5,5,6,.72) 66%, var(--bg) 97%),
    radial-gradient(130% 72% at 50% 34%, transparent 26%, rgba(5,5,6,.5) 100%);
}

.tagline{
  position:relative;z-index:3;
  display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:0 13px;
  margin:0 auto;padding-inline:16px;
  font-family:var(--cond);font-weight:600;
  font-size:clamp(9.5px,1.05vw,11.5px);letter-spacing:.4em;
  text-transform:uppercase;color:var(--ink-2);
}
.tagline i{color:var(--hot);font-style:normal;font-size:11px}

.plate p{
  position:relative;z-index:3;
  width:min(62ch,88vw);max-width:none;margin:18px auto 0;
  text-align:center;font-size:clamp(13.5px,1.4vw,16px);
  color:var(--ink-2);text-wrap:pretty;
}

/* Counters as a band, not a row of text. They count up in app.js. */
.specrow{
  position:relative;z-index:3;
  display:grid;grid-template-columns:repeat(4,1fr);gap:0;
  margin:34px auto 0;max-width:1080px;
  border-top:1px solid var(--plate-rule);
  border-bottom:1px solid var(--rule);
  background:linear-gradient(180deg,rgba(5,5,6,.72),var(--bg));
  backdrop-filter:blur(3px);
}
.spec{padding:20px 18px 18px;margin:0;text-align:center;border-right:1px solid var(--rule)}
.spec:last-child{border-right:0}
.spec b{
  display:block;font-family:var(--disp);
  font-size:clamp(26px,3.6vw,40px);line-height:1;
  color:var(--ink);font-variant-numeric:tabular-nums;
}
.spec:first-child b{color:var(--accent)}
.spec span{display:block;margin-top:7px;font-family:var(--mono);font-size:9px;letter-spacing:.24em;text-transform:uppercase;color:var(--ink-3)}

@media(max-width:719px){
  /* A viewport-height crop is wrong here. The art is 3:1, so filling 80vh of
     a 375px screen scales it enormously and shows a slice — the wordmark ran
     off both edges. Give the image its own ratio back and let it be short: a
     banner that fits beats a close-up that does not. */
  .hero{margin-bottom:-30px}
  .hero img{height:auto;aspect-ratio:1.85;object-position:center}
  .hero::after{
    background:linear-gradient(180deg, rgba(5,5,6,.34) 0%, rgba(5,5,6,0) 26%,
                                       rgba(5,5,6,.80) 72%, var(--bg) 98%);
  }
  .tagline{letter-spacing:.26em}
  .plate p{margin-top:13px}
  .specrow{grid-template-columns:repeat(2,1fr);margin-top:26px}
  .spec:nth-child(2){border-right:0}
  .spec:nth-child(-n+2){border-bottom:1px solid var(--rule)}
}

/* ---------- nav ---------- */
nav{position:sticky;top:env(safe-area-inset-top,0px);z-index:20;background:rgba(5,5,6,.90);backdrop-filter:blur(10px);border-bottom:1px solid var(--rule)}
nav .wrap{display:flex;align-items:stretch;gap:15px}
.navmark{height:21px;width:auto;align-self:center;flex:0 0 auto}
.tabs{display:flex;gap:2px;overflow-x:auto;scrollbar-width:none;min-width:0}
.tabs::-webkit-scrollbar{display:none}
/* ink-2, not ink-3: these are the primary navigation. At ink-3 they measured
   3.79:1 against the nav background, under the 4.5:1 needed for text this
   size — a long-standing issue, caught by auditing contrast rather than by
   looking at it. */
.tab{position:relative;background:none;border:0;padding:17px 16px;font-family:var(--cond);font-size:13px;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-2);cursor:pointer;white-space:nowrap}
.tab:hover{color:var(--ink)}
.tab[aria-selected="true"]{color:var(--ink)}
/* A crimson marker under the live tab, inset from the label so it reads as a
   marker rather than a border. */
.tab[aria-selected="true"]::after{content:"";position:absolute;left:16px;right:16px;bottom:11px;height:2px;background:var(--hot)}
.tab .n{font-family:var(--mono);font-size:10px;margin-left:5px;color:var(--hot);letter-spacing:0}

/* signed-in controls, right of the tabs */
.account{display:flex;align-items:center;gap:8px;margin-left:auto;flex:0 0 auto}
.acct-btn{background:none;border:1px solid var(--hot);color:var(--hot);padding:7px 17px;border-radius:999px;font-family:var(--cond);font-size:12.5px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;cursor:pointer;white-space:nowrap}
.acct-btn:hover{background:var(--hot-soft)}
.acct-out{background:none;border:0;color:var(--ink-3);font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;cursor:pointer;padding:6px 4px;white-space:nowrap}
.acct-out:hover{color:var(--hot)}
.lbl-short{display:none}
@media(max-width:600px){
  .acct-btn{font-size:11px;padding:5px 9px}
  .acct-out{display:none}
  /* the long label is what pushes the tab strip off a phone screen */
  .lbl-full{display:none}
  .lbl-short{display:inline}
}

main{padding-block:44px 90px}
.eyebrow{font-family:var(--mono);font-size:10px;letter-spacing:.26em;text-transform:uppercase;color:var(--hot);margin:0 0 8px}

/* ---------- filters ---------- */
.filters{display:flex;flex-direction:column;gap:12px;margin-bottom:18px}
.search{width:100%;padding:13px 15px;background:var(--surface);border:1px solid var(--rule);border-radius:2px;color:var(--ink);font-family:var(--mono);font-size:13.5px}
.search::placeholder{color:var(--ink-3)}
.search:focus{outline:none;border-color:var(--accent)}
.chips{display:flex;flex-wrap:wrap;gap:6px}
.chip{background:var(--surface);border:1px solid var(--rule);border-radius:999px;padding:6px 14px;font-family:var(--mono);font-size:11px;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-2);cursor:pointer}
.chip:hover{border-color:var(--ink-3)}
.chip[aria-pressed="true"]{background:var(--hot);border-color:var(--hot);color:#fff}

/* ---------- roster ----------
   A card grid, not a table. The markup is still a flat list of buttons —
   thumb, designation, name, category, status — so this is entirely a
   question of how they are laid out. */
.roster{border-top:0}
/* The column headings only made sense above a table. */
.rhead{display:none!important}

#list{display:grid;grid-template-columns:repeat(auto-fill,minmax(268px,1fr));gap:16px}

.row{
  display:flex;flex-direction:column;align-items:flex-start;gap:0;
  width:100%;text-align:left;padding:0;cursor:pointer;
  background:var(--surface);border:1px solid var(--rule);
  overflow:hidden;transition:border-color .16s, transform .16s;
}
.row:hover{border-color:var(--accent);transform:translateY(-2px)}

/* The status stripe moves from the left edge to under the artwork, where it
   reads as part of the image rather than a margin decoration. */
.thumb{display:block;width:100%;height:150px;object-fit:cover;background-color:var(--surface-2);border:0;border-bottom:2px solid var(--stripe,var(--rule))}
.thumb.ph{background-image:url("assets/favicon-64.png");background-size:auto 46%;background-position:center;background-repeat:no-repeat;opacity:.38}

.row .desig{padding:13px 15px 0;font-family:var(--mono);font-size:10.5px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--hot)}
.row .nm{padding:5px 15px 0;font-family:var(--cond);font-weight:700;font-size:19px;letter-spacing:.01em;line-height:1.15}
.row .cat{padding:8px 15px 0;font-family:var(--mono);font-size:10.5px;color:var(--ink-3)}
.row .st{padding:9px 15px 14px}
/* The addon GUID is developer detail. It belongs on the mod's own page, not
   on a card somebody is scanning. */
.row .guid{display:none}

@media(prefers-reduced-motion:reduce){
  .row{transition:none}
  .row:hover{transform:none}
}
/* marks a roster row whose addon is downloadable right now */
.avail{display:inline-block;margin-left:7px;font-family:var(--mono);font-size:11px;font-weight:600;color:var(--accent);vertical-align:1px}
.panel.get > h3{color:var(--accent)}
.st{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;font-weight:500;white-space:nowrap}
.st::before{content:"";display:inline-block;width:6px;height:6px;margin-right:6px;vertical-align:.08em;background:currentColor}
.st.live{color:var(--live)} .st.dev{color:var(--dev)} .st.core{color:var(--core)}
.empty{padding:34px 12px;color:var(--ink-3);font-family:var(--mono);font-size:12.5px}

/* ---------- dossier ---------- */
.back{background:none;border:0;padding:0;margin-bottom:16px;font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);cursor:pointer}
.back:hover{color:var(--accent)}
.dhead{border-left:3px solid var(--stripe,var(--accent));padding-left:14px;margin-bottom:22px}
.dhead .desig{font-family:var(--mono);font-size:12px;letter-spacing:.14em;color:var(--ink-3);text-transform:uppercase}
.dhead h2{font-size:clamp(25px,5.5vw,38px);line-height:1.04;text-transform:uppercase;letter-spacing:.02em;margin:3px 0 9px}
.dhead p{margin:0;max-width:64ch;color:var(--ink-2)}
.specs{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1px;background:var(--rule);border:1px solid var(--rule);margin-bottom:24px}
.specs div{background:var(--surface);padding:11px 13px}
.specs dt{font-family:var(--mono);font-size:9.5px;letter-spacing:.17em;text-transform:uppercase;color:var(--ink-3);margin-bottom:3px}
.specs dd{margin:0;font-family:var(--mono);font-size:13px;word-break:break-word}
.deps{display:flex;flex-wrap:wrap;gap:5px}
.dep{font-family:var(--mono);font-size:11px;background:var(--surface-2);border:1px solid var(--rule);padding:3px 8px;color:var(--ink-2)}
.dep.ext{border-style:dashed;color:var(--ink-3)}

/* ---------- panels ---------- */
.panel{background-color:var(--surface);border:1px solid var(--rule);padding:18px;margin-bottom:18px}
.panel > h3{font-size:16px;text-transform:uppercase;letter-spacing:.11em;margin-bottom:4px}
.panel .hint{font-size:12.5px;color:var(--ink-3);margin:0 0 14px}
.note{font-family:var(--mono);font-size:11.5px;color:var(--ink-3);padding:10px 0}
.note.warn{color:var(--hot)}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;gap:9px;background:var(--accent);color:var(--accent-ink);border:1px solid var(--accent);padding:11px 24px;border-radius:999px;font-family:var(--cond);font-size:14px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;cursor:pointer;text-decoration:none}
.btn:hover{filter:brightness(1.1);color:var(--accent-ink)}
.btn.ghost{background:none;color:var(--accent);border-color:var(--accent)}
.btn.ghost:hover{background:var(--accent-soft);color:var(--gold-2)}
.btnrow{display:flex;flex-wrap:wrap;gap:10px;margin-top:4px}

/* ---------- issues ---------- */
.issue{border-bottom:1px solid var(--rule-2);padding:13px 0;display:grid;grid-template-columns:1fr auto;gap:5px 12px}
.issue .t{font-weight:600;font-size:14.5px;grid-column:1}
.issue .t a{text-decoration:none;color:var(--ink)}
.issue .t a:hover{color:var(--accent);text-decoration:underline}
.issue .meta{grid-column:1/-1;font-family:var(--mono);font-size:10.5px;color:var(--ink-3);letter-spacing:.05em}
.num{font-family:var(--mono);font-size:11px;color:var(--ink-3);align-self:start;white-space:nowrap}
.labels{display:flex;flex-wrap:wrap;gap:5px;grid-column:1/-1;margin-top:3px}
.label{font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;padding:2px 7px;border:1px solid var(--rule);color:var(--ink-3)}

/* ---------- prose ---------- */
.prose{max-width:66ch}
.prose h3{font-size:17px;text-transform:uppercase;letter-spacing:.1em;margin:26px 0 8px}
.prose h3:first-child{margin-top:0}
.prose p{margin:0 0 12px;color:var(--ink-2)}
.prose ol,.prose ul{margin:0 0 14px;padding-left:20px;color:var(--ink-2)}
.prose li{margin-bottom:6px}
.prose code{font-family:var(--mono);font-size:12.5px;background:var(--surface-2);padding:1px 5px;border:1px solid var(--rule-2)}
.callout{border-left:3px solid var(--hot);background:var(--surface);padding:12px 15px;margin:0 0 16px}
.callout p{margin:0;font-size:13.5px}

/* ---------- report checklist ---------- */
.checklist{margin:0;padding-left:0;list-style:none;counter-reset:cl}
.checklist li{counter-increment:cl;position:relative;padding:0 0 13px 34px;font-size:13.5px;color:var(--ink-2)}
.checklist li:last-child{padding-bottom:0}
.checklist li::before{content:counter(cl,decimal-leading-zero);position:absolute;left:0;top:1px;font-family:var(--mono);font-size:11px;color:var(--gold-dim);letter-spacing:.05em}
.checklist strong{color:var(--ink);font-weight:600}

/* ---------- servers ---------- */
.srv-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:6px}
.srv-title{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
.srv-head h3{font-size:17px;text-transform:uppercase;letter-spacing:.09em}
.srv-tag{font-family:var(--mono);font-size:9.5px;letter-spacing:.17em;text-transform:uppercase;color:var(--accent);border:1px solid var(--accent);padding:2px 8px}
.dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto;background:var(--ink-3)}
.dot.on{background:var(--live);box-shadow:0 0 0 3px rgba(111,158,95,.18)}
.dot.off{background:var(--hot);box-shadow:0 0 0 3px rgba(196,59,40,.15)}
.dot.unknown{background:var(--ink-3)}
.srv-state{font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase}
.srv-state.on{color:var(--live)} .srv-state.off{color:var(--hot)}
.srv .specs{margin:14px 0 0}
.srv-mods{margin-top:14px}
.srv-mods .eyebrow{margin-bottom:6px}

/* player count + capacity bar */
.players{margin:12px 0 2px}
.players-lab{display:flex;align-items:baseline;gap:7px;margin-bottom:6px}
.pc{font-family:var(--mono);font-size:19px;font-weight:600;color:var(--ink);font-variant-numeric:tabular-nums}
.pl{font-family:var(--mono);font-size:9.5px;letter-spacing:.17em;text-transform:uppercase;color:var(--ink-3)}
.bar{height:6px;background:var(--sunk);border:1px solid var(--rule);overflow:hidden}
.bar span{display:block;height:100%;background:var(--live);transition:width .4s ease}
.bar span.idle{background:var(--ink-3)}
@media (prefers-reduced-motion:reduce){.bar span{transition:none}}
.connect{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px}
.addr{font-family:var(--mono);font-size:14px;color:var(--ink);background:var(--sunk);border:1px solid var(--rule);padding:8px 12px;user-select:all;overflow-wrap:anywhere}

/* ---------- ACM network browser ---------- */
.netsum{display:flex;flex-wrap:wrap;gap:0;border-top:1px solid var(--plate-rule);border-bottom:1px solid var(--rule);margin-bottom:14px}
.netsum .spec{padding:12px 22px 10px 0;margin-right:22px}
.netlist{display:flex;flex-direction:column}
.netsrv{margin-bottom:14px}
.netsrv .srv-head h3{font-size:15.5px;text-transform:none;letter-spacing:.01em;overflow-wrap:anywhere}
.netstats{display:flex;flex-wrap:wrap;gap:0 26px;margin:12px 0 10px}
.netstat b{display:block;font-family:var(--mono);font-size:17px;font-weight:600;line-height:1.2;color:var(--ink);font-variant-numeric:tabular-nums}
.netstat span{font-family:var(--mono);font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3)}
.netstat.acm b{color:var(--accent)}
.netsrv .bar{margin-bottom:12px}
.netsrv .hint{margin:0 0 10px}
.dep.acm{border-color:var(--accent);color:var(--gold-2);background:var(--accent-soft)}
.netfoot{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:14px}
.seenat{font-family:var(--mono);font-size:10.5px;color:var(--ink-3)}
.modlist{display:flex;flex-wrap:wrap;gap:5px;margin-top:14px;padding-top:14px;border-top:1px solid var(--rule-2);max-height:320px;overflow-y:auto}

/* ---------- community directory ---------- */
.dirgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(310px,1fr));gap:14px}
.dircard{margin-bottom:0;display:flex;flex-direction:column}
.dirhead{display:flex;align-items:center;gap:13px;margin-bottom:12px}
.diricon{width:52px;height:52px;flex:0 0 auto;border-radius:50%;object-fit:cover;background:var(--surface-2);border:1px solid var(--rule)}
.diricon.ph{display:block;background-image:url("assets/favicon-64.png");background-size:auto 56%;background-position:center;background-repeat:no-repeat;opacity:.4}
.dirtitle{min-width:0}
.dirtitle h3{font-size:15.5px;text-transform:none;letter-spacing:.01em;overflow-wrap:anywhere}
.dirsub{display:flex;flex-wrap:wrap;gap:0 10px;margin-top:3px}
.dirsub span{font-family:var(--mono);font-size:9.5px;letter-spacing:.15em;text-transform:uppercase;color:var(--ink-3)}
.dirsub .off{color:var(--accent)}
.dirstats{display:flex;flex-wrap:wrap;gap:0 24px;margin-bottom:12px;min-height:34px;align-items:center}
.dirblurb{margin:0 0 12px;font-size:13.5px;color:var(--ink-2)}
.dirsched{margin:10px 0 0;font-family:var(--mono);font-size:11px;color:var(--ink-3)}
.dircard .btnrow{margin-top:auto;padding-top:14px}
.dircard.clickable{cursor:pointer}
.dircard.clickable:hover{border-color:var(--accent)}
.dircard.clickable:focus-visible{outline:2px solid var(--hot);outline-offset:2px}

/* ---------- community profile ---------- */
.profbanner{padding:6px;margin-bottom:20px}
.profbanner img{display:block;width:100%;height:auto;aspect-ratio:1920/600;object-fit:cover}
.profhead{display:flex;align-items:center;gap:18px;margin-bottom:16px}
.proficon{width:84px;height:84px;flex:0 0 auto;border-radius:50%;object-fit:cover;background:var(--surface-2);border:1px solid var(--rule)}
.proficon.ph{display:block;background-image:url("assets/favicon-64.png");background-size:auto 56%;background-position:center;background-repeat:no-repeat;opacity:.4}
.profstats{display:flex;flex-wrap:wrap;gap:0 30px;align-items:center;margin-bottom:18px;min-height:40px}
.profstats .netstat b{font-size:22px}
.profabout p{margin:0 0 12px;color:var(--ink-2);font-size:14.5px;max-width:64ch}
.profabout p:last-child{margin-bottom:0}
a.dep.acm{text-decoration:none}
a.dep.acm:hover{background:var(--accent);color:var(--accent-ink)}
.profgal{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:10px}
.profgal img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;border:1px solid var(--rule)}
@media(max-width:600px){.profhead{gap:13px}.proficon{width:60px;height:60px}}

/* ---------- store ---------- */
.shop-hero{padding:26px 22px;margin-bottom:22px;background-color:var(--surface)}
.shop-hero h2{margin:2px 0 10px}
.shop-hero > p{margin:0 0 18px;max-width:56ch;color:var(--ink-2)}
.btn.disabled{opacity:.45;cursor:not-allowed;pointer-events:none}
.prodgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(258px,1fr));gap:1px;background:var(--rule);border:1px solid var(--rule);margin-bottom:8px}
.prod{background-color:var(--surface);padding:18px;position:relative;display:flex;flex-direction:column}
/* The print artwork, shown as the thing it is. `contain`, not `cover`: this
   is a design, and cropping a design is worse than leaving space around it.
   The panel colour stands in for the garment — pale for a dark print, near
   black for a light one — chosen from the artwork by tools/make-shop-art.py. */
.prod-art{margin:-18px -18px 14px;background:#101013;border-bottom:1px solid var(--rule);
  display:grid;grid-template-rows:minmax(0,1fr);place-items:center;
  aspect-ratio:1/1;overflow:hidden;padding:26px}
.prod-art img{display:block;max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain}

/* Black line art needs something to sit on. A warm sand rather than white:
   it is one of the garment colours, and a white rectangle in a dark page
   reads as a hole. */
.prod-art.on-light{background:linear-gradient(160deg,#d8c9ab,#bfae8c)}

/* Nothing drawn yet. Deliberately flat and quiet — it should read as an
   honest gap, not as an image that failed to load. */
.prod-art.pending{background:repeating-linear-gradient(135deg,#101013,#101013 9px,#141419 9px,#141419 18px)}
.prod-art.pending span{font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-3)}

/* kept: the old flat-garment cards used this, and a stale reference should
   degrade to a plain panel rather than an unstyled image */
.prod-shot{margin:-18px -18px 14px;background:var(--sunk);border-bottom:1px solid var(--rule)}
.prod-shot img{display:block;width:100%;height:auto;aspect-ratio:4/3;object-fit:cover}
.prod-n{position:absolute;top:14px;right:16px;z-index:1;font-family:var(--mono);font-size:11px;color:var(--gold-2);background:rgba(8,8,10,.72);padding:2px 6px}
.prod h3{font-size:16px;text-transform:uppercase;letter-spacing:.08em;padding-right:32px}
.prod-kind{margin:3px 0 10px;font-family:var(--mono);font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3)}
.prod-desc{margin:0 0 13px;font-size:13.5px;color:var(--ink-2)}
.prod-meta{display:flex;flex-wrap:wrap;gap:5px}
.colgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(232px,1fr));gap:14px}
.coll{border-left:2px solid var(--accent);padding:2px 0 2px 13px}
.coll h3{font-size:13.5px;text-transform:uppercase;letter-spacing:.15em;color:var(--accent);margin-bottom:5px}
.coll p{margin:0;font-size:13px;color:var(--ink-2)}

/* ---------- brand blocks ---------- */
.shot{margin:0 0 22px;padding:6px}
.shot img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover}
.wip{display:flex;flex-wrap:wrap;gap:0 18px;margin:0 0 20px;padding:9px 0 9px 13px;border-left:2px solid var(--dev)}
.wip span{font-family:var(--mono);font-size:9.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--dev)}
.studio{display:grid;grid-template-columns:150px 1fr;gap:24px;align-items:center;padding:20px;background-color:var(--surface)}
.studio img{width:100%;height:auto;display:block}
.studio h3{font-size:16px;text-transform:uppercase;letter-spacing:.26em;color:var(--accent);margin-bottom:9px}
.studio p{margin:0;color:var(--ink-2);font-size:14px;max-width:56ch}
@media(max-width:600px){.studio{grid-template-columns:1fr;gap:16px}.studio img{max-width:180px}}

footer{border-top:1px solid var(--rule);margin-top:10px}
footer .wrap{padding-block:20px 30px;font-family:var(--mono);font-size:11px;color:var(--ink-3);display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap}
footer a{color:var(--ink-3);text-decoration:none}
footer a:hover{color:var(--accent)}
@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* ---------- dashboard, editor, review queue ---------- */
.dashrow{margin-bottom:14px}
.dashrow .dirhead{margin-bottom:10px}

/* form fields */
.fld{margin:0 0 18px}
.fld > label{display:flex;align-items:center;gap:9px;margin-bottom:6px;font-family:var(--cond);font-size:12.5px;font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-2)}
.fld input[type=text],.fld textarea,.fld select{
  width:100%;box-sizing:border-box;padding:9px 11px;
  background:var(--bg);border:1px solid var(--rule);border-radius:2px;
  color:var(--ink);font-family:var(--body);font-size:14.5px;line-height:1.5}
.fld textarea{resize:vertical;min-height:70px}
.fld input[type=text]:focus,.fld textarea:focus,.fld select:focus{outline:none;border-color:var(--accent)}
.fld input[type=file]{width:100%;font-family:var(--mono);font-size:11.5px;color:var(--ink-3)}
.fld input[type=checkbox]{width:17px;height:17px;accent-color:var(--accent)}
.fld .hint{margin:6px 0 0}

/* "needs approval" — the single most important thing the editor tells you */
.majortag{font-family:var(--mono);font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--dev);border:1px solid var(--dev);border-radius:2px;padding:1px 5px;white-space:nowrap}

.checkrow{display:flex;flex-wrap:wrap;gap:7px}
.chk{display:flex;align-items:center;gap:7px;padding:6px 11px 6px 8px;border:1px solid var(--rule);border-radius:2px;
  font-family:var(--cond);font-size:13px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-2);cursor:pointer}
.chk:hover{border-color:var(--accent);color:var(--ink)}
.chk input{margin:0}
.modpick{max-height:260px;overflow:auto;padding:9px;border:1px solid var(--rule);border-radius:2px}

.claimrow{display:flex;gap:9px;margin-top:9px;flex-wrap:wrap}
.claimrow input{flex:1 1 240px;min-width:0;padding:8px 11px;background:var(--bg);
  border:1px solid var(--rule);border-radius:2px;color:var(--ink);font-family:var(--body);font-size:14px}
.claimrow input:focus{outline:none;border-color:var(--accent)}

.imgprev{display:flex;align-items:center;gap:13px;margin-bottom:10px}
.imgprev img{max-width:180px;max-height:96px;border:1px solid var(--rule);background:var(--bg)}
.imggrid{display:flex;flex-wrap:wrap;gap:13px;margin-bottom:10px}
.imggrid .imgprev{flex-direction:column;align-items:flex-start;gap:7px;margin:0}

.sticky-actions{position:sticky;bottom:0;z-index:5;background-color:var(--surface)}

/* the before/after a reviewer actually decides on */
.diffs{display:flex;flex-direction:column;gap:11px;margin:14px 0}
.diff{display:grid;grid-template-columns:150px 1fr 1fr;gap:11px;align-items:start;
  padding-bottom:11px;border-bottom:1px solid var(--plate-rule)}
.diff:last-child{border-bottom:0}
.dk{font-family:var(--cond);font-size:12.5px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3)}
.dv{font-size:14px;line-height:1.5;word-break:break-word}
.dv.was{color:var(--ink-3)}
.dv.now{color:var(--ink)}
.dtag{display:block;font-family:var(--mono);font-size:8.5px;letter-spacing:.16em;text-transform:uppercase;margin-bottom:4px}
.dv.was .dtag{color:var(--core)}
.dv.now .dtag{color:var(--accent)}
@media(max-width:719px){.diff{grid-template-columns:1fr;gap:5px}}

.notes{margin-top:16px;border-top:1px solid var(--plate-rule);padding-top:12px}
.notes summary{cursor:pointer;font-family:var(--mono);font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-3)}
.notes summary:hover{color:var(--accent)}
.notelist{margin-top:11px}

/* The action bar follows you down a long form, which is the point — but at
   full size on a phone it covered nearly half the screen, including the field
   you were trying to edit. Shrink it there and drop the explanation: every
   field already carries its own "needs approval" tag. */
@media(max-width:719px){
  .sticky-actions{padding:11px 13px}
  .sticky-actions .note{display:none}
  .sticky-actions .btnrow{gap:7px}
  .sticky-actions .btn{padding:8px 13px;font-size:12.5px}
}

/* ---------- side rails ----------
   Decoration with a job: the left one reports scroll position, the right one
   labels the page. Both hide when there is no room for them, and neither is
   ever the only place something is said — they are aria-hidden for that
   reason. */
.rail{position:fixed;z-index:15;top:0;bottom:0;width:56px;pointer-events:none}
@media(max-width:1340px){.rail{display:none}}
.rail-l{left:0}
.rail-r{right:0}
.rail-track{position:absolute;left:50%;top:26vh;bottom:26vh;width:1px;background:var(--rule);transform:translateX(-50%)}
.rail-fill{position:absolute;left:0;right:0;top:0;height:0;background:var(--hot);transition:height .1s linear}
.rail-pct{position:absolute;left:50%;top:calc(26vh - 26px);transform:translateX(-50%);font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;color:var(--ink-3);font-variant-numeric:tabular-nums}
.rail-r .rail-txt{position:absolute;right:19px;top:50%;transform:translateY(-50%) rotate(180deg);writing-mode:vertical-rl;font-family:var(--mono);font-size:9.5px;letter-spacing:.34em;text-transform:uppercase;color:var(--ink-3);white-space:nowrap}

/* ---------- music ----------
   Never autoplays. The browser would block it and it would be rude anyway:
   sound starts because somebody asked for it, and the button says which
   state it is in rather than relying on an icon alone. */
/* ACM RADIO. Fixed bottom-right rather than in the nav, so it is reachable
   from anywhere on a long page and never scrolls away from someone who
   wants to turn it down. z-index clears page content but it never meets the
   sticky nav, which owns the top of the screen and sits at 20. */
.audio{position:fixed;right:18px;bottom:18px;z-index:30;
  display:flex;align-items:center;gap:8px;
  padding:6px 8px 6px 6px;border:1px solid var(--rule);border-radius:999px;
  background:rgba(10,10,12,.92);backdrop-filter:blur(10px);
  box-shadow:0 8px 28px rgba(0,0,0,.55)}
.audio:empty{display:none}
.audio.on{border-color:var(--rule-2)}

/* Mute and volume. Collapsed, the panel is one pill that starts playback in
   a single press; these appear only once there is something to adjust. */
.audio-more{display:none;align-items:center;gap:8px;padding-right:4px}
.audio.on .audio-more{display:flex}
.audio-mute{background:none;border:0;padding:0 2px;cursor:pointer;
  font-family:var(--mono);font-size:9.5px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-3)}
.audio-mute:hover{color:var(--accent)}
.audio-mute[aria-pressed="true"]{color:var(--hot)}
@media(max-width:600px){.audio{right:10px;bottom:10px;gap:6px}}
.audio-btn{display:flex;align-items:center;gap:8px;background:none;border:1px solid var(--rule);color:var(--ink-3);border-radius:999px;padding:6px 13px 6px 10px;cursor:pointer;font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;white-space:nowrap}
.audio-btn:hover{border-color:var(--accent);color:var(--ink-2)}
.audio-btn[aria-pressed="true"]{border-color:var(--hot);color:var(--hot)}
.audio-bars{display:flex;align-items:flex-end;gap:2px;height:11px}
.audio-bars i{width:2px;height:3px;background:currentColor;display:block}
.audio-btn[aria-pressed="true"] .audio-bars i{animation:vu .9s ease-in-out infinite}
.audio-bars i:nth-child(2){animation-delay:.15s}
.audio-bars i:nth-child(3){animation-delay:.3s}
.audio-bars i:nth-child(4){animation-delay:.45s}
@keyframes vu{0%,100%{height:3px}50%{height:11px}}
/* Someone who asked for less motion should not be handed a dancing icon. */
@media(prefers-reduced-motion:reduce){
  .audio-btn[aria-pressed="true"] .audio-bars i{animation:none;height:7px}
}
.audio-track{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;color:var(--ink-3);max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@media(max-width:760px){.audio-track{display:none}}
@media(max-width:600px){.audio-btn{padding:5px 9px;font-size:9px}}

/* Billing lives inside the same collapsible the staff notes use, so a
   manager's card stays about their community and money stays folded away
   until they go looking for it. */
.billing summary{color:var(--ink-2)}
.billing .btnrow{margin-top:12px}
.billing .note{max-width:62ch}

/* "Promoted" is paid position and must not look like the editorial badges
   beside it. `.off` is gold and filled — ACM saying something about a
   community. This is grey, outlined and quieter: a disclosure, not a
   commendation. Making them look alike would be the whole problem. */
.dirsub .promo{
  border:1px solid var(--ink-3);border-radius:2px;padding:1px 6px;
  font-family:var(--mono);font-size:8.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-3);
}

/* ------------------------------------------------- public server browser
   Five thousand rows, so the row has to stay cheap: no shadows, no per-row
   gradients, one border. Laid out as a grid rather than a table because the
   columns collapse to stacked blocks on a phone, which a table will not do
   without fighting it. */
.srvsearch{display:flex;gap:8px;margin:0 0 14px;flex-wrap:wrap}
.srvsearch input{flex:1 1 260px;min-width:0;background:var(--sunk);border:1px solid var(--rule);
  color:var(--ink);font-family:var(--sans);font-size:14px;padding:9px 12px}
.srvsearch input:focus{outline:2px solid var(--hot);outline-offset:-1px}

.srvlist{border:1px solid var(--rule);border-bottom:0}
.srvrow{display:grid;grid-template-columns:10px minmax(0,1.6fr) minmax(0,1fr) 104px;
  gap:12px;align-items:center;padding:10px 13px;border-bottom:1px solid var(--rule);
  background:var(--surface)}
.srvrow:nth-child(odd){background:var(--surface-2)}

.srvdot{width:7px;height:7px;border-radius:50%;background:var(--ok,#4c9a5a)}
.srvdot.full{background:var(--hot)}

.srvname{min-width:0}
.srvname>span{display:block;font-family:var(--cond);font-weight:600;font-size:15px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.srvtags{display:flex;gap:5px;flex-wrap:wrap;margin-top:4px}

.srvmeta{min-width:0;font-family:var(--mono);font-size:11px;color:var(--ink-3)}
.srvmeta>span{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.srvsub{color:var(--ink-3);opacity:.75;margin-top:2px}

.srvpop{font-family:var(--mono);font-size:13px;text-align:right;white-space:nowrap}
.srvpop.full strong{color:var(--hot)}
.srvq{display:block;font-size:10px;color:var(--ink-3);white-space:normal;line-height:1.3}

.srvnav{align-items:center;margin-top:14px}
.srvpage{font-family:var(--mono);font-size:11px;color:var(--ink-3);letter-spacing:.1em}

/* On a phone the scenario column is the first thing to go: the name and the
   player count are what someone is actually scanning for. */
@media(max-width:720px){
  .srvrow{grid-template-columns:8px minmax(0,1fr) 92px;gap:9px}
  .srvmeta{display:none}
  /* .srvrow.open is set further down, in the block that owns the expandable
     row. Setting it here as well only looks like it works: the later block
     wins, and the two drift apart. */
}

/* Directory kind filter. Roleplay and milsim are different things and a
   browser should be able to pick one. */
.dirfilter{display:flex;gap:7px;flex-wrap:wrap;margin:0 0 16px}
.chipf{background:var(--sunk);border:1px solid var(--rule);color:var(--ink-2);
  font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  padding:7px 11px;cursor:pointer;display:inline-flex;align-items:center;gap:6px}
.chipf:hover{color:var(--ink);border-color:var(--ink-3)}
.chipf.on{background:var(--hot);border-color:var(--hot);color:#0b0b0d}
.chipn{font-size:10px;opacity:.7}
.chipf.on .chipn{opacity:.85}

/* A server row opens to show its details. The chevron is the affordance; the
   whole row is the target, because a 10px chevron is not a tap target. */
.srvitem{border-bottom:1px solid var(--rule)}
.srvlist .srvitem:nth-child(odd) .srvrow{background:var(--surface-2)}
.srvrow.open{cursor:pointer;border-bottom:0;grid-template-columns:10px minmax(0,1.6fr) minmax(0,1fr) 104px 18px}
.srvrow.open:hover{background:var(--sunk)}
.srvrow.open:focus-visible{outline:2px solid var(--hot);outline-offset:-2px}
.srvchev{font-family:var(--mono);font-size:15px;color:var(--ink-3);text-align:center;line-height:1}
.srvrow.open:hover .srvchev{color:var(--hot)}

.srvdetail{padding:4px 14px 16px;background:var(--sunk)}
.srvdetail .specs{margin:0 0 12px}
.srvdetail .tick{margin-top:4px}
.srvdetail .deps{margin-bottom:4px}
.srvdetail .notes{margin-top:10px}

@media(max-width:720px){
  /* Same fixed player column as the wide layout, for the same reason: an
     auto column is measured per row, and rows that do not share a column
     width do not read as a list. */
  .srvrow.open{grid-template-columns:8px minmax(0,1fr) 92px 18px}
}

/* A mod chip that links somewhere should look like it does. Ours already have
   the gold treatment (a.dep.acm above); this covers everybody else's, which
   now open their Workshop page. Plain chips that are NOT links keep the flat
   look, so the difference stays visible. */
a.dep{text-decoration:none;cursor:pointer}
a.dep:hover{border-color:var(--ink-3);color:var(--ink);background:var(--sunk)}
a.dep:focus-visible{outline:2px solid var(--hot);outline-offset:1px}

/* The mod list in a server panel gets room to breathe — a hundred chips with
   no gap is a paragraph, not a list. */
.srvdetail .deps{display:flex;flex-wrap:wrap;gap:6px}
.srvdetail details .hint{margin:8px 0 8px}

/* An offline server is absent, not empty — so it gets its own quiet treatment
   rather than reading as a server with nobody on it. */
.srvdot.off{background:var(--ink-3);opacity:.5}
.srvpop.off strong{color:var(--ink-3);font-size:11px;letter-spacing:.1em;text-transform:uppercase}

/* The network panel's mod chips are links now too. `.dep.ext` is dashed and
   dimmed to mark a third-party mod; keep that, just make it behave like the
   link it is. */
a.dep.ext{text-decoration:none}
a.dep.ext:hover{border-style:solid;color:var(--ink);background:var(--sunk);border-color:var(--ink-3)}

/* Real product photographs from the shop. They arrive cut out on transparency
   at 3:4, so they need containing rather than cropping, and they sit directly
   on the card's own dark background — no panel colour, because the garment IS
   the subject and a coloured box behind it just competes. */
.prod-photo{margin:-18px -18px 14px;background:#0d0d10;border-bottom:1px solid var(--rule);
  display:grid;grid-template-rows:minmax(0,1fr);place-items:center;
  aspect-ratio:1/1;overflow:hidden;padding:14px}
.prod-photo img{display:block;max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain}

/* "Showing 150 of 98,702" sits with the load button rather than above it, so
   the number and the way to change it are in the same place. */
.srvnav{align-items:center;gap:12px;flex-wrap:wrap}
.srvhint{font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-3)}

/* What a slot costs, on the Communities tab, before anyone signs in. Four
   cards rather than a table: on a phone a pricing table becomes a horizontal
   scroll, which is how people decide not to read it. */
.pricing{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
  gap:10px;margin:14px 0 18px}
.pricecard{background:var(--sunk);border:1px solid var(--rule);padding:14px}
.pricecard.top{border-color:var(--accent)}
.priceamt{display:block;font-family:var(--cond);font-weight:700;font-size:22px;
  color:var(--ink);letter-spacing:.01em}
.pricecard.top .priceamt{color:var(--gold-2)}
.pricecard h4{margin:4px 0 6px;font-family:var(--cond);font-weight:600;font-size:14px;
  letter-spacing:.04em;text-transform:uppercase}
.pricecard .hint{margin:0}

/* Browse by game. Cards rather than a list, because "which game" is a visual
   choice somebody makes in a second, and a list of thirty names is not. */
.gamegrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));
  gap:12px;margin:0 0 18px}
.gamecard{background:var(--surface);border:1px solid var(--rule);padding:0 0 14px;
  text-align:left;cursor:pointer;font:inherit;color:inherit;display:flex;
  flex-direction:column}
.gamecard:hover{border-color:var(--accent)}
.gamecard:focus-visible{outline:2px solid var(--hot);outline-offset:2px}
.gamecard h3{margin:12px 14px 0;font-family:var(--cond);font-weight:700;font-size:16px;
  letter-spacing:.02em}
.gamecard .hint{margin:6px 14px 0}
/* A game nobody has listed under yet is still a real card — just quieter, so
   the populated ones read first without the empty ones looking broken. */
.gamecard.empty .gameshot{filter:saturate(.5)}
.gamecard.empty:hover .gameshot{filter:none}

/* ---- the art slot ----
   A picture when we have one, and when we do not, a card BUILT from the
   game's tone rather than a grey box apologising for itself: the colour, a
   dark rake across it, a fine diagonal texture, and the name set large. It
   has to look like a decision, because it is one. */
.gameshot{position:relative;aspect-ratio:16/9;background:var(--tone,#4A4A50);
  border-bottom:1px solid var(--rule);display:grid;place-items:center;overflow:hidden}
.gameshot::before{content:"";position:absolute;inset:0;
  background:linear-gradient(197deg,rgba(0,0,0,.18) 0%,rgba(8,8,10,.62) 52%,rgba(8,8,10,.93) 100%)}
.gameshot::after{content:"";position:absolute;inset:0;opacity:.5;
  background:repeating-linear-gradient(115deg,rgba(255,255,255,.05) 0 1px,transparent 1px 7px)}
.gamecard:hover .gameshot::before{
  background:linear-gradient(197deg,rgba(0,0,0,.06) 0%,rgba(8,8,10,.52) 52%,rgba(8,8,10,.9) 100%)}

/* With a real picture the tone and the texture get out of the way; the scrim
   stays, because the count sits on top of it and has to stay readable. */
.gameshot.hasart{background:var(--sunk)}
.gameshot.hasart::after{display:none}
.gameshot.hasart::before{
  background:linear-gradient(197deg,rgba(0,0,0,0) 38%,rgba(8,8,10,.78) 100%)}
.gameshot img{width:100%;height:100%;object-fit:cover;position:absolute;inset:0}
/* The name is drawn even over a picture, then hidden — so a picture that
   fails to load leaves a titled card rather than an empty rectangle. */
.gameshot.hasart .gametype{visibility:hidden}
.gameshot.hasart img:not([src])+.gametype{visibility:visible}
/* The name, stacked, standing in for cover art. Sized by how many lines it
   runs to so a four-word game still fits the same slot as a one-word one. */
.gametype{position:relative;z-index:1;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1px;padding:0 12px;text-align:center;font-family:var(--display);
  color:var(--ink);opacity:.92;line-height:1.02;letter-spacing:.05em;
  text-shadow:0 2px 14px rgba(0,0,0,.55);max-width:100%;overflow:hidden}
.gametype[data-lines="1"]{font-size:34px}
.gametype[data-lines="2"]{font-size:27px}
.gametype[data-lines="3"]{font-size:21px}
.gametype[data-lines="4"]{font-size:17px}
.gamecard.empty .gametype{opacity:.72}
.gamecard:hover .gametype{opacity:1}
.gamecount{position:absolute;z-index:1;left:8px;bottom:8px;background:rgba(8,8,10,.82);
  border:1px solid var(--rule);font-family:var(--mono);font-size:10.5px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-2);padding:3px 7px}
/* "Be the first" is an invitation, so it is gold rather than grey — and it
   must not be mistaken for a count of nothing. */
.gamecount.none{color:var(--accent);border-color:rgba(200,154,46,.45)}

/* ---- the bar above the grid ---- */
.gamewrap{margin:0 0 18px}
.gamesearch{margin-bottom:8px}
.gamesearch input{flex:1 1 220px}
.gamecountline{display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;
  margin:0 0 12px;font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-3)}

.gamehead{font-family:var(--cond);font-weight:700;font-size:20px;letter-spacing:.03em;
  text-transform:uppercase;margin:4px 0 12px}

/* Which game a community plays, on the card itself. Only rendered when the
   list is not already filtered to one game, so it never repeats itself. Quiet
   and outlined: it is a classifier, not a badge anybody earned. */
.dirsub .gametag{
  border:1px solid var(--rule);border-radius:2px;padding:1px 6px;
  color:var(--ink-2);font-size:8.5px;
}

/* The column header. Same grid as an open row so the three columns line up
   with what is under them, and quiet enough that it does not read as the
   first server in the list. The odd/even striping counts it as a child, so
   it also flips which rows are which — hence the :nth-child rules below it
   are written against .srvitem, not the row. */
.srvrow.srvhead{
  grid-template-columns:10px minmax(0,1.6fr) minmax(0,1fr) 104px 18px;
  background:var(--sunk);padding:7px 13px;cursor:default;
  border-bottom:1px solid var(--rule)
}
.srvhead span{font-family:var(--mono);font-size:9.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-3)}
.srvhead span:nth-child(4){text-align:right}
@media(max-width:720px){
  .srvrow.srvhead{grid-template-columns:8px minmax(0,1fr) 92px 18px;gap:9px}
  .srvhead span:nth-child(3){display:none}
  .srvhead span:nth-child(4){text-align:right}
}

/* ===========================================================================
   Community card, second pass.

   One card per row instead of a grid of tiles, because a unit's card now has
   to carry things a 310px tile cannot: their banner, what they require of a
   recruit, and when they play. The layout is main column + rail, and the rail
   is the part worth defending — it says WHEN THIS UNIT PLAYS where another
   directory would put a vote button. A play schedule is a fact about the
   unit. A vote count is a fact about its fanbase, and a directory that ranks
   by it ends up sorted by who ran the loudest campaign.
   =========================================================================== */
/* Tiles, like the game cards, rather than one card per row. A community's
   own banner is the thing that makes their page feel like theirs, and a full
   width strip gives it less presence than a tile does, not more. Two across
   on a desktop: these carry far more than a game card, so five would shrink
   the type past reading. */
.dirgrid{grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:14px}
/* Inside a tile there is no room for a side rail, and the card's own media
   query cannot help: the CARD is narrow while the VIEWPORT is wide. */
.dirgrid .dirbody{grid-template-columns:1fr;gap:14px}
.dirgrid .dirrail{flex-direction:column;align-items:stretch}
.dirgrid .dirrail .btnrow{flex-direction:column;align-items:stretch}
.dircard{display:flex;flex-direction:column}
.dircard .dirbody{flex:1 1 auto}

/* Promoted. Gold edge, and a glow that reads as lit rather than as a border
   somebody thickened. Kept subtle on purpose — a directory where the paid
   cards scream is one people stop trusting. */
.dircard.vip{border-color:var(--accent)}
.dircard.vip{box-shadow:0 0 0 1px rgba(200,154,46,.22), 0 10px 34px -16px rgba(200,154,46,.55)}
.dircard.vip .dirbanner{border-bottom-color:rgba(200,154,46,.35)}
.flag.vip{color:#0b0b0d;background:var(--gold-2);border-color:var(--gold-2);font-weight:600}
.ordernote{margin-top:-6px;margin-bottom:14px;max-width:70ch}

/* ---- banner ---- */
.dirbanner{position:relative;aspect-ratio:1200/260;background:var(--sunk);
  border-bottom:1px solid var(--rule);overflow:hidden;margin:-18px -18px 16px}
.dirbanner img{width:100%;height:100%;object-fit:cover;display:block}

/* ---- the two flags a reader scans for ---- */
.dirflags{display:flex;gap:6px;flex-wrap:wrap}
.dirbanner .dirflags{position:absolute;top:10px;right:10px}
.dirhead .dirflags{margin-left:auto;align-self:flex-start}
.flag{font-family:var(--mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  padding:4px 9px;border:1px solid;border-radius:2px;background:rgba(8,8,10,.78);white-space:nowrap}
.flag.ok{color:var(--accent);border-color:var(--accent)}
.flag.open{color:var(--live);border-color:var(--live)}
/* Closed is stated, not hidden: a shut unit that looks open wastes an
   application and somebody's evening. Quiet, though — it is not a demerit. */
.flag.shut{color:var(--ink-3);border-color:var(--rule)}

/* ---- body: main column + rail ---- */
.dirbody{display:grid;grid-template-columns:minmax(0,1fr) 218px;gap:20px;align-items:start}
.dirmain{min-width:0}
.dirrail{display:flex;flex-direction:column;gap:12px;min-width:0}
.dirrail .btnrow{margin-top:0;padding-top:0;flex-direction:column;align-items:stretch;gap:7px}
.dirrail .btn{text-align:center;justify-content:center}

/* Requirements read as conditions, not as tags: a dotted underline of sorts,
   in the body typeface, so they do not blend into the play-style chips. */
.dep.req{font-family:var(--sans);font-size:11.5px;color:var(--ink-2);
  background:transparent;border-color:var(--ink-3)}

/* ---- the ops block ---- */
.opsrail{border:1px solid var(--rule);background:var(--sunk);padding:11px 12px}
.opsrail .eyebrow{margin:0 0 5px}
.opswhen{margin:0;display:flex;align-items:baseline;gap:6px;flex-wrap:wrap}
.opswhen b{font-family:var(--cond);font-weight:700;font-size:19px;letter-spacing:.02em;color:var(--ink)}
.opswhen span{font-family:var(--mono);font-size:10px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-3)}
.opsin{margin:3px 0 0;font-family:var(--mono);font-size:11.5px;color:var(--accent);letter-spacing:.04em}
.opsmine{margin:2px 0 0;font-family:var(--mono);font-size:10.5px;color:var(--ink-3)}
.opsrate{margin:8px 0 0;padding-top:7px;border-top:1px solid var(--rule);
  font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3)}
.opsfree{margin:0;font-family:var(--mono);font-size:11px;color:var(--ink-2);line-height:1.5}

/* The rail goes under the body on a narrow screen rather than squeezing the
   name into three words a line. The ops block keeps its box; the buttons
   spread out again, because a full-width stack of them is a wall. */
@media(max-width:820px){
  .dirbody{grid-template-columns:1fr;gap:14px}
  .dirrail{flex-direction:row;flex-wrap:wrap;align-items:stretch}
  .opsrail{flex:1 1 200px}
  .dirrail .btnrow{flex:1 1 200px;flex-direction:row;align-items:center}
}
@media(max-width:600px){
  .dirbanner{margin:-14px -14px 13px}
  .dirrail{flex-direction:column}
  .dirrail .btnrow{flex-direction:column;align-items:stretch}
}

/* The ops block on a profile page: same component, laid out wide rather than
   as a column, because there is room and nothing to sit beside. */
.profops{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 18px;margin:0 0 18px}
.profops .eyebrow{margin:0}
.profops .opsrate{margin:0;padding-top:0;border-top:0}
.profops .opsin,.profops .opsmine{margin:0}

/* Seven day boxes in a row, which is narrower than the checkbox rows above
   it and should look deliberately so. */
.daysrow{gap:6px}
.daysrow .chk{padding:6px 9px}
.opspreview{margin-top:10px;color:var(--ink-2)}
.opspreview b{color:var(--ink)}
.fld input[type=number],.fld input[type=time]{width:auto;min-width:130px}
.gamesort{background:var(--sunk);border:1px solid var(--rule);color:var(--ink-2);
  font-family:var(--mono);font-size:11px;letter-spacing:.08em;padding:7px 9px;flex:0 0 auto}

/* Attribution for the game cards. Quiet, permanent, and on every page. */
footer .credit{color:var(--ink-3);opacity:.75}

/* The free-month banner, above the prices.

   Gold border because it is the offer, but the terms beside it are the same
   size as the offer itself and in ordinary body text. An offer set in 22px
   over terms set in 9px grey is the house style of every subscription anyone
   has ever been angry about. */
.pricing-wrap{margin:14px 0 18px}
.pricing-wrap .pricing{margin:12px 0 0}
.trialbar{border:1px solid var(--accent);background:rgba(200,154,46,.07);
  padding:11px 14px;display:flex;flex-wrap:wrap;gap:0 7px;align-items:baseline}
.trialbar b{font-family:var(--cond);font-weight:700;font-size:15px;letter-spacing:.03em;
  text-transform:uppercase;color:var(--gold-2)}
.trialbar span{font-size:13px;color:var(--ink-2);line-height:1.5;flex:1 1 320px;min-width:0}
.pricetrial{display:block;margin-top:8px;font-family:var(--mono);font-size:9.5px;
  letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3)}

/* ------------------------------------------------------- project requests
   One card per request, with the vote sitting to the right of the name where
   the eye lands after reading it. The count is the big number because the
   count is the information — the button around it is just how you add to it. */
.reqgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px}
.reqcard{margin-bottom:0;display:flex;flex-direction:column}
.reqhead{display:flex;gap:14px;align-items:flex-start;justify-content:space-between}
.reqtitle{min-width:0;flex:1 1 auto}
.reqtitle h3{font-size:15.5px;text-transform:none;letter-spacing:.01em;overflow-wrap:anywhere}
.reqcard .dirblurb{margin-top:11px}
.reqcard .btnrow{margin-top:auto;padding-top:12px}

.votebox{flex:0 0 auto}
.votebtn{background:var(--sunk);border:1px solid var(--rule);color:var(--ink-2);
  cursor:pointer;font:inherit;padding:8px 11px;min-width:92px;text-align:center;
  display:flex;flex-direction:column;gap:2px;align-items:center;line-height:1.2}
.votebtn b{font-family:var(--cond);font-weight:700;font-size:20px;color:var(--ink);
  font-variant-numeric:tabular-nums}
.votebtn span{font-family:var(--mono);font-size:8.5px;letter-spacing:.11em;
  text-transform:uppercase;color:var(--ink-3)}
.votebtn:hover{border-color:var(--accent)}
.votebtn:focus-visible{outline:2px solid var(--hot);outline-offset:2px}
.votebtn:disabled{opacity:.55;cursor:default}
/* Voted is gold and filled: it is a state you are in, not a button you can
   press again. Pressing it again takes the vote back, which is why it stays
   a button rather than becoming a label. */
.votebtn.on{border-color:var(--accent);background:rgba(200,154,46,.09)}
.votebtn.on b{color:var(--gold-2)}
.votebtn.on span{color:var(--accent)}
.btn.small{padding:5px 9px;font-size:10px}
/* The one destructive action on the review page. Red on hover only — a row
   of red buttons makes the whole queue look dangerous. */
.btn.ghost.danger:hover{border-color:var(--hot);color:var(--hot)}

/* The price cards are buttons now, so they have to look pressable — and say
   where they lead, since they take you to the dashboard rather than charging
   anything themselves. */
.pricecard{text-align:left;font:inherit;color:inherit;cursor:pointer;width:100%;
  display:flex;flex-direction:column}
.pricecard:hover{border-color:var(--accent)}
.pricecard:focus-visible{outline:2px solid var(--hot);outline-offset:2px}
.pricego{display:block;margin-top:auto;padding-top:11px;font-family:var(--mono);
  font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--accent)}
.pricecard:hover .pricego{color:var(--gold-2)}

/* The one thing on the staff register that is a warning about the future
   rather than a report about now. */
.callout.cliff{border-left:2px solid var(--hot)}
.callout.cliff p{margin:0 0 8px}
.callout.cliff p:last-child{margin:0}

/* The pay-first lock. Gold rather than the default hot-red left rule: this is
   an offer with a free month attached, not an error, and red would read as
   "you did something wrong" to somebody who has done nothing but arrive. */
.callout.lock{border-left-color:var(--accent);background:rgba(200,154,46,.06)}
.callout.lock p{color:var(--ink-1)}

/* A heading that is also a link to the full staff page. Styled as text rather
   than as a button: it sits inside an <h3> and a button chrome there reads as
   an action on the row, which it is not - it is the row's title. */
.linkish{background:none;border:0;padding:0;margin:0;font:inherit;color:inherit;
  cursor:pointer;text-align:left}
.linkish:hover{color:var(--accent);text-decoration:underline;text-underline-offset:3px}

/* ACM Supported. Gold-on-dark like the accent, but NOT the promoted gold —
   a paid slot and a designation ACM grants must not look like the same thing,
   or the badge starts reading as something bought. */
.flag.acm{color:var(--accent);border-color:var(--accent);background:rgba(200,154,46,.10)}
.chipf.acmchip{border-color:rgba(200,154,46,.45)}
.chipf.acmchip.on{background:var(--accent);border-color:var(--accent);color:#0b0b0d}
/* A badge toggle that is currently ON, in the staff panel. */
.btn.ghost.on{border-color:var(--accent);color:var(--accent)}

/* ---------------------------------------------------------- flight manual --
   A reference people come back to mid-flight, not an article. Everything here
   is about being scannable at a glance: keys that look like keys, a jump bar
   that gets you to the table in one tap, and rows you can follow across. */
.manual .lede{font-size:16px;color:var(--ink-1);max-width:62ch}
.manual h3{margin-top:34px;scroll-margin-top:72px}
.manual .sub{font-family:var(--mono);font-size:11px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-3);margin:-6px 0 10px}

/* The jump bar. Sticky, because the thing people want is the key table and
   making them scroll back up to find it is the whole failure mode. */
.manual .jump{position:sticky;top:0;z-index:5;display:flex;flex-wrap:wrap;gap:6px;
  padding:10px 0;margin:14px 0 4px;background:var(--bg);
  border-bottom:1px solid var(--rule)}
.manual .jump a{font-family:var(--mono);font-size:10.5px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--ink-2);text-decoration:none;
  border:1px solid var(--rule);border-radius:999px;padding:5px 11px}
.manual .jump a:hover{color:var(--accent);border-color:var(--accent)}

/* The lock sequence, as a sequence. */
.manual .stages{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:14px 0}
.manual .stages span{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;background:var(--surface);border:1px solid var(--rule);
  border-radius:3px;padding:5px 10px;color:var(--ink-2)}
/* LOCKED, not MEMORY. The gold marks the state you may fire in - the manual
   says so outright - and MEMORY is the fallback after you LOSE the target.
   Highlighting the last item in the sequence pointed the eye at the wrong
   word. MEMORY is dimmed instead, because it is a grace period, not a goal. */
.manual .stages span:nth-child(4){border-color:var(--accent);color:var(--accent)}
.manual .stages span:last-child{opacity:.6;font-style:italic}

kbd{font-family:var(--mono);font-size:11.5px;background:var(--sunk);
  border:1px solid var(--rule);border-bottom-width:2px;border-radius:4px;
  padding:2px 7px;color:var(--ink);white-space:nowrap;display:inline-block}

table.keys{width:100%;border-collapse:collapse;margin:8px 0 20px}
table.keys th,table.keys td{text-align:left;padding:9px 10px;
  border-bottom:1px solid var(--rule);vertical-align:top}
table.keys th{font-weight:400;color:var(--ink-1);width:46%}
table.keys td{width:27%}
table.keys .none{color:var(--ink-3)}
/* The header strip is its own table so each group below can size its own
   columns without the header fighting them. */
table.keyhead td,table.keyhead th{font-family:var(--mono);font-size:10.5px;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);
  border-bottom:1px solid var(--ink-3);padding-bottom:6px}
.manual h4{font-family:var(--mono);font-size:11px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--accent);margin:22px 0 2px}

@media (max-width:620px){
  /* Three columns do not fit a phone. Stack them, and label the two values so
     the keyboard and controller bindings stay distinguishable once the column
     headers are off-screen. */
  table.keys th,table.keys td{display:block;width:auto;border-bottom:0;padding:3px 0}
  table.keys tr{display:block;border-bottom:1px solid var(--rule);padding:10px 0}
  table.keys td::before{font-family:var(--mono);font-size:10px;letter-spacing:.07em;
    text-transform:uppercase;color:var(--ink-3);margin-right:8px}
  table.keys td:nth-of-type(1)::before{content:"Key"}
  table.keys td:nth-of-type(2)::before{content:"Pad"}
  table.keyhead{display:none}
}

/* ---------------------------------------------------------------- guides --
   The index. A short list of documents, so cards rather than a table: what
   somebody needs to choose between two guides is the title and one line
   saying what is inside, and a table row gives the title too little weight. */
.guidelist{display:grid;gap:12px;margin:22px 0 0}
@media (min-width:700px){.guidelist{grid-template-columns:1fr 1fr}}
.guidecard{display:flex;flex-direction:column;gap:5px;text-align:left;
  background:var(--surface);border:1px solid var(--rule);padding:16px 18px 15px;
  cursor:pointer;font:inherit;color:inherit;transition:border-color .12s}
.guidecard:hover{border-color:var(--accent)}
.guidecard .gsub{font-family:var(--mono);font-size:10px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--hot)}
.guidecard .gtitle{font-family:var(--cond);font-weight:700;font-size:20px;
  letter-spacing:.03em;color:var(--ink)}
.guidecard .gblurb{font-size:13.5px;color:var(--ink-2);line-height:1.5}
.guidecard .gmeta{font-family:var(--mono);font-size:10.5px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--ink-3);margin-top:3px}

/* Settings tables inside a guide. Not keyboard bindings, so the middle column
   is a chip naming where to find the setting, and the notes column carries
   most of the text rather than a second key. */
.tab-chip{font-family:var(--mono);font-size:10px;letter-spacing:.09em;
  text-transform:uppercase;background:var(--sunk);border:1px solid var(--rule);
  border-radius:3px;padding:3px 7px;color:var(--ink-2);white-space:nowrap;
  display:inline-block}
/* Reserved for the things somebody has to plan around - unfinished work and
   outright blockers. Gold, so skimming the limitations table lands on them. */
.tab-chip.warn{border-color:var(--accent);color:var(--accent)}
table.keys.opts th{width:26%}
table.keys.opts td{width:auto}
table.keys.opts td.wide{width:60%;color:var(--ink-2);font-size:13.5px;line-height:1.5}
table.keys.plain th{width:30%}
table.keys.plain td{width:auto;color:var(--ink-2)}

@media (max-width:620px){
  /* The stacked layout labels the value columns "Key" and "Pad", which is
     right for the flight manual and wrong for everything else. These two
     tables are not bindings, so the labels come off. */
  table.keys.opts td::before,
  table.keys.plain td::before{content:none}
  table.keys.opts td.wide{font-size:13px}
}

/* Volume for the audio player. Hidden until the player is actually on:
   somebody who has never pressed play has nothing to set the level of, and
   the nav bar has no room to spare. Appearing on play is also the moment it
   becomes useful, which is the only moment the layout shift is expected. */
.audio-vol{display:block;width:66px;height:14px;margin:0;background:none;
  cursor:pointer;-webkit-appearance:none;appearance:none}
.audio-vol::-webkit-slider-runnable-track{height:2px;background:var(--rule)}
.audio-vol::-moz-range-track{height:2px;background:var(--rule)}
.audio-vol::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  width:9px;height:9px;margin-top:-3.5px;border-radius:50%;background:var(--hot)}
.audio-vol::-moz-range-thumb{width:9px;height:9px;border:0;border-radius:50%;
  background:var(--hot)}
.audio-vol:hover::-webkit-slider-thumb{background:var(--accent)}
.audio-vol:hover::-moz-range-thumb{background:var(--accent)}
.audio-vol:focus-visible{outline:1px solid var(--accent);outline-offset:3px}
@media(max-width:600px){.audio-vol{width:52px}}

/* ---- ACM RADIO: the visible YouTube player -------------------------------
   The player is shown whenever it is allowed to be running. YouTube's embed
   terms require a visible, unobscured player of at least 200x200 and forbid
   using an embed purely as an audio source, so these dimensions are a floor
   set by the terms rather than a styling preference. Pausing leaves it on
   screen; only the close control ends the session and collapses the panel. */
.audio-stage{display:none}
.audio.open{flex-direction:column;align-items:stretch;gap:6px;
  border-radius:12px;padding:7px}
.audio.open .audio-stage{display:flex;flex-direction:column;gap:4px}
.audio-stage iframe,
.audio-stage #audio-video{display:block;width:356px;height:200px;
  border:0;border-radius:8px;background:#000}
/* 356x200 is 16:9 at the 200px floor. Narrower screens keep the 200px height
   and let YouTube letterbox, because dropping below 200 in either direction
   would put the embed out of compliance. */
@media(max-width:430px){
  .audio-stage iframe,
  .audio-stage #audio-video{width:calc(100vw - 44px);min-width:200px}
}
.audio-close{align-self:flex-end;width:22px;height:22px;padding:0;
  background:none;border:1px solid var(--rule);border-radius:50%;
  color:var(--ink-3);font-size:14px;line-height:1;cursor:pointer}
.audio-close:hover{color:var(--accent);border-color:var(--accent)}
.audio.open .audio-bar,
.audio.open .audio-more{display:flex}

/* ------------------------------------------------- modding studios ------
   The Roster landing page when this build shows studios rather than ACM's
   own projects. Borrows the Communities browsing pattern - a grid of cards
   you scan and open - without borrowing its game cards: these represent
   studios, which are a different thing entirely. */
.studios-head{max-width:66ch;margin:0 0 22px}
.studios-title{font-family:var(--cond);font-weight:700;font-size:30px;
  letter-spacing:.03em;margin:4px 0 8px;color:var(--ink)}
.studios-lede{color:var(--ink-2);font-size:14.5px;line-height:1.55;margin:0 0 16px}
.studios-search{width:100%;max-width:420px;background:var(--sunk);
  border:1px solid var(--rule);color:var(--ink);padding:10px 13px;
  font-family:var(--mono);font-size:12px;letter-spacing:.04em}
.studios-search:focus{outline:none;border-color:var(--accent)}

.studiogrid{display:grid;gap:16px;margin:0 0 28px}
@media(min-width:760px){.studiogrid{grid-template-columns:1fr 1fr}}

.studiocard{display:flex;flex-direction:column;text-align:left;padding:0;
  background:var(--surface);border:1px solid var(--rule);cursor:pointer;
  font:inherit;color:inherit;overflow:hidden;transition:border-color .12s}
.studiocard:hover{border-color:var(--accent)}
/* ACM's card is marked, not ranked: the gold edge is a platform position,
   never a consequence of recommendations. */
.studiocard.official{border-color:var(--accent)}
.studiocard-art{height:132px;background:var(--sunk);overflow:hidden}
.studiocard-art img{width:100%;height:100%;object-fit:cover;display:block}
.studiocard-body{padding:14px 16px 16px;display:flex;flex-direction:column;gap:7px}
.studiochip{align-self:flex-start;font-family:var(--mono);font-size:9.5px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--ink-3);
  border:1px solid var(--rule);border-radius:3px;padding:3px 8px}
.studiochip.official{color:var(--accent);border-color:var(--accent)}
.studiocard-name{font-family:var(--cond);font-weight:700;font-size:21px;
  letter-spacing:.03em;margin:0;color:var(--ink)}
.studiocard-blurb{margin:0;color:var(--ink-2);font-size:13.5px;line-height:1.5}
.studiocard-stats{display:flex;gap:26px;margin:4px 0 2px}
.studiostat strong{display:block;font-family:var(--cond);font-weight:700;
  font-size:22px;color:var(--accent);line-height:1.1}
.studiostat span{font-family:var(--mono);font-size:9.5px;letter-spacing:.18em;
  text-transform:uppercase;color:var(--ink-3)}
.studiocard-foot{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin-top:4px;padding-top:10px;border-top:1px solid var(--rule)}
.studiocard-game{font-family:var(--mono);font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-3)}
.studiocard-go{margin-left:auto;font-family:var(--mono);font-size:10px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}

/* A dependency that resolves inside the roster is a link, not a label. ACM's
   own deps are plain text because they are matched by name; these are rows
   with an id, so they can point at the real project. */
.deplink{background:none;border:0;padding:0;font:inherit;color:var(--accent);
  cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.deplink:hover{color:var(--ink)}
.dep.deplink{border:1px solid var(--accent);border-radius:3px;padding:3px 8px;
  text-decoration:none}
.dep.deplink:hover{background:rgba(200,154,46,.1)}
a.dep.ext{text-decoration:none}
a.dep.ext:hover{border-color:var(--accent);color:var(--accent)}
