:root{
  /* Cogitator phosphor. The green is the hot yellow-green a P1 tube actually
     throws, not a mint; amber is the alert/active channel and is never used
     for ordinary text. */
  --void:#040705; --panel:#080d0a; --raised:#0f1712; --edge:#1d3625;
  --phosphor:#8dff5a; --phosphor-dim:#3f8a3d; --amber:#ff9d2e; --gold:#e8c15a;
  --hostile:#ff5d6c; --paper:#d3e8c6; --dim:#7fa072; --dimmer:#4d6845;
  --bloom:0 0 6px currentColor;
  --blue:#4d8fe8; --red:#e05a52; --green:#22c55e;
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
  --sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{
  background:var(--void); color:var(--paper); font-family:var(--mono);
  font-size:13.5px; line-height:1.55; min-height:100vh;
  font-variant-numeric:tabular-nums;
}
/* no underlines on links anywhere: a link reads by its colour, and lights on hover */
a{text-decoration:none}
::selection{background:var(--phosphor);color:var(--void)}
:focus-visible{outline:2px solid var(--phosphor);outline-offset:2px}

/* Phosphor bloom. Emissive text only — headings, captions, active states and
   alert values. Body copy is deliberately excluded: a glow on a paragraph
   smears the counters and the page stops being readable. */
.brand h1,.head h2,h3.sec,.mtitle,.dispname,.dispmiss,.mparth,.mpart .msub,
.detail .dt,.tblkh,.score .nm,.mgoal b,.warnbox b,.lgcap,
.tip .tt,.tipbody .tt,.tip .tk,.tipbody .tk,
.tipbody .fsec,.nav a[aria-selected="true"],.stat.ok,.stat.no,
.laytab.on,.tabc button.on,.ticker b,.ticker em,.mnum,
th,.force .fh,.tpb .ct.full,.crtbtn[aria-pressed="true"]{
  text-shadow:var(--bloom);
}
/* .rcard .rnote and b.kw are amber but both are prose — a multi-line note and
   inline emphasis inside body copy — so they stay unbloomed for the same
   reason body copy does. The rule is glow on labels, not on sentences. */

.shell{position:relative;display:grid;grid-template-columns:184px 1fr;
  height:100vh;overflow:hidden;padding:14px}
@media(max-width:860px){.shell{grid-template-columns:1fr;
  grid-template-rows:auto minmax(0,1fr);height:100vh;height:100dvh;
  overflow:hidden;padding:9px}}

/* ------------------------------------------------------- cabinet frame */
/* Two hairline rules with bright corner elbows, the way the panel bezel is
   silk-screened on the reference set. Corners are drawn as separate elbows
   rather than chamfering the rules, because clipping a border cuts the
   stroke rather than mitring it. */
.frame{position:absolute;inset:0;pointer-events:none;z-index:40}
.frame::before,.frame::after{content:"";position:absolute;pointer-events:none}
.frame::before{inset:4px;border:1px solid rgb(141 255 90 / .34)}
.frame::after{inset:10px;border:1px solid rgb(141 255 90 / .15)}
.frame i{position:absolute;width:26px;height:26px;pointer-events:none;
  border:0 solid var(--phosphor);opacity:.72;
  filter:drop-shadow(0 0 4px rgb(141 255 90 / .55))}
.frame i.tl{top:4px;left:4px;border-top-width:2px;border-left-width:2px}
.frame i.tr{top:4px;right:4px;border-top-width:2px;border-right-width:2px}
.frame i.bl{bottom:4px;left:4px;border-bottom-width:2px;border-left-width:2px}
.frame i.br{bottom:4px;right:4px;border-bottom-width:2px;border-right-width:2px}
@media(max-width:860px){.frame i{width:16px;height:16px}}

/* the elbow tying the lit rail entry to the field it opened */
.bracket{position:absolute;inset:0;width:100%;height:100%;
  pointer-events:none;z-index:38;overflow:visible}
.bracket path{fill:none;stroke:var(--amber);stroke-width:1.4;
  vector-effect:non-scaling-stroke;
  filter:drop-shadow(0 0 4px rgb(255 157 46 / .9))}
.bracket circle{fill:var(--amber);filter:drop-shadow(0 0 4px rgb(255 157 46 / .9))}
@media(max-width:860px){.bracket{display:none}}

/* ---------------------------------------------------------------- rail */
.rail{
  grid-column:1;grid-row:1;
  border-right:1px solid var(--edge); background:var(--panel);
  padding:18px 0; position:sticky; top:0; overflow-y:auto;
  /* height comes from the grid stretch — an explicit 100% ignores the 16px of
     vertical margin and pushes the rail through the bezel */
  min-height:0; margin:8px 0 8px 8px;
}
.brand{padding:0 16px 16px;border-bottom:1px solid var(--edge);margin-bottom:13px}
.brand h1{font-size:13px;letter-spacing:4px;color:var(--phosphor);font-weight:700;line-height:1.25}
.brand .sub{font-size:10px;letter-spacing:1.8px;color:var(--dimmer);margin-top:5px}
.nav{display:flex;flex-direction:column;gap:3px;padding:0 9px}
/* The menu button exists only on a phone; on a desktop the rail is the menu. */
.menubtn{display:none}
/* On a phone the rail folds into a title bar with a three-line button, and the
   entries drop down over the page as a vertical list, as phone menus do. The
   bar is overflow:visible so the list can hang below it. */
@media(max-width:860px){
  /* z-index 59: above the map's zoom bar (.boardbar, 58), which would otherwise
     paint through the open list on a short landscape phone; level with .ovl,
     which comes later in the markup and so still covers the rail */
  .rail{position:relative;z-index:59;display:flex;align-items:center;justify-content:flex-start;
    height:auto;border-right:0;border-bottom:1px solid var(--edge);
    padding:0;margin:4px 4px 0;overflow:visible}
  .brand{padding:8px 12px;border-bottom:0;margin-bottom:0}
  .brand .sub{margin-top:3px}
  .menubtn{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:5px;
    flex:0 0 auto;width:44px;height:44px;margin-left:6px;padding:0;cursor:pointer;
    background:transparent;border:1px solid var(--edge);border-radius:3px}
  .menubtn span{display:block;width:20px;height:2px;background:var(--phosphor);
    transition:transform .15s,opacity .15s}
  .menubtn:hover,.menubtn:focus-visible,.menubtn[aria-expanded="true"]{border-color:var(--phosphor-dim)}
  /* open, the three lines cross into an X */
  .menubtn[aria-expanded="true"] span:nth-child(1){transform:translateY(7px) rotate(45deg)}
  .menubtn[aria-expanded="true"] span:nth-child(2){opacity:0}
  .menubtn[aria-expanded="true"] span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
  .railmenu{position:absolute;left:-1px;right:-1px;top:100%;
    background:var(--panel);border:1px solid var(--edge);border-top:0;
    box-shadow:0 14px 28px rgb(0 0 0 / .65);padding:6px 0 12px;
    max-height:calc(100vh - 80px);max-height:calc(100dvh - 80px);overflow-y:auto}
  .rail:not(.menuopen) .railmenu{display:none}
  .nav{padding:0 9px}
  .nav a{display:flex;align-items:center;min-height:44px;padding:0 12px}
}
.nav a{
  font:inherit;font-size:12px;letter-spacing:2.8px;text-align:left;font-weight:700;
  background:none;border:1px solid transparent;color:var(--dim);
  padding:8px 10px;cursor:pointer;white-space:nowrap;
  /* the rail entries are links now that each view is its own page; they are
     still meant to read as console keys, not as prose links */
  display:block;text-decoration:none;
  transition:color .12s,background .12s,border-color .12s;
}
.nav a:hover{color:var(--phosphor);background:var(--raised);border-color:var(--edge)}
/* the lit entry is amber and boxed — the one channel the operator has open */
.nav a[aria-selected="true"]{
  color:var(--amber);border-color:rgb(255 157 46 / .70);
  background:rgb(255 157 46 / .10);
}
.railfoot{padding:15px 16px;margin-top:16px;border-top:1px solid var(--edge);
  font-size:10px;color:var(--dimmer);letter-spacing:.6px}
/* the phase text goes; the CRT button goes too, as the tube is always off here */
@media(max-width:860px){
  .railfoot{padding:10px 21px 0;margin-top:8px;border-top:1px solid var(--edge)}
  /* display:none, not font-size:0 — zero-height text is still read aloud and
     still lands in a copy */
  .rfinfo{display:none}
}
.crtbtn{font:inherit;font-size:9.5px;letter-spacing:1.6px;font-weight:700;
  background:transparent;border:1px solid var(--edge);color:var(--dimmer);
  padding:4px 9px;margin-top:12px;cursor:pointer;display:block}
.crtbtn:hover{border-color:var(--phosphor-dim);color:var(--dim)}
.crtbtn[aria-pressed="true"]{border-color:var(--phosphor-dim);color:var(--phosphor)}

/* --------------------------------------------------------------- layout */
/* .main is the fixed field: it carries the tick rule and the ticker, and does
   not scroll. Only .mainbody inside it scrolls, so the rule stays put. */
.main{grid-column:2;grid-row:1;position:relative;padding:12px 12px 10px 30px;min-width:0;min-height:0;
  overflow:hidden;display:flex;flex-direction:column;margin:8px 8px 8px 0;
  /* the field's own faint graticule, as the reference rules its plot area */
  background-image:
    linear-gradient(to right,rgb(141 255 90 / .030) 0 1px,transparent 1px),
    linear-gradient(to bottom,rgb(141 255 90 / .030) 0 1px,transparent 1px);
  background-size:96px 100%,100% 96px}
/* the field edge, ruled like a trace graticule: minor ticks every 13px,
   major every 65px */
.main::before{
  content:"";position:absolute;left:16px;top:12px;bottom:10px;width:9px;
  pointer-events:none;
  background-image:
    repeating-linear-gradient(to bottom,rgb(141 255 90 / .40) 0 1px,transparent 1px 65px),
    repeating-linear-gradient(to bottom,rgb(141 255 90 / .17) 0 1px,transparent 1px 13px);
  background-size:9px 100%,5px 100%;
  background-repeat:no-repeat;
  background-position:left top,left top;
}
@media(max-width:860px){
  .shell:not(.mapfull) .main{grid-column:1;grid-row:2;margin:0 4px 4px}
}
/* the 40px foot was scroll run-out from when .main was the scroller; now that
   .mainbody scrolls it is only a dead gap that shortens the phone viewport */
@media(max-width:600px){.main{padding:10px 12px 10px 22px}
  .main::before{left:10px}}
.mainbody{flex:1 1 auto;min-height:0;min-width:0;overflow-y:auto;
  display:flex;flex-direction:column}
/* A child that declares any overflow has an auto-minimum size of zero and
   would be crushed by flex-shrink — this is what was flattening every table.
   NOTE: .mapview sets flex:1 1 auto later at equal specificity, so its shrink
   stays 1; that is fine, it declares no overflow and grows to fill. */
.mainbody > *{flex-shrink:0}

/* ------------------------------------------------------------- ticker */
/* Real campaign telemetry, not set dressing — date, phase, alliance points
   and outstanding orders, in the delimited form the panel prints them. */
.ticker{flex:0 0 auto;display:flex;align-items:baseline;font-size:10px;
  letter-spacing:1.5px;color:var(--dimmer);white-space:nowrap;overflow:hidden;
  padding:0 0 8px;margin-bottom:8px;border-bottom:1px solid var(--edge)}
/* the telemetry run gives way first; the alert never does */
.ticker .tseg{min-width:0;overflow:hidden;text-overflow:ellipsis}
.ticker .talert{flex:0 0 auto;margin-left:auto;padding-left:12px}
.ticker .tdue,.ticker .tpts{flex:0 0 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* the dates every player has to act on */
.dlbar{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;margin:0 0 18px}
@media(max-width:700px){.dlbar{grid-template-columns:1fr}}
.dl1{background:var(--panel);border:1px solid var(--edge);border-left:2px solid var(--c,var(--phosphor-dim));
  padding:11px 13px}
.dl1.due{border-color:var(--amber);border-left-color:var(--amber);
  background:rgb(255 157 46 / .07)}
.dl1.past{opacity:.55}
.dl1 .dlk{font-size:9.5px;letter-spacing:2px;color:var(--dimmer);font-weight:700}
.dl1 .dld{font-size:15px;letter-spacing:1.6px;color:var(--paper);font-weight:700;margin-top:4px}
.dl1.due .dld{color:var(--amber);text-shadow:var(--bloom)}
.dl1 .dlc{font-size:10px;letter-spacing:1.2px;color:var(--dim);margin-top:3px}
.dl1.due .dlc{color:var(--amber)}
.dl1 .dln{font-size:9.5px;color:var(--dimmer);margin-top:5px;letter-spacing:.6px}
.ticker b{color:var(--phosphor);font-weight:700}
.ticker em{color:var(--amber);font-style:normal;font-weight:700;
  border:1px solid rgb(255 157 46 / .75);padding:1px 6px 2px;margin-left:2px;
  box-shadow:0 0 8px rgb(255 157 46 / .22),inset 0 0 8px rgb(255 157 46 / .10)}
.ticker s{text-decoration:none;color:var(--edge);padding:0 7px}
@media(max-width:600px){
  .ticker{font-size:9px;letter-spacing:1px}
  /* no room for the preamble or the scores: the next deadline and the
     fleet count are what the line is for on a phone */
  .ticker .tseg,.ticker .tpts{display:none}
  .ticker .tdue > s:first-child{display:none}
  /* the date gives way first if the line is still too long */
  .ticker .tdue{flex:1 1 auto}
  .ticker .talert{padding-left:8px}
}

.head{display:flex;flex-wrap:wrap;align-items:baseline;gap:12px;margin-bottom:6px}
.head h2{font-size:18px;letter-spacing:5px;color:var(--phosphor);font-weight:700}
.head .note{font-size:10.5px;letter-spacing:1.4px;color:var(--dimmer)}
.lede{color:var(--dim);font-size:12px;margin-bottom:14px;max-width:66ch}
.hint{font-size:10.5px;letter-spacing:.6px;color:var(--dim)}
.hint b{font-weight:700}
.panel{background:var(--panel);border:1px solid var(--edge);padding:16px 18px}
.grid{display:grid;gap:14px}
h3.sec{font-size:11.5px;letter-spacing:3.2px;color:var(--amber);margin:26px 0 10px;font-weight:700}
h3.sec:first-child{margin-top:0}

/* --------------------------------------------------------- scoreboard */
.score{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:20px}
@media(max-width:600px){.score{grid-template-columns:1fr}}
.score .card{background:var(--panel);border:1px solid var(--edge);border-top:2px solid var(--c);padding:13px 15px}
/* the factions mustered under this banner, above the banner's own name */
.score .crests{display:flex;flex-wrap:wrap;align-items:flex-end;gap:10px;
  min-height:32px;margin-bottom:10px}
.score .crests .fmark{width:32px;height:32px;margin:0;vertical-align:baseline}
.score .who .fmark.badge{width:16px;height:16px;vertical-align:-3px;margin-right:4px}
.score .who .facm{white-space:nowrap}
.score .nm{font-size:10px;letter-spacing:2.6px;color:var(--c);font-weight:700}
.score .pts{font-size:30px;line-height:1.05;margin:6px 0 2px;font-weight:700}
.score .brk{font-size:9.5px;color:var(--dimmer);letter-spacing:.8px}
.score .who{font-size:10px;color:var(--dim);margin-top:8px;padding-top:8px;border-top:1px solid var(--edge)}

/* ------------------------------------------------------- phase battles */
.bday{margin-bottom:18px}
.bdh{display:flex;align-items:baseline;gap:12px;font-size:10.5px;letter-spacing:2.6px;color:var(--phosphor);font-weight:700;
  border-bottom:1px solid var(--edge);padding-bottom:6px;margin-bottom:10px}
.bdh em{font-style:normal;color:var(--dim);letter-spacing:1.2px;font-weight:400}
.bgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
@media(max-width:980px){.bgrid{grid-template-columns:1fr}}
.bcard{display:flex;gap:14px;align-items:center;background:var(--panel);border:1px solid var(--edge);padding:12px 14px}
.bworld{position:relative;flex:0 0 112px;width:112px;height:112px}
.bworld .globe{width:112px;height:112px;display:block;border-radius:50%;
  box-shadow:0 0 18px rgba(141,255,90,.08)}
.bworld .bthe{position:absolute;right:-4px;bottom:-4px;width:30px;height:30px;display:block;cursor:help;
  background:var(--void);border:1px solid var(--edge);border-radius:50%;padding:3px}
/* the badge stays on the globe's edge however far the tier shrinks it */
.bworld .bthe{right:calc((1 - var(--ws,1)) * 50% - 4px);bottom:calc((1 - var(--ws,1)) * 50% - 4px)}
.bworld .bthe img{width:100%;height:100%;object-fit:contain;display:block}
.bworld .bthe:hover,.bworld .bthe:focus-visible{border-color:var(--phosphor-dim)}
.btip{cursor:help;border-bottom:1px dotted var(--dimmer)}
.bmlink{cursor:pointer;border-bottom:1px dotted rgba(255,157,46,.5)}
.bmlink:hover,.bmlink:focus-visible{color:var(--gold);border-bottom-color:var(--gold)}
.btip:hover,.btip:focus-visible{color:var(--phosphor)}
.bbody{min-width:0;flex:1}
.bplanet{font-size:13px;letter-spacing:2.4px;font-weight:700;color:var(--paper)}
.bmeta{font-size:10px;color:var(--dim);letter-spacing:.6px;margin-top:1px}
.bmission{font-size:11px;color:var(--amber);letter-spacing:1.6px;font-weight:700;margin:6px 0 8px;text-transform:uppercase}
.bvs{display:flex;align-items:stretch;gap:8px}
.bside{flex:1;min-width:0;border-left:2px solid var(--c);padding:2px 0 2px 8px;display:flex;flex-direction:column;gap:2px}
.brole{font-size:8.5px;letter-spacing:2px;color:var(--dimmer)}
.bwho{display:inline-flex;align-items:center;gap:5px;font-size:12px;white-space:nowrap}
.bwho .fmark.badge{width:16px;height:16px}
.bside.open .bwho b{color:var(--dim);font-weight:400}
.bor{font-size:9px;color:var(--dimmer);letter-spacing:1px;margin:0 2px}
.bside.open{flex-direction:column}
.btag{font-size:9.5px;letter-spacing:1px}
.bx{align-self:center;font-size:9px;letter-spacing:2px;color:var(--dimmer)}
@media(max-width:1280px){.bx{display:none}}
.bres{margin-top:8px;font-size:10.5px;color:var(--phosphor)}
td.ost{font-weight:700;white-space:nowrap}
td.ost.rcv{color:var(--blue)}
td.ost.rsv{color:var(--green)}
td.ost.wait{color:var(--red)}
/* a fought battle takes the winning Alliance's colour, and the victor's side is lit */
.bcard.won{border-color:var(--w);box-shadow:inset 4px 0 0 var(--w);
  background:linear-gradient(90deg,color-mix(in srgb,var(--w) 20%,var(--panel)),color-mix(in srgb,var(--w) 5%,var(--panel)) 75%)}
.bcard.won .bres{color:var(--w)}
.bside.win{border-left-width:4px;background:color-mix(in srgb,var(--c) 22%,transparent)}
.bside.win .brole,.bside.win .bwho b{color:var(--c)}
.bside.win .bwho b{font-size:13px;letter-spacing:.6px}
.bside.lost{opacity:.5}
@media(max-width:600px){.bcard{gap:10px;padding:10px}.bworld,.bworld .globe{flex-basis:76px;width:76px;height:76px}.bworld .bthe{width:24px;height:24px}.bvs{flex-direction:column;gap:6px}.bx{display:none}.bwho{white-space:normal}}

/* --------------------------------------------------- infrastructure */
.infra{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
@media(max-width:760px){.infra{grid-template-columns:1fr}}
.iblk{background:var(--panel);border:1px solid var(--edge);border-top:2px solid var(--c);padding:12px 14px}
.inm{font-size:10px;letter-spacing:2.6px;font-weight:700;margin-bottom:8px}
.ity{padding:6px 0;border-top:1px solid var(--edge)}
.ityh{display:flex;align-items:center;gap:6px;font-size:9.5px;letter-spacing:1.8px;color:var(--dim);text-transform:uppercase;margin-bottom:3px}
.ityh img{width:16px;height:16px;object-fit:contain}
.iit{display:flex;justify-content:space-between;gap:10px;font-size:11.5px;padding:1px 0 1px 22px}
.iit em{font-style:normal;color:var(--c)}
.iit.none{color:var(--dimmer)}

/* --------------------------------------------------------- points race */
.race{background:var(--panel);border:1px solid var(--edge);padding:14px 16px;margin-bottom:6px}
.rplot{position:relative}
.rplot svg{width:100%;height:auto;display:block}
.rax{font-size:10px;fill:var(--dimmer);letter-spacing:1px;font-family:var(--mono)}
.rax.now{fill:var(--phosphor)}
.rclab{font-size:10.5px;fill:var(--paper);letter-spacing:1.4px;font-family:var(--mono);font-weight:700}
.rval{fill:var(--dim);font-weight:400}
.rtip{position:absolute;top:8px;background:var(--void);border:1px solid var(--edge);padding:7px 10px;font-size:10.5px;pointer-events:none;min-width:150px}
.rtip b{display:block;letter-spacing:2px;color:var(--phosphor);margin-bottom:4px}
.rtip div{display:flex;align-items:center;gap:6px}
.rtip svg{width:12px;height:12px}
.rtip em{margin-left:auto;font-style:normal;color:var(--paper)}
.rrow{display:grid;grid-template-columns:2fr 1fr;gap:18px;align-items:center}
@media(max-width:900px){.rrow{grid-template-columns:1fr}}
.rbars{border-left:1px solid var(--edge);padding-left:16px}
@media(max-width:900px){.rbars{border-left:0;padding-left:0;border-top:1px solid var(--edge);padding-top:12px}}
.rbh{font-size:9.5px;letter-spacing:2px;color:var(--dim);margin-bottom:8px}
.rb{margin-bottom:10px}
.rbl{display:flex;align-items:center;gap:6px;font-size:10px;letter-spacing:2px;font-weight:700;margin-bottom:4px}
.rbl svg{width:12px;height:12px}
.rbt{display:flex;align-items:center;gap:2px;height:14px}
.rbs{display:block;height:100%;border-radius:0 4px 4px 0}
.rbs:first-child{border-radius:0}
.rbs:first-child:last-of-type{border-radius:0 4px 4px 0}
.rbs.sb{opacity:.45}
.rbt em{font-style:normal;margin-left:8px;font-size:13px;font-weight:700;color:var(--paper)}
.rbk2{display:flex;align-items:center;gap:6px;font-size:9px;letter-spacing:1.4px;color:var(--dimmer);margin-top:4px}
.rbs.key{width:14px;height:8px;background:var(--dim);border-radius:0}
.rbs.key.sb{margin-left:8px}
.sronly{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* ------------------------------------------------------------ map pins */
.pin{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;margin-left:5px;vertical-align:-3px;
  color:var(--dim);border:1px solid var(--edge);border-radius:3px;background:var(--void)}
.pin svg{width:8px;height:11px;fill:currentColor}
.pin:hover,.pin:focus-visible{color:var(--phosphor);border-color:var(--phosphor-dim)}
td.holocell{white-space:nowrap}
.holocell .pin{margin-left:2px;vertical-align:middle}
.drow .tglobe{width:42px;height:42px;border-radius:50%;flex:none;margin-left:auto}
/* the same room as a world's header frame, so a zoomed globe has space to grow */
.twlbox{position:relative;width:320px;height:300px;max-width:100%;margin:2px auto 4px;overflow:hidden}
.twld{position:absolute;left:50%;top:50%;width:168px;height:168px;display:block;border-radius:50%;
  box-shadow:0 0 22px rgba(141,255,90,.1)}
.twlbox .globe.twld{transform:translate(-50%,-50%) scale(var(--ws,1))}
@media(max-width:600px){.twlbox{width:270px;height:250px}.twld{width:132px;height:132px}}
.twldn{text-align:center;font-size:9.5px;letter-spacing:2.4px;color:var(--dim);margin-bottom:12px}
/* the whole world, spinning free, at the head of its panel */
.globe[data-zoom]{cursor:zoom-in}
/* the globe's frame keeps its size; a zoomed globe grows inside it and is clipped */
.wldorb{--z:1;position:relative;width:320px;height:300px;margin:12px auto 4px;overflow:hidden}
/* pinned to the top of the readout, so the globe is always in view as it turns
   to a place opened further down; the readout scrolls beneath it */
@media(min-width:601px){.wldorb{position:sticky;top:0;z-index:5;width:100%;max-width:none;margin:0 0 4px;
  background:linear-gradient(to bottom,var(--panel) 88%,transparent)}}
.wldhd{position:absolute;left:50%;top:50%;width:150px;height:150px;
  border-radius:50%;box-shadow:0 0 22px rgba(141,255,90,.1)}
.wldorb .globe.wldhd{transform:translate(-50%,-50%) scale(var(--ws,1))}
/* the Fleets sit still round their world, evenly spaced */
.orbring{position:absolute;inset:0;z-index:1;pointer-events:none}
.orbslot{position:absolute;left:50%;top:50%;width:0;height:0;
  transform:rotate(var(--a)) translateX(calc(var(--or, calc(75px * var(--ws, 1) + 40px)) * var(--z))) rotate(calc(-1 * var(--a)))}
/* orbital docks ride a closer orbit than the Fleets */
.orbslot.dock{--or:calc(75px * var(--ws, 1) + 19px)}
/* the dock circles its world on a close orbit; the Fleets hold station */
.dockspin{position:absolute;inset:0;animation:dockorbit 150s linear infinite}
@keyframes dockorbit{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
@media(prefers-reduced-motion:reduce),(max-width:860px){.dockspin{animation:none}}
/* the dock grows with the zoom, keeping its size against the world */
.orbdock{--ds:calc(15px * var(--z, 1));position:absolute;margin:calc(var(--ds) / -2) 0 0 calc(var(--ds) / -2);display:flex;flex-direction:column;align-items:center;gap:0;
  background:none;border:0;padding:0;cursor:pointer;pointer-events:auto;font:inherit;
  filter:none}
.orbdock svg{width:var(--ds);height:var(--ds);display:block;transition:transform .25s ease-out}
/* its Support Facility opened: the dock lights up and pulses */
.orbdock.lit{filter:drop-shadow(0 0 4px var(--dc)) drop-shadow(0 0 10px var(--dc))}
/* running lights on a dark hull, blinking out of step */
.orbdock .dl{fill:#ffd88a;animation:dockblink 2.4s steps(1) infinite}
.orbdock .dl.d2{animation-delay:-.8s;fill:#ff8a5a}
.orbdock .dl.d3{animation-delay:-1.6s}
@keyframes dockblink{0%,100%{opacity:1}55%{opacity:.15}}
@media(prefers-reduced-motion:reduce){.orbdock .dl{animation:none}}
.orbdock.lit svg{transform:scale(1.35)}
.orbdock.lit::after{content:"";position:absolute;inset:-6px;border-radius:50%;border:1.5px solid var(--phosphor);
  animation:docklit 1.4s ease-out infinite;pointer-events:none}
@keyframes docklit{from{transform:scale(.7);opacity:1}to{transform:scale(1.6);opacity:0}}
@media(prefers-reduced-motion:reduce){.orbdock.lit::after{animation:none}}
.orbdock:focus-visible{outline:1px solid var(--phosphor);outline-offset:2px}
.orbslot .flm{position:absolute;margin:-22px 0 0 -18px;pointer-events:auto}
.orbslot .fmw{width:24px}
/* the ship itself, projected, over its name */
.orbship{position:absolute;margin:-21px 0 0 -25px;display:flex;flex-direction:column;align-items:center;gap:1px;
  background:none;border:0;padding:0;cursor:pointer;pointer-events:auto;font:inherit}
.orbship .holo.row{width:50px;height:31px}
.orbship .holo.row::before{display:none}
.orbship b{font-size:8px;letter-spacing:.06em;color:rgb(var(--hrgb));text-shadow:0 0 6px rgba(var(--hrgb),.6)}
.orbship:focus-visible{outline:1px solid var(--phosphor);outline-offset:2px}
@media(max-width:600px){.wldorb{width:270px;height:250px}
  .wldhd{width:120px;height:120px}
  .orbslot{transform:rotate(var(--a)) translateX(calc(var(--or, calc(60px * var(--ws, 1) + 34px)) * var(--z))) rotate(calc(-1 * var(--a)))}
  .orbslot.dock{--or:calc(60px * var(--ws, 1) + 15px)}}
/* Theatre worlds circling each sun on the map */
.orbw{position:absolute;aspect-ratio:1;transform:translate(-50%,-50%) rotate(0deg);pointer-events:none;
  animation:orbspin linear infinite}
@keyframes orbspin{from{transform:translate(-50%,-50%) rotate(0deg)}to{transform:translate(-50%,-50%) rotate(360deg)}}
/* The world is a few pixels across and sits on its ring; the transparent
   padding keeps it big enough to point at. */
.orbw .orbg{position:absolute;left:100%;top:50%;aspect-ratio:1;height:auto;transform:translate(-50%,-50%);
  box-sizing:content-box;padding:5px;pointer-events:auto;cursor:pointer;
  filter:drop-shadow(0 0 1.5px rgba(211,232,198,.55))}
/* lifeless companions: seen, never pointed at */
.orbw .orbg.body{pointer-events:none;cursor:default;padding:0;filter:none}
/* Off the map a world keeps its infrastructure tier inside the slot it is
   given: the largest fills it, each slot fewer is a step smaller. */
.globe:not(.orbg){transform:scale(var(--ws,1))}
@media(prefers-reduced-motion:reduce),(max-width:860px){.orbw{animation:none}}

/* ---------------------------------------------------------------- map */
.mapview{display:flex;flex-direction:column;flex:1 1 auto;min-height:0;position:relative}
.mapwrap{flex:1 1 auto;min-height:0;display:grid;
         grid-template-columns:minmax(0,1fr);gap:10px}
.mapwrap>*{min-width:0}
.ophead{display:flex;align-items:flex-start;gap:9px;margin-bottom:9px}
.ophead u{flex:0 0 auto;width:26px;height:26px;background:var(--phosphor);
  -webkit-mask:var(--m) no-repeat center/contain;mask:var(--m) no-repeat center/contain}
.ophead > div{flex:1 1 auto;min-width:0}
.tipbody .oprow{padding:4px 0;border-bottom:1px solid rgba(232,239,229,.07)}
.tipbody .oprow:last-child{border-bottom:0}
.tipbody .oprow.small{padding:2px 0}
.tipbody .oprow.dim{opacity:.5}
.tipbody .oprow b{display:block;color:var(--paper);font-size:11px;letter-spacing:.4px}
.tipbody .oprow b i{color:var(--dimmer);font-style:normal;font-size:9.5px}
.tipbody .oprow s{display:block;text-decoration:none;color:var(--dim);font-size:10px;margin-top:1px}
.tipbody .oprow em{display:block;color:var(--dimmer);font-style:italic;font-size:9.5px;margin-top:1px}
.plrow2{display:flex;flex-wrap:wrap;gap:5px;margin-top:3px}
/* an attack type is a choice, so it reads as one */
.tipbody .oprow.pick{cursor:pointer;padding:5px 6px;margin:0 -6px;border-radius:3px;
  transition:background .12s}
.tipbody .oprow.pick:hover,.tipbody .oprow.pick:focus-visible{
  background:rgba(111,255,142,.10);outline:none}
.tipbody .oprow.pick:hover b{color:var(--phosphor)}
/* mission detail */
/* ---- mission types view ---- */

/* one mission, one box */
.mcard{background:var(--panel);border:1px solid var(--edge);border-radius:5px;
  padding:20px 22px 18px;margin-bottom:20px;scroll-margin-top:14px;max-width:96ch}
.mcardhead{border-bottom:1px solid var(--edge);padding-bottom:14px;margin-bottom:16px}
.mnum{font-size:9.5px;letter-spacing:2px;color:var(--dimmer)}
.mtitle{font-size:26px;letter-spacing:3px;color:var(--phosphor);font-weight:700;
  margin:4px 0 0;line-height:1.15;text-shadow:0 0 14px rgba(111,255,142,.25)}
.mcard .mflav{color:var(--dim);font-style:italic;font-size:12px;line-height:1.6;
  max-width:74ch;margin:10px 0 0}
.mgoal{display:flex;flex-wrap:wrap;gap:5px 26px;font-size:11.5px;color:var(--paper);margin-top:10px}
.mgoal b{color:var(--amber);font-weight:700}

/* the phase the campaign is standing on, in the schedule table */
tr.phnow td{background:rgba(111,255,142,.09);color:var(--phosphor)}
tr.ktgap td{color:var(--amber)}
/* outbound links to Games Workshop's own rules documents */
.exlink{color:var(--amber);text-decoration:none}
.exlink:hover{color:var(--phosphor)}
/* the campaign timeline on the state screen */
.tline{display:flex;gap:10px;overflow-x:auto;padding:2px 0 10px;margin-bottom:6px}
.tlp{flex:0 0 auto;min-width:150px;border:1px solid var(--edge);border-radius:4px;
  padding:10px 12px;background:var(--panel);color:var(--dim);font-size:11px;line-height:1.5}
.tlp.done{opacity:.5}
.tlp.now{border-color:var(--phosphor);background:rgba(111,255,142,.08);color:var(--paper)}
.tlp.next{border-style:dashed}
.tlph{font-size:9.5px;letter-spacing:2px;color:var(--dimmer);margin-bottom:6px}
.tlp.now .tlph{color:var(--phosphor)}
.tlpm{font-size:14px;letter-spacing:1px;font-weight:700;color:var(--paper);margin-bottom:4px}
.tlp.now .tlpm{color:var(--phosphor);text-shadow:var(--bloom)}
.tlpr{white-space:nowrap}
.tlpv{font-size:9px;letter-spacing:1.5px;color:var(--amber);margin-top:6px}
.tlpr b{color:var(--paper)}
.tlp.now .tlpr b{color:var(--phosphor)}
.tlpd{margin-top:7px;padding-top:6px;border-top:1px solid var(--edge);
  font-size:10px;letter-spacing:.6px;color:var(--amber)}
.tlpd.lift{color:var(--hostile);font-weight:700}
tr.phnow td b{color:var(--phosphor)}

/* Cross-reference out of a mission card to the page that carries its rules. */
.mxref{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.mxlink{font:inherit;font-size:10.5px;letter-spacing:1.6px;font-weight:700;
  text-decoration:none;color:var(--amber);background:rgba(255,157,46,.08);
  border:1px solid rgba(255,157,46,.35);border-radius:3px;padding:6px 10px}
.mxlink:hover{background:rgba(255,157,46,.16);border-color:var(--amber)}

/* rules / objectives / results inside the box */
.mpart{border:1px solid var(--edge);border-radius:4px;background:rgba(255,255,255,.02);
  padding:13px 15px;margin-bottom:11px;font-size:12px;line-height:1.6}
.mpart:last-child{margin-bottom:0}
.mparth{font-size:15px;letter-spacing:3px;color:var(--amber);font-weight:700;
  margin:0 0 11px;padding-bottom:8px;border-bottom:1px solid rgba(232,239,229,.14)}
.mpart .msub{font-size:12.5px;letter-spacing:1.4px;color:var(--phosphor);font-weight:700;
  margin:14px 0 5px}
.mpart p{margin:0 0 7px;color:var(--paper)}
.mpart p:last-child{margin-bottom:0}
.mpart ul{margin:0 0 7px;padding-left:16px;color:var(--paper)}
.mpart li{margin:3px 0}
.mpart .kw{color:var(--amber)}
.mpart .mflav{color:var(--dim);font-style:italic;margin:0 0 7px;font-size:11.5px}
.mdisp{border-color:var(--phosphor-dim);background:rgba(111,255,142,.05)}
.dispgrid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:4px 0 12px}
.dispside{border:1px solid var(--edge);border-radius:3px;padding:9px 11px;
  background:rgba(0,0,0,.25)}
.dispwho{font-size:9.5px;letter-spacing:2px;color:var(--dimmer);margin-bottom:4px}
/* The objective the grid produces is the headline. The disposition pairing that
   produced it sits above in small type, so a player can see the derivation
   rather than having to trust it. */
.dispobj{font-size:19px;letter-spacing:2px;color:var(--phosphor);font-weight:700;
  line-height:1.18;text-shadow:0 0 12px rgba(141,255,90,.32)}
.dispfrom{font-size:9px;letter-spacing:1.5px;color:var(--dimmer);margin-bottom:5px;
  line-height:1.45}
.dispfrom b{color:var(--amber);font-weight:700}
.dispnone{font-size:13px;letter-spacing:1.4px;color:var(--dimmer);font-weight:700}
/* what the mission scores, once the Warmaster has written it */
.disprule{margin-top:9px;padding-top:8px;border-top:1px solid rgba(232,239,229,.14);
  font-size:11.5px;line-height:1.6;color:var(--paper)}
.disprule p{margin:0 0 6px}
.disprule p:last-child{margin-bottom:0}
.disprule ul{margin:0 0 6px;padding-left:16px}
.disprule li{margin:3px 0}
.disprule .msub{font-size:11px;letter-spacing:1.2px;color:var(--amber);font-weight:700;
  margin:8px 0 4px}
.disppend{margin-top:9px;padding-top:8px;border-top:1px solid rgba(232,239,229,.14);
  font-size:10px;letter-spacing:1.2px;color:var(--dimmer);font-style:italic}
.mdispnone{border-color:var(--edge);background:rgba(255,255,255,.02)}
@media(max-width:700px){.dispgrid{grid-template-columns:1fr}}
.dispwhy{color:var(--dim)!important;font-style:italic;font-size:11.5px}
/* ---- terrain layouts ---- */
.layouts{margin:0 0 12px}
.laytabs{display:flex;gap:6px;margin-bottom:10px}
.laytab{font:inherit;font-size:12px;letter-spacing:2px;font-weight:700;cursor:pointer;
  background:var(--panel);border:1px solid var(--edge);color:var(--dim);
  padding:6px 16px;border-radius:3px}
.laytab:hover{border-color:var(--phosphor);color:var(--phosphor)}
.laytab.on{background:rgba(111,255,142,.12);border-color:var(--phosphor);color:var(--phosphor)}

/* ---------------------------------------------------- boarding actions */
/* Two tabs over two long prose documents, each laid out the way MISSION TYPES
   is: a jump nav over a stack of .mcard sections. The cards, their headers and
   their .mpart blocks are the mission view's own styles, reused rather than
   restated, so the two screens cannot drift apart. */
.batabs{margin-bottom:14px}
.bapane{display:none}
.bapane.on{display:block}
.bafront{max-width:96ch;margin-bottom:20px}
.banotes{border-top:1px solid var(--edge);margin-top:22px;padding-top:14px}
.baseq{margin-top:22px;border-top:1px solid var(--edge);padding-top:12px;max-width:96ch}
.baseq summary{cursor:pointer;font-size:11px;letter-spacing:2px;font-weight:700;color:var(--dim)}
.baseq summary:hover{color:var(--phosphor)}
.barule{border:0;border-top:1px solid var(--edge);margin:16px 0}
/* a card's flavour line is markdown, so it arrives wrapped in a paragraph */
.mcard .mflav .bap{margin:0}
/* the deployment map. Scales to the card and never forces a sideways scroll. */
.bamap img{display:block;width:100%;height:auto;max-width:760px;margin:0 auto;
  border:1px solid var(--edge);border-radius:3px;background:#0b0f0c}

/* rendered markdown. Sized against the rest of the text views rather than
   given its own scale, so a rules page reads as part of the same screen. */
.babody{font-size:12.5px;line-height:1.6;color:var(--text)}
.babody .bap{margin:0 0 9px}
.babody .bal{margin:0 0 10px;padding-left:18px}
.babody .bal li{margin:0 0 4px}
.babody .bal .sub{margin:4px 0 4px;padding-left:16px}
.babody .sec{margin:16px 0 8px}
.babody .bq,.mpart .bq{margin:0 0 12px;padding:8px 12px;border-left:2px solid var(--phosphor-dim);
  background:rgba(111,255,142,.05);color:var(--dim)}
.babody .bq .bap:last-child,.mpart .bq .bap:last-child{margin-bottom:0}
.babody code{font-size:11.5px;color:var(--amber)}
.babody b,.mpart p b,.mpart li b{color:var(--phosphor)}
.laybox{display:none}
.laybox.on{display:block}
.lay{display:block;background:#070b09;border:0;border-radius:0}
/* the board sits in its own viewport so it can be pushed about and zoomed,
   the way the campaign map can */
.laypan{position:relative;width:100%;max-width:380px;
  /* the panel is the board's own 44:60, so a fitted board leaves no dead
     black. A fixed height left 27% of the panel empty at 390px. */
  /* --ar comes from the image each panel holds; the fallback is the board's
     own 44:60, for a panel that somehow has none */
  aspect-ratio:var(--ar,44/60);height:auto;max-height:74vh;overflow:auto;
  background:#070b09;border:1px solid var(--edge);border-radius:3px;
  cursor:grab;
  /* the swipe is only ours once the board is zoomed and has somewhere to go.
     At fit there is nothing to pan, and swallowing the gesture stopped a
     phone reader scrolling the page past a diagram half the screen tall. */
  touch-action:pan-y;
  -webkit-user-select:none;user-select:none;
  scrollbar-width:thin;scrollbar-color:var(--edge) transparent}
.laypan.zoomed{touch-action:none}
.laypan.dragging{cursor:grabbing}
.laypan::-webkit-scrollbar{width:9px;height:9px}
.laypan::-webkit-scrollbar-track{background:transparent}
.laypan::-webkit-scrollbar-thumb{background:var(--edge);border:2px solid transparent;
  background-clip:content-box;border-radius:0}
.laypan::-webkit-scrollbar-thumb:hover{background:var(--phosphor-dim);background-clip:content-box}
.laypan::-webkit-scrollbar-corner{background:transparent}
.laybar{display:flex;align-items:center;gap:6px;margin-top:7px;max-width:330px}
.laybtn{font:inherit;font-size:10px;letter-spacing:1.4px;font-weight:700;cursor:pointer;
  background:var(--panel);border:1px solid var(--edge);color:var(--dim);
  padding:3px 9px;border-radius:3px;line-height:1.5}
.laybtn:hover{border-color:var(--phosphor);color:var(--phosphor)}
.layzoom{font-size:9.5px;letter-spacing:1.2px;color:var(--dimmer);margin-left:auto;
  font-variant-numeric:tabular-nums}
/* what the layout is for: the Force Disposition pairing it serves, who else
   plays it, and the page it is printed on */
.laymatch b{color:var(--phosphor);font-weight:700;letter-spacing:.8px}
.mnote{font-size:10.5px;line-height:1.5;color:var(--amber);margin:0 0 9px}
.laynone{padding:14px;color:var(--dimmer);font-size:10px;text-align:center}
.laykey{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:8px;font-size:9.5px;
  letter-spacing:.8px;color:var(--dimmer);align-items:center}
.laykey i{display:inline-block;width:10px;height:10px;margin-right:5px;vertical-align:-1px;
  border-radius:2px}
/* the terrain editor's own legend; the mission pages show printed maps now */
.laykey .ka{background:rgba(179,72,66,.55);border:1px solid #b34842}
.laykey .kd{background:rgba(62,114,186,.55);border:1px solid #3e72ba}
.laysrc{margin-left:auto;font-style:italic}
.laynote{margin:10px 0 0!important;color:var(--dim)!important;font-size:11px;font-style:italic}
/* ---- terrain editor ---- */
.tedit{display:grid;grid-template-columns:230px minmax(0,1fr) 230px;gap:16px;align-items:start}
@media(max-width:1150px){.tedit{grid-template-columns:200px minmax(0,1fr)}
  .tedit .tcol-r{grid-column:1/-1;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}}
@media(max-width:820px){.tedit{grid-template-columns:1fr}}
.tblk{background:var(--panel);border:1px solid var(--edge);border-radius:3px;padding:12px 13px;
  margin-bottom:12px}
.tblkh{font-size:10px;letter-spacing:2.2px;font-weight:700;color:var(--phosphor);
  margin-bottom:9px;padding-bottom:7px;border-bottom:1px solid var(--edge)}
.tblkh s{float:right;text-decoration:none;color:var(--dimmer);font-weight:400;
  letter-spacing:1px}
.tsel{width:100%;font:inherit;font-size:10px;letter-spacing:0;background:#0b110e;
  color:var(--paper);border:1px solid var(--edge);border-radius:3px;padding:7px 8px;cursor:pointer}
.tsel:focus{outline:none;border-color:var(--phosphor)}
.tabc{display:flex;gap:6px;margin-top:9px}
.tabc button{flex:1 1 0;font:inherit;font-size:12px;letter-spacing:2px;font-weight:700;
  cursor:pointer;background:#0b110e;border:1px solid var(--edge);color:var(--dim);
  padding:6px 0;border-radius:3px}
.tabc button:hover{border-color:var(--phosphor);color:var(--phosphor)}
.tabc button.on{background:rgba(111,255,142,.12);border-color:var(--phosphor);color:var(--phosphor)}
.tmeta{margin-top:9px;font-size:9.5px;letter-spacing:.8px;color:var(--dimmer);line-height:1.7}
.tmeta b{color:var(--dim);font-weight:700}
.tpal{display:flex;flex-direction:column;gap:6px}
.tpb{display:flex;align-items:center;gap:9px;font:inherit;text-align:left;cursor:pointer;
  background:#0b110e;border:1px solid var(--edge);border-radius:3px;padding:7px 9px;color:var(--paper)}
.tpb:hover:not(:disabled){border-color:var(--phosphor)}
.tpb:disabled{opacity:.34;cursor:not-allowed}
.tpb .sw{flex:0 0 auto;width:26px;height:26px;display:block}
.tpb .swg{display:block;width:26px;height:26px;overflow:visible}
.tpb .sw.area .swg *{fill:rgba(150,158,152,.14);stroke:#8c948e;stroke-width:1.1}
.tpb .sw.dense .swg *{fill:#2f8a45;stroke:#4f9e5e;stroke-width:.6}
.tpb .sw.light .swg *{fill:#e3c927;stroke:#e3c927;stroke-width:.6}
.tpb .sw.obj .swg *{stroke-width:0}
.tpb .sw .swg .ticks{display:none}
.tpb .nm{flex:1 1 auto;min-width:0}
.tpb .nm b{display:block;font-size:10px;letter-spacing:.9px;font-weight:700}
.tpb .nm s{display:block;text-decoration:none;font-size:9px;letter-spacing:.7px;color:var(--dimmer);
  margin-top:2px}
.tpb .ct{flex:0 0 auto;font-size:11px;font-weight:700;color:var(--dim);font-variant-numeric:tabular-nums}
.tpb .ct.full{color:var(--phosphor)}
.tbtns{display:flex;flex-wrap:wrap;gap:6px}
.tbtn{font:inherit;font-size:9.5px;letter-spacing:1.4px;font-weight:700;cursor:pointer;
  background:#0b110e;border:1px solid var(--edge);color:var(--dim);padding:6px 9px;border-radius:3px}
.tbtn:hover:not(:disabled){border-color:var(--phosphor);color:var(--phosphor)}
.tbtn:disabled{opacity:.34;cursor:not-allowed}
.tbtn.warn:hover:not(:disabled){border-color:#b34842;color:#d2716b}
.tboardwrap{background:#070b09;border:1px solid var(--edge);border-radius:3px;padding:10px}
/* Turned a quarter turn above the phone breakpoint, so the frame is landscape
   and fills the width instead of leaving the sides empty. */
@media(min-width:861px){#tboard{aspect-ratio:65/50;height:auto !important;max-height:calc(100vh - 232px)}}
#tboard{display:block;width:100%;height:min(calc(100vh - 232px),1040px);
  touch-action:none;cursor:default}
#tboard .lb{fill:#0d1512}
#tboard .g1{stroke:rgba(232,239,229,.075);stroke-width:.05}
#tboard .g6{stroke:rgba(232,239,229,.16);stroke-width:.1}
#tboard .ghalf{stroke:rgba(232,239,229,.45);stroke-width:.15;stroke-dasharray:1.1 .9}
#tboard .gdiag{stroke:rgba(232,239,229,.32);stroke-width:.13;stroke-dasharray:.9 .8}
#tboard #tterr{pointer-events:none}
#tboard .gterr{stroke:rgba(232,239,229,.72);stroke-width:.2;stroke-dasharray:.45 .55;
  stroke-linecap:round}
#tboard .terra{fill:#b34842;stroke:#f4f8f5;stroke-width:.14}
#tboard .terrd{fill:#3e72ba;stroke:#f4f8f5;stroke-width:.14}
#tboard .lf{fill:none;stroke:rgba(232,239,229,.34);stroke-width:.22}
#tboard .za{fill:rgba(179,72,66,.10);stroke:rgba(179,72,66,.5);stroke-width:.18;stroke-dasharray:.8 .6}
#tboard .zd{fill:rgba(62,114,186,.10);stroke:rgba(62,114,186,.5);stroke-width:.18;stroke-dasharray:.8 .6}
#tboard .obj{fill:none;stroke:rgba(232,239,229,.55);stroke-width:.2}
#tboard .obj.oa{stroke:rgba(179,72,66,.8)}
#tboard .obj.od{stroke:rgba(62,114,186,.8)}
#tboard .rule{font-size:1.15px;fill:var(--dimmer);letter-spacing:.06px}
#tboard .tp{cursor:grab}
#tboard .tp>rect,#tboard .tp>polygon,#tboard .tp>circle{stroke-width:.2}
#tboard .tp.obj *{stroke-width:0}
#tboard .tp.obj .objsel{fill:none;stroke:var(--phosphor);stroke-width:.3}
#tboard .tp.dense>polygon,#tboard .tp.light>polygon{stroke-width:.09}
#tboard .tp .ticks line{stroke-width:.14;opacity:.75}
/* terrain areas are the printed footprints: grey, outline only, so the
   ruins standing on them keep the eye */
#tboard .tp.area>rect,#tboard .tp.area>polygon{fill:rgba(150,158,152,.10);
  stroke:#8c948e;stroke-dasharray:.7 .45}
#tboard .tp.area .tplab{fill:#8c948e;font-size:.85px;text-anchor:start;opacity:.85}
#tboard .tp.dense>rect,#tboard .tp.dense>polygon,#tboard .tp.dense>circle{
  fill:#1f6b33;stroke:#4f9e5e}
#tboard .tp.dense .ticks{stroke:#0f3d1d;fill:none}
#tboard .tp.light>rect,#tboard .tp.light>polygon,#tboard .tp.light>circle{
  fill:#e3c927;stroke:#f6e56b}
#tboard .tp.light .ticks{stroke:#6b5c0d;fill:none}
#tboard .tp.sel:not(.obj)>rect,#tboard .tp.sel:not(.obj)>polygon,
#tboard .tp.sel:not(.obj)>circle{stroke:var(--phosphor);stroke-width:.3}
#tboard .tplab{font-size:1.05px;fill:#e6f2e3;text-anchor:middle;font-weight:700;
  letter-spacing:.05px;pointer-events:none;opacity:.72}
.tinsp{font-size:10px;letter-spacing:.9px;color:var(--dim);line-height:1.9}
.tinsp b{color:var(--paper)}
.tnone{color:var(--dimmer);font-style:italic;font-size:10px;letter-spacing:.7px}
.tkeys{margin-top:10px;font-size:9px;letter-spacing:.7px;color:var(--dimmer);line-height:1.9}
.tkeys kbd{display:inline-block;min-width:14px;text-align:center;background:#0b110e;
  border:1px solid var(--edge);border-radius:2px;padding:1px 4px;margin-right:5px;
  font:inherit;font-size:8.5px;color:var(--dim)}
.tsaved{margin-top:6px;padding-top:6px;border-top:1px solid var(--edge)}
.tmode button{letter-spacing:1.4px;font-size:10px}
#tboard .zpt{cursor:grab;stroke:#f4f8f5;stroke-width:.16}
#tboard .zpt.za{fill:#b34842}
#tboard .zpt.zd{fill:#3e72ba}
#tboard .zpt.sel{stroke:var(--phosphor);stroke-width:.3}
/* in zone mode the zone body is a click target for adding a corner */
#tboard .zedit{cursor:copy}
#tboard .rleash{stroke:var(--phosphor);stroke-width:.14;stroke-dasharray:.5 .4;opacity:.7;
  pointer-events:none}
#tboard .rgrip{fill:var(--phosphor);stroke:#0d1512;stroke-width:.16;cursor:grab}
.mpart .depmap{margin:0;padding:12px 13px;background:#070b09;border:1px solid var(--edge);
  border-radius:3px;overflow-x:auto;font-size:10px;line-height:1.2;color:var(--dim);white-space:pre}
.tipbody .mflav{color:var(--dim);font-style:italic;margin:6px 0 4px;font-size:10.5px;line-height:1.5}
.tipbody .msub{font-size:10px;letter-spacing:1.2px;color:var(--phosphor);font-weight:700;
  margin:9px 0 3px}
.tipbody p{margin:0 0 6px;color:var(--paper);line-height:1.55}
.tipbody p:last-child{margin-bottom:0}
.tipbody .kw{color:var(--amber)}
.tipbody .depmap{margin:4px 0 0;padding:8px 9px;background:#070b09;border:1px solid var(--edge);
  border-radius:3px;overflow-x:auto;font-size:8px;line-height:1.15;color:var(--dim);
  white-space:pre}
.plpip{font-size:9px;letter-spacing:.6px;color:#fff;background:var(--c);
  padding:1px 5px;border-radius:2px;font-weight:700}
/* an Operation with nothing to act on */
.opsrow.dead{opacity:.5;cursor:not-allowed}
.opsrow.dead u{background:#8a4b4b}
.opsrow.dead b{color:#b06a6a;letter-spacing:1.6px}
.mapcol{display:flex;flex-direction:column;min-height:0;min-width:0}

.boardscroll{overflow:hidden;border:1px solid var(--edge);background:var(--void);
  flex:1 1 auto;min-height:240px;display:flex}
.crtbox{position:relative;padding:10px 12px;
  flex:1 1 auto;min-width:0;display:flex;align-items:center;justify-content:center}
.board{
  container-type:inline-size;
  position:relative;
  /* The board's size is set by the zoom (on .pan); everything inside is sized
     in cqw or %, so it all redraws at native resolution when that changes. */
  width:100%; height:auto; aspect-ratio:1600/1124;
  /* deep space: two faint nebular casts over a near-black ground */
  background:
    radial-gradient(ellipse 66% 54% at 21% 17%, rgba(34,74,88,.26) 0%, rgba(34,74,88,0) 63%),
    radial-gradient(ellipse 58% 48% at 79% 74%, rgba(58,38,80,.22) 0%, rgba(58,38,80,0) 60%),
    radial-gradient(ellipse 96% 76% at 50% 48%, #0a1013 0%, #06090b 56%, #030506 100%);
}
/* Grain, so those gradients never band. Inline turbulence — no network fetch. */
.board::before{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:0;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  /* real screen pixels: the board is laid out, never transform-scaled, so the
     grain keeps a constant density at every zoom without any compensation */
  background-size:180px 180px;
  opacity:.055; mix-blend-mode:screen;
}
.board::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(ellipse at 50% 45%,rgba(6,8,7,0) 0%,rgba(6,8,7,.45) 100%);
}
/* ---- starfield ---- */
.sky{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.sky i{position:absolute;display:block;aspect-ratio:1;border-radius:50%;
  background:var(--c,#e6f0e9);opacity:var(--o);
  box-shadow:0 0 .1cqw rgba(210,230,255,.45)}
.sky i.b1{box-shadow:0 0 .20cqw rgba(206,228,255,.50)}
.sky i.b2{box-shadow:0 0 .30cqw rgba(214,232,255,.60), 0 0 .85cqw rgba(150,190,255,.22)}
/* named deep-sky stars — see deepStars() for where they sit and why */
.sky i.red{background:#ff6a4d;
  box-shadow:0 0 .32cqw rgba(255,120,86,.55), 0 0 1.05cqw rgba(255,74,52,.20)}
.sky i.blue{background:#9ec8ff;
  box-shadow:0 0 .32cqw rgba(150,195,255,.55), 0 0 1.05cqw rgba(90,150,250,.20)}
.sky i.twk{animation:twk 6s ease-in-out infinite}
@keyframes twk{
  0%,100%{opacity:calc(var(--o) * .22)}
  50%    {opacity:var(--o)}
}
@media(prefers-reduced-motion:reduce){.sky i.twk{animation:none;opacity:var(--o)}}
.board > svg{position:absolute;inset:0;width:100%;height:100%;z-index:2}
/* the campaign's warp routes — dashed, as the printed board drew them */
.board .link{stroke:rgb(126 196 160 / .34);stroke-width:1.15;stroke-dasharray:7 7;
  stroke-linecap:round;vector-effect:non-scaling-stroke}
/* orbit rings — ours, in phosphor, over the board's own faint ones */
.board .orbit{fill:none;stroke-linecap:round;vector-effect:non-scaling-stroke}
/* the three inner rings are solid; only the outermost stays dashed, and the
   ORBIT LOCATION caption rides just inside it */
.board .orbit.o1{stroke:rgb(111 255 142 / .52);stroke-width:2.0}
.board .orbit.o2{stroke:rgb(111 255 142 / .38);stroke-width:1.8}
.board .orbit.o3{stroke:rgb(111 255 142 / .27);stroke-width:1.6}
.board .orbit.o4{stroke:rgb(111 255 142 / .21);stroke-width:1.6;stroke-dasharray:3 12}
.board .orbpath{fill:none;stroke:none}
.board .orblab{fill:rgb(111 255 142 / .8);font-family:var(--mono);font-size:5.6px;
  letter-spacing:.85px;text-anchor:middle;
  pointer-events:none;user-select:none}
.node{z-index:3}
/* ---- overlay painted into the board's own printed furniture ---- */
.ov{position:absolute;inset:0;z-index:3}
.pl{position:absolute;transform:translate(-50%,-50%);display:flex;align-items:center;
    justify-content:center;font-weight:700;line-height:1;color:#0a0f0c;
    border-radius:1px;box-shadow:0 0 0 1px rgba(0,0,0,.45);pointer-events:none}
.slot{position:absolute;transform:translate(-50%,-50%);border-radius:50%;
      display:flex;align-items:center;justify-content:center;font-weight:700;
      line-height:1;color:#06120b;pointer-events:none}
.slot.free{background:transparent;border:1px dashed rgba(232,239,229,.30);color:transparent}
.plate{position:absolute;transform:translate(-50%,-50%);text-align:center;
       background:rgba(6,10,8,.65);border:1px solid var(--edge);padding:.16cqw .5cqw .22cqw;
       white-space:nowrap;cursor:pointer;border-radius:2px;line-height:1.18;
       transition:border-color .14s,background .14s}
.plate:hover{border-color:var(--phosphor);background:rgba(6,10,8,.85)}
.plate[aria-pressed="true"]{border-color:var(--phosphor);box-shadow:0 0 0 1px var(--phosphor)}
.plate .nm{display:block;color:var(--paper);letter-spacing:.033cqw;font-weight:700}
.plate .sy{display:block;color:var(--dimmer);letter-spacing:.021cqw}
.plate.act{border-color:var(--hostile);animation:pulse 2.1s ease-in-out infinite}
.plate.act .nm{color:var(--hostile)}
.fl{position:absolute;transform:translate(-50%,0);display:flex;gap:.75cqw;z-index:5;
    align-items:flex-start}
.flm{display:flex;flex-direction:column;align-items:center;gap:.12cqw;cursor:pointer;
     background:none;border:0;padding:0 .2cqw;font:inherit;border-radius:2px}
.flm .fmi{display:block;position:static;inset:auto;
       width:100%;
       aspect-ratio:1;height:auto;transition:opacity .16s ease-out;
       overflow:visible;filter:drop-shadow(0 1px 2px rgba(0,0,0,.85))}
.flm .fmi text{font-family:var(--mono);letter-spacing:0}
.flm b{font-size:.5cqw;letter-spacing:.06em;color:#fff;font-weight:700;
       white-space:nowrap;line-height:1;text-shadow:0 0 3px #000,0 0 2px #000}
.flm:hover .fmi,.flm:focus-visible .fmi{
       filter:drop-shadow(0 0 3px var(--fc)) drop-shadow(0 0 9px var(--fc))}
.flm:hover,.flm:focus-visible{background:none;outline:none}
/* a fleet's reachable routes, laid over the dotted ones already there */
.board .reach line{stroke:var(--rc,#6fff8e);stroke-width:2.6;stroke-linecap:round;
  vector-effect:non-scaling-stroke;filter:drop-shadow(0 0 3px var(--rc))}
/* fleet readout: roster rows */
.fplayer{font-size:11px;letter-spacing:1.1px;color:var(--paper);margin-top:3px}
.fstation{font-size:10.5px;color:var(--dim);margin-top:6px}
/* each part of a fleet readout sits in its own block */
.tipbody .fblk{border:1px solid var(--edge);background:rgba(255,255,255,.025);
  border-radius:4px;padding:10px 11px;margin-bottom:9px}
.tipbody .fblk:last-child{margin-bottom:0}
.tipbody .fblk > .twh:first-child{margin-top:0}
.tipbody .fsec{font-size:9px;letter-spacing:1.4px;color:var(--amber);font-weight:700;
  margin:9px 0 3px}
.tipbody .frow{display:flex;gap:8px;justify-content:space-between;font-size:10.5px;
  line-height:1.45;color:var(--paper)}
.tipbody .frow em{font-style:normal;color:var(--dimmer);font-variant-numeric:tabular-nums}

/* ---- fleet hull holograms ----------------------------------------------
   A projector housing. The canvas inside draws the ship; ::before lays the
   scanlines over it and ::after is the glow pool the projection stands in.
   Both overlays are decoration, so neither takes a pointer event, and the
   scanlines follow the CRT switch like everything else on the page. */
.holo{position:relative;display:block;overflow:hidden;pointer-events:none}
.holo canvas{display:block;width:100%;height:100%}
.holo.big::before,.holo.row::before{content:"";position:absolute;inset:0;
  background:repeating-linear-gradient(to bottom,
    rgba(141,255,90,.11) 0 1px,transparent 1px 3px)}
body.nocrt .holo::before{display:none}
.holo.big::after{content:"";position:absolute;left:50%;bottom:3px;width:58%;height:11%;
  transform:translateX(-50%);border-radius:50%;
  background:radial-gradient(ellipse at center,
    rgba(141,255,90,.32) 0%,rgba(141,255,90,.09) 46%,transparent 72%)}
/* the projector at the head of a fleet readout */
.holo.big{height:140px;margin:0 0 9px;border:1px solid var(--edge);border-radius:3px;
  background:radial-gradient(ellipse at 50% 60%,rgba(141,255,90,.06),transparent 66%),#060a08}
/* a thumbnail beside a row of the dispositions table */
.holo.row{width:54px;height:34px}
td.holocell{width:54px;padding-right:0}
/* The fleet's number on its ship. The housing is pointer-events:none, so the
   tooltip lives on .holowrap, which wraps the housing and this badge. */
.holowrap{position:relative;display:inline-block;vertical-align:middle;border-radius:3px}
.holowrap:focus-visible{outline:1px solid var(--phosphor);outline-offset:2px}
.holowrap.big{display:block}
.holonum{position:absolute;right:3px;bottom:2px;pointer-events:none;
  font-size:10px;font-weight:700;line-height:1;color:rgb(var(--hrgb));
  text-shadow:0 0 6px rgba(var(--hrgb),.6)}
.holowrap.big .holonum{font-size:15px;right:8px;bottom:6px}
/* ---- faction marks -----------------------------------------------------
   A faction badge is painted, not drawn: a block of phosphor masked to the
   logo's own alpha, the same trick the operation icons use. The artwork keeps
   no colour of its own, so it matches the panel exactly and costs one small
   greyscale file instead of a mesh and a canvas.

   The scanlines are IN the fill rather than laid over it. A mask clips
   everything painted on the element, so an overlay would be clipped away with
   the mark itself. */
.fmark{display:inline-block;background:repeating-linear-gradient(to bottom,
    var(--fc, var(--phosphor)) 0 2px, color-mix(in srgb, var(--fc, var(--phosphor)) 42%, transparent) 2px 3px);
  -webkit-mask:var(--m) no-repeat center/contain;
  mask:var(--m) no-repeat center/contain;
  filter:drop-shadow(0 0 3px color-mix(in srgb, var(--fc, var(--phosphor)) 50%, transparent));
  animation:fmark 5.3s linear infinite}
body.nocrt .fmark{background:var(--fc, var(--phosphor))}
.fac{color:var(--fc)}
a.wh{color:inherit;text-decoration:none}
a.wh:hover,a.wh:focus-visible{color:var(--phosphor)}
/* an unsteady projector: a slow sag, two brief dropouts, never fully out */
@keyframes fmark{
  0%,100%{opacity:.93} 7%{opacity:.7} 9%{opacity:.96}
  38%{opacity:.87} 40%{opacity:.58} 43%{opacity:.9}
  71%{opacity:1} 73%{opacity:.74} 76%{opacity:.94}}
@media(prefers-reduced-motion:reduce){.fmark{animation:none;opacity:.93}}
.fmark.badge{width:22px;height:22px;vertical-align:-6px;margin-right:7px}
.fmark.crest{width:44px;height:44px;float:left;margin:-2px 10px 4px 0}
.mcardhead .fmark.crest{width:48px;height:48px;margin:0;float:none}
.mcardhead:has(.fmark.crest){display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:12px;align-items:center}
.mcardhead:has(.fmark.crest) > :not(.fmark){grid-column:2;min-width:0}
.mcardhead:has(.fmark.crest) > .fmark.crest{grid-column:1;grid-row:1 / span 2}
.mcardhead:has(.fmark.crest):not(:has(.mnum)) > .fmark.crest{grid-row:1}
@media(max-width:420px){.mcardhead:has(.fmark.crest) .mtitle{letter-spacing:2px}}
td:has(> .fmark.badge){white-space:nowrap}
.laytabs{flex-wrap:wrap;row-gap:6px}
/* a hull with nothing baked for it yet keeps the housing, so adding the STL
   later changes the picture and not the layout */
.holo.dead{display:flex;align-items:center;justify-content:center;text-align:center}
.holo.dead span{font-size:8.5px;letter-spacing:1.5px;color:var(--dimmer);
  border:1px dashed var(--edge);border-radius:2px;padding:4px 7px}
.holo.row.dead span{font-size:6px;letter-spacing:.4px;padding:2px 3px}
/* over a map marker: the disc and its number stay the primary marker, and the
   hull fades in over them on hover or focus. A hologram permanently lit at
   this size would be an unreadable smudge and would bury the fleet number. */
.fmw{position:relative;display:block;width:1.85cqw;aspect-ratio:1}
.fmw .holo.mark{position:absolute;inset:0;width:100%;height:100%;
  opacity:0;transition:opacity .16s ease-out}
.flm:hover .holo.mark,.flm:focus-visible .holo.mark,.flm[aria-pressed="true"] .holo.mark{opacity:1}
.flm:hover .fmi,.flm:focus-visible .fmi{opacity:.28}
@media(prefers-reduced-motion:reduce){.fmw .holo.mark{transition:none}}
/* the board's fleet marker, at a fixed size in the world readout */
.flrow{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:flex-start}
.detail .flm{gap:3px;padding:0 2px}
.detail .fmw{width:30px}
.detail .flm b{font-size:9.5px}
.detail .flm.inl{display:inline-flex;flex-direction:row;gap:4px;padding:0;vertical-align:middle}
.detail .flm.inl .fmw{width:18px}
.detail .flm.inl b{font-size:10px}
@media(prefers-reduced-motion:reduce){
  .plate.act{animation:none;box-shadow:0 0 0 2px rgba(255,93,108,.45),0 0 14px 3px rgba(255,93,108,.28)}
}
/* ---- hover details + points tracker ---- */
.hot{position:absolute;transform:translate(-50%,-50%);border-radius:50%;cursor:help;
     border:1px solid transparent;transition:border-color .12s,box-shadow .12s}
.hot:hover,.hot:focus-visible{border-color:var(--phosphor);box-shadow:0 0 10px rgba(111,255,142,.45)}
.tip{position:fixed;z-index:60;max-width:360px;background:var(--panel);
     border:1px solid var(--phosphor-dim);padding:9px 11px 10px;pointer-events:none;
     box-shadow:0 6px 26px rgba(0,0,0,.75);font-size:11px;line-height:1.5}
.fcrest{display:inline-flex;align-items:center;vertical-align:middle;cursor:help;border-radius:3px}
.fcrest:focus-visible{outline:1px solid var(--phosphor);outline-offset:2px}
/* One floating label for every [data-ftip] element. Same z-index as .tip, so it
   paints above the overlay (59 on a phone, 60 on a desktop and appended later). */
.ftip{position:fixed;z-index:60;pointer-events:none;max-width:260px;
  padding:4px 8px;border:1px solid var(--edge);border-radius:3px;
  background:rgba(8,12,9,.94);color:var(--phosphor);
  font-size:10px;letter-spacing:1.2px;font-weight:700;white-space:nowrap}
/* the readout column renders the same content as a hover tip */
.tipbody .tt{font-size:12px}
.tip .tpin,.tipbody .tpin{margin-top:9px;padding-top:7px;border-top:1px solid var(--edge);
     font-size:8.5px;letter-spacing:1.3px;color:var(--phosphor-dim)}
.tip .tt,.tipbody .tt{font-size:10px;letter-spacing:2px;color:var(--phosphor);font-weight:700}
.tip .tk,.tipbody .tk{font-size:9px;letter-spacing:1.4px;color:var(--amber);margin-top:2px}
.tip .tf,.tipbody .tf{color:var(--dim);font-style:italic;margin:6px 0 5px}
.tip ul,.tipbody ul{margin:0;padding-left:14px;color:var(--paper)}
.tip li,.tipbody li{margin:3px 0}
.tip .tw2,.tipbody .tw2{color:var(--dimmer);margin-top:6px;font-size:10px}
.tip .twh,.tipbody .twh{font-size:9px;letter-spacing:1.4px;color:var(--amber);margin:8px 0 3px;font-weight:700}
.tip .twr,.tipbody .twr{display:flex;gap:7px;margin:3px 0;font-size:10.5px;line-height:1.45}
.tip .twr b,.tipbody .twr b{flex:0 0 auto;color:var(--phosphor);font-weight:700;min-width:22px}
.tip .twr i,.tipbody .twr i{color:var(--paper);font-style:normal;font-weight:700}
.tip .twr span,.tipbody .twr span{color:var(--dim)}
/* ---- unit stat card: hover or focus a unit's Wahapedia link ---- */
.statcard{position:fixed;z-index:70;left:0;top:0;width:max-content;
  max-width:min(420px,calc(100vw - 16px));overflow-y:auto;overflow-x:hidden;
  background:var(--panel);border:1px solid var(--phosphor-dim);
  border-top:2px solid var(--phosphor);padding:7px 9px 8px;pointer-events:none;
  box-shadow:0 6px 26px rgba(0,0,0,.75);font-family:var(--mono);font-size:10.5px;
  line-height:1.4;color:var(--paper);text-align:left;overscroll-behavior:contain;
  scrollbar-width:thin;scrollbar-color:var(--edge) transparent;animation:scin .09s ease-out}
.statcard.touch{pointer-events:auto}
@keyframes scin{from{opacity:0}to{opacity:1}}
@media(prefers-reduced-motion:reduce){.statcard{animation:none}}
/* The hover stat card's renderer, laid out as a page in the overlay. */
.statcard.ovsheet{position:static;width:auto;max-width:none;pointer-events:auto;
  box-shadow:none;margin:0}
.ovsheetlink{display:block;margin-top:12px;text-align:right;font-size:10px;letter-spacing:1px;color:var(--amber)}
/* the name reads as the sheet's title: centred, and clear of the invulnerable
   save, which keeps its corner whatever the name's length */
.statcard .sch{position:relative;display:flex;align-items:center;justify-content:center;
  min-height:20px;padding:0 46px 4px;margin-bottom:4px;border-bottom:1px solid var(--edge)}
.statcard .sch b{color:var(--phosphor);font-size:13px;letter-spacing:1.6px;
  font-weight:700;text-shadow:var(--bloom);text-align:center}
.statcard .scinv{position:absolute;right:0;top:0;color:var(--amber);font-weight:700;
  border:1px solid var(--amber);padding:0 5px;font-size:10.5px;white-space:nowrap}
.statcard .sch2{font-size:9px;letter-spacing:1.4px;color:var(--amber);font-weight:700;margin:6px 0 1px}
.statcard .scw{overflow-x:auto}
.statcard table{border-collapse:collapse;width:100%;font-size:10.5px;table-layout:auto}
.statcard th,.statcard td{padding:1px 5px;border-bottom:1px solid var(--edge);
  text-align:center;vertical-align:top;white-space:nowrap}
.statcard th{background:transparent;color:var(--dim);font-size:8.5px;letter-spacing:1px;
  font-weight:700;text-shadow:none}
/* the profile line is the sheet's headline: its M/T/SV/W/LD/OC labels are read
   as often as the numbers under them, so they are not shrunk to caption size */
.statcard .scp th{font-size:10.5px;color:var(--dim);letter-spacing:.6px}
.statcard tr:last-child td{border-bottom:0}
.statcard .scn{text-align:left;white-space:normal;padding-left:0}
.statcard .scp td{font-size:12px;font-weight:700;color:var(--phosphor)}
.statcard .scp td.scn{font-size:10px;font-weight:400;color:var(--paper)}
.statcard td.scn i{display:block;font-style:normal;font-size:8.5px;color:var(--dimmer);letter-spacing:.3px}
.statcard .scab{color:var(--dim);margin:2px 0}
.statcard .scab em{font-style:normal;color:var(--phosphor-dim);font-size:8.5px;letter-spacing:1px;margin-right:4px}
.statcard .sckw{color:var(--dimmer);font-size:9px;letter-spacing:.3px}
/* ---- the full datasheet: the wording, only in the overlay level ---- */
.statcard .scabx{margin:2px 0 6px;color:var(--paper);white-space:normal;line-height:1.5}
.statcard .scabx > b:first-child{color:var(--phosphor)}
.statcard .scabx ul,.ruletip ul{margin:3px 0 3px 17px}
.statcard.ovsheet .sch b{font-size:16px}
.statcard.ovsheet{font-size:11.5px}
.statcard.ovsheet table{font-size:11.5px}
.statcard.ovsheet .scp th{font-size:11.5px}
/* a named rule: its wording on hover or focus, Wahapedia's page on a click */
a.rk{color:var(--amber);cursor:help;text-decoration:none}
a.rk:hover,a.rk:focus-visible{color:var(--phosphor)}
.ruletip{position:fixed;z-index:72;left:0;top:0;width:max-content;
  max-width:min(400px,calc(100vw - 16px));max-height:min(60vh,420px);overflow-y:auto;
  background:var(--panel);border:1px solid var(--phosphor-dim);border-top:2px solid var(--amber);
  padding:8px 10px;font-family:var(--mono);font-size:11px;line-height:1.5;color:var(--paper);
  box-shadow:0 6px 26px rgba(0,0,0,.75);pointer-events:none;overscroll-behavior:contain;
  scrollbar-width:thin;scrollbar-color:var(--edge) transparent}
.ruletip.touch{pointer-events:auto}
.ruletip .rth{display:flex;align-items:baseline;justify-content:space-between;gap:10px;
  color:var(--amber);font-weight:700;letter-spacing:1.2px;font-size:10px;margin-bottom:4px}
.ruletip .rth i{font-style:normal;color:var(--dimmer);font-weight:400}
.ruletip .rtl{display:block;margin-top:7px;color:var(--amber);font-size:10px;letter-spacing:1px}
/* ---- sector star: dull at rest, brightens when the sector is hovered ---- */
/* --g is the star's live intensity (0-1), driven per frame from cursor
   proximity times a slow breath. Everything below is derived from it. */
/* --g is the star's live intensity (0-1), driven per frame from cursor
   proximity times a slow breath. Colour comes from the star's house-canon
   class (see build.mjs) — harsh, temperate or neutral. */
/* --g is the star's live intensity. It now rests at 1 and rises to 1.3 with
   cursor proximity, so the resting look IS the old hover look and hovering is
   30% brighter again. Opacity is fixed at 1 — it would clip above that — so
   the extra headroom comes from brightness and the glow layers. */
.sun{--g:1;
     --c1:#fffdf3; --c2:#fff7dc; --c3:#ffeab0; --c4:#f6cf74;
     --h1:255 214 110; --h2:255 196 74; --h3:255 176 44;
     position:absolute;border-radius:50%;z-index:1;cursor:pointer;
     transform:translate(-50%,-50%) scale(calc(1 + .1*var(--g)));
     background:radial-gradient(circle at 50% 50%,var(--c1) 0%,var(--c2) 40%,var(--c3) 68%,var(--c4) 100%);
     opacity:1;
     /* keeps the agreed response: resting level IS the old hover look, and the
        cursor lifts it a further 30%. Cheap here — it repaints only the small
        core, not a 60px blur. */
     filter:saturate(calc(1 + .08*var(--g))) brightness(calc(1.08*var(--g)));
     /* a tight white-hot bloom hugging the disc, under the wide halo */
     box-shadow:0 0 4px 1px rgb(var(--h1) / .95),0 0 12px 3px rgb(var(--h2) / .55),0 0 26px 6px rgb(var(--h3) / .28)}
/* the molten face, painted per frame over the gradient it replaces */
.suncv{position:absolute;inset:0;width:100%;height:100%;border-radius:50%;display:block}
/* The halo is a separate painted layer whose breath moves only opacity and
   transform, both of which the compositor handles. Driving it through
   box-shadow instead re-rasterised a 60px blur on all 13 stars every frame and
   held the whole page to ~23fps. */
.sun::after{
     content:"";position:absolute;left:50%;top:50%;
     width:620%;height:620%;border-radius:50%;pointer-events:none;z-index:-1;
     background:radial-gradient(circle closest-side,
        rgb(var(--h1) / .66) 0%, rgb(var(--h1) / .44) 17%,
        rgb(var(--h2) / .26) 34%, rgb(var(--h3) / .11) 56%,
        rgb(var(--h3) / 0) 72%);
     transform:translate(-50%,-50%) scale(calc(.80 + .22*var(--g)));
     opacity:calc(.42 + .50*var(--g));
     will-change:opacity,transform}
/* hot, harsh star — the Dead Lands hook */
.sun.harsh{--c1:#ffffff; --c2:#eef6ff; --c3:#cfe3ff; --c4:#9dc2f5;
           --h1:170 210 255; --h2:130 180 255; --h3:96 150 245}
/* temperate star — Xenoflora Jungle */
.sun.temperate{--c1:#fffdf0; --c2:#fff4c9; --c3:#ffe08e; --c4:#efb84e;
           --h1:255 205 120; --h2:255 180 70; --h3:246 158 40}
/* ageing red star — assigned directly in 12-stellar-house-canon.md */
.sun.dying{--c1:#fff2e9; --c2:#ffcdb2; --c3:#ff9163; --c4:#d9512c;
           --h1:255 150 110; --h2:255 104 64; --h3:224 62 34}
.sun.lit{--g:1.3}

/* ---- our own points tracker, laid over the printed one ---- */
/* The strip is exactly 55 equal cells with no padding or gaps, so a marker's
   left offset maps precisely onto its space. Markers are children of it. */
.trkui{position:absolute;z-index:4;display:flex;flex-direction:column;overflow:visible;
       background:rgba(6,10,8,.65);border:1px solid var(--edge);border-radius:2px}
/* one lane per alliance, each back-filled to its campaign total */
/* the lane divider is an inset shadow, not a border — a border would add to
   the box and leave the last lane a pixel shorter than the other two */
.trkrow{flex:1 1 0;min-height:0;display:flex;cursor:help;
        box-shadow:inset 0 -1px 0 rgba(232,239,229,.10)}
.trkrow:last-child{box-shadow:none}
.trkrow:hover,.trkrow:focus-visible{outline:1px solid var(--phosphor);outline-offset:-1px}
.trkrow i{flex:1 1 0;min-width:0;background:#131c19;
         border-right:1px solid rgba(232,239,229,.07)}
.trkrow i:last-child{border-right:0}
.trkrow i.m5{background:#1e2f28;border-right-color:rgba(232,239,229,.20)}
.trkticks{position:absolute;z-index:4}
.trkticks span{position:absolute;transform:translateX(-50%);font-size:.41cqw;
       letter-spacing:.4px;color:var(--dim);top:0;white-space:nowrap}
/* ---- our plates, laid over the board's printed ones ---- */
/* Plates are pure backdrops; every cell/disc is positioned absolutely from the
   detected geometry, so nothing depends on flex resolving inside a % box. */
.pnl{--capfs:.64cqw;font-size:var(--capfs);
     position:absolute;border-radius:5px;background:rgba(7,11,9,.65);
     border:1px solid rgba(232,239,229,.18);
     box-shadow:0 2px 9px rgba(0,0,0,.55),inset 0 0 0 1px rgba(0,0,0,.3);z-index:2}
.pcap{position:absolute;z-index:3;color:var(--dim);letter-spacing:.056cqw;font-weight:700;
      white-space:nowrap;line-height:1;transform:translate(0,-100%);pointer-events:none;
      text-shadow:0 1px 3px rgba(0,0,0,.9)}
.gc{position:absolute;transform:translate(-50%,-50%);border-radius:0;
    background:rgba(255,255,255,.65);
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.22);z-index:3;
    display:flex;align-items:center;justify-content:center;line-height:1;
    font-weight:700;color:#fff;text-shadow:0 1px 1px rgba(0,0,0,.45)}
.rlab{position:absolute;transform:translate(-50%,-50%);z-index:3;color:var(--dimmer);
     line-height:1;font-weight:700}
.dsc{position:absolute;transform:translate(-50%,-50%);border-radius:50%;z-index:3;
     display:flex;align-items:center;justify-content:center;line-height:1;
     font-weight:700;color:#06120b;background:#1b2621;
     box-shadow:inset 0 0 0 1px rgba(232,239,229,.10)}
.dsc.free{background:rgba(255,255,255,.65);border:0;
     box-shadow:inset 0 0 0 1px rgba(0,0,0,.25)}
.dsc.ico{background:transparent no-repeat center/100% auto;box-shadow:none;opacity:.65}
.dsc.badge{background:rgba(255,255,255,.65);box-shadow:none;overflow:hidden}
.dsc.badge::after{content:"";position:absolute;inset:0;border-radius:50%;
     background:var(--own);
     -webkit-mask:var(--m) no-repeat center/100% auto;
     mask:var(--m) no-repeat center/100% auto}
.opico{display:inline-block;width:1.2em;height:1.2em;vertical-align:-.25em;margin-right:6px;
       background:currentColor;-webkit-mask:var(--m) no-repeat center/contain;
       mask:var(--m) no-repeat center/contain}
.hot.dhot{position:absolute;transform:translate(-50%,-50%);border-radius:50%;
     background:transparent;z-index:4}
/* ---- pan + zoom viewport ---- */
/* The zoom lives on its own layer, separate from the map box. */
/* The map is laid out at its true size and this box simply lets it overflow.
   Zoom resizes the board itself, so the browser re-renders every glyph, rule
   and vector at the new size — nothing is ever a scaled-up bitmap. */
.panwrap{position:relative;overflow:auto;touch-action:none;cursor:grab;
  width:100%;height:100%;
  display:flex;align-items:safe center;justify-content:safe center;
  -webkit-user-select:none;user-select:none}
/* livery is shared with every other scroller — see the scrollbar block below */
.panwrap.dragging{cursor:grabbing}
/* while a drag is live nothing anywhere may select, in case the pointer
   leaves the map mid-drag */
body.dragging,body.dragging *{-webkit-user-select:none!important;user-select:none!important}
.pan{width:1600px;height:1124px;position:relative;flex:0 0 auto}
.zoomlab{font-size:9.5px;letter-spacing:1.4px;color:var(--dimmer);padding:5px 2px;
         min-width:52px;text-align:right;font-variant-numeric:tabular-nums}
/* ---- the board's top-right legend, rebuilt ---- */
.lgtitle{position:absolute;z-index:3;display:flex;align-items:flex-end;justify-content:flex-end;
  background:rgba(6,10,8,.65);padding-right:.4%;
  color:var(--phosphor);font-weight:700;letter-spacing:.2em;white-space:nowrap;line-height:1;
  border-bottom:1px solid var(--phosphor-dim);text-shadow:0 0 9px rgba(111,255,142,.35);
  padding-bottom:.35%}
.lgcap{position:absolute;z-index:4;color:var(--amber);font-weight:700;letter-spacing:.12em;
  background:rgba(6,10,8,.65);padding:.15% .4%;border-radius:2px;
  white-space:nowrap;line-height:1}
.pnl.lgpanel{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);
  padding:1% 1.2%;gap:.3% .6%}
.lgc u{flex:0 0 auto;width:2.1cqw;aspect-ratio:1;
  background:var(--phosphor);-webkit-mask:var(--m) no-repeat center/contain;
  mask:var(--m) no-repeat center/contain}
.lgc{display:flex;align-items:center;gap:7%;min-width:0;cursor:help;border-radius:3px;
  padding:0 2%;transition:background .12s}
.lgc:hover,.lgc:focus-visible{background:rgba(111,255,142,.11);outline:none}
.lgc i{flex:0 0 auto;width:2.3cqw;aspect-ratio:1;border-radius:50%;
  background:transparent no-repeat center/100% auto}
.lgc b{font-weight:700;letter-spacing:.04em;color:var(--paper);line-height:1.12;
  min-width:0;overflow:hidden;word-break:normal;overflow-wrap:anywhere}
/* Positioned so it paints ABOVE the board. .panwrap is position:relative, and
   a positioned element paints over a static one whatever the DOM order — so on
   a short window, where the map column overflows, the pan layer covered the
   whole bar and none of its buttons could be tapped. */
/* left:50% shrink-to-fit trap: an absolutely-positioned box with left:50% has
   its shrink-to-fit width computed against the containing block MINUS that
   50% offset, not the full width — on a narrow phone board this starves the
   strip to ~160px and flex-wrap stacks the buttons into three rows over the
   map. transform:translateX(-50%) only recentres the box; it does not give
   the width back. width:max-content + max-width keep it one row without
   letting it overflow the board. Do not "tidy" these away. */
.boardbar{position:absolute;left:50%;bottom:12px;transform:translateX(-50%);
          z-index:58;display:flex;flex-wrap:wrap;align-items:center;gap:10px;
          padding:7px 12px;border:1px solid var(--edge);border-radius:4px;
          background:rgba(10,12,10,.86);backdrop-filter:blur(3px);
          transition:opacity .18s ease,transform .18s ease;
          width:max-content;max-width:calc(100% - 16px)}
.boardbar.away{opacity:0;pointer-events:none;transform:translateX(-50%) translateY(10px)}
.shell.mapfull .boardbar{bottom:calc(env(safe-area-inset-bottom,0px) + 12px)}
.boardbar button{min-height:44px}
@media(max-width:860px){
  .boardbar .legend{display:none}
  .boardbar{flex-wrap:nowrap;gap:6px;padding:6px 8px}
  .boardbar .zoomlab{display:none}
}
.toggle{
  font:inherit;font-size:9.5px;letter-spacing:1.6px;background:var(--panel);
  border:1px solid var(--edge);color:var(--dim);padding:5px 10px;cursor:pointer;
}
.toggle:hover{border-color:var(--phosphor-dim);color:var(--paper)}
.toggle[aria-pressed="true"]{border-color:var(--phosphor);color:var(--phosphor)}
.node{
  position:absolute;transform:translate(-50%,-50%);
  background:rgba(11,15,13,.93);border:1px solid var(--edge);
  box-shadow:0 2px 10px rgba(0,0,0,.6);
  padding:5px 7px 6px;cursor:pointer;min-width:74px;
  transition:border-color .14s,transform .14s;
}
.node:hover{border-color:var(--phosphor-dim);transform:translate(-50%,-50%) scale(1.05);z-index:5}
.node[aria-pressed="true"]{border-color:var(--phosphor);z-index:6}
.node .pn{font-size:8.5px;letter-spacing:1.1px;color:var(--paper);white-space:nowrap;font-weight:700}
.node .bars{display:flex;gap:2px;margin-top:4px}
.node .bar{height:11px;flex:1;background:var(--raised);position:relative;overflow:hidden}
.node .bar i{position:absolute;left:0;right:0;bottom:0;display:block;background:var(--bc)}
.node .marks{display:flex;gap:3px;margin-top:4px;font-size:8px;color:var(--dimmer);letter-spacing:.4px}
.node .marks .sh{color:var(--amber)}
.node .marks .fl{color:var(--phosphor)}
.node.active{border-color:var(--hostile);animation:pulse 2.1s ease-in-out infinite}
.node.active .pn{color:var(--hostile)}
@keyframes pulse{
  0%,100%{box-shadow:0 0 0 0 rgba(255,93,108,.55),0 0 14px 2px rgba(255,93,108,.25)}
  50%{box-shadow:0 0 0 7px rgba(255,93,108,0),0 0 22px 5px rgba(255,93,108,.42)}
}
@media(prefers-reduced-motion:reduce){
  .node.active{animation:none;box-shadow:0 0 0 3px rgba(255,93,108,.4),0 0 18px 4px rgba(255,93,108,.3)}
}
.chip-active{
  position:absolute;top:-9px;left:50%;transform:translateX(-50%);
  background:var(--hostile);color:#1a0508;font-size:7.5px;letter-spacing:1.2px;
  padding:1px 5px;font-weight:700;white-space:nowrap;
}
.legend{display:flex;flex-wrap:wrap;gap:12px;font-size:9.5px;color:var(--dimmer);letter-spacing:.7px;margin-left:6px}
.legend b{font-weight:400}
.swatch{display:inline-block;width:9px;height:9px;vertical-align:-1px;margin-right:4px}

/* ------------------------------------------------------------- detail */
.opsrow{padding:2px 4px}
.opsrow u{flex:0 0 auto;width:21px;height:21px;background:var(--phosphor);
  -webkit-mask:var(--m) no-repeat center/contain;mask:var(--m) no-repeat center/contain}
.opsrow b{font-size:10px;letter-spacing:1.05px;color:var(--paper);font-weight:700;
  line-height:1.15;min-width:0;overflow-wrap:anywhere}
.detail{background:var(--panel);border:1px solid var(--edge);padding:14px 15px;
  height:100%;overflow-y:auto;border-radius:3px}
.detail.empty{display:flex;flex-direction:column;justify-content:center;align-items:center;
  text-align:center;border-style:dashed}
@media(max-width:1080px){.detail{position:static;max-height:none}}
.detail .dt{font-size:13px;letter-spacing:2.6px;color:var(--phosphor);font-weight:700}
.detail .ds{font-size:9.5px;letter-spacing:1.4px;color:var(--dimmer);margin-top:3px}
.dl{display:grid;grid-template-columns:auto 1fr;gap:5px 12px;margin-top:12px;font-size:11px}
.dl dt{color:var(--dimmer);letter-spacing:1px;font-size:9.5px;padding-top:2px}
.dl dd{color:var(--paper)}
/* ---- icon rows in the world readout ---- */
.dsec{margin-top:13px}
.dsec .dh{font-size:9.5px;letter-spacing:1.4px;color:var(--dimmer);margin-bottom:5px}
.drow{display:flex;align-items:center;gap:9px;padding:3px 4px;border-radius:3px;
      cursor:help;transition:background .12s}
.drow:hover,.drow:focus-visible{background:rgba(111,255,142,.09);outline:none}
.drow i{flex:0 0 auto;width:26px;height:26px;border-radius:50%;
        background:transparent no-repeat center/100% auto}
.drow i.own{box-shadow:0 0 0 1.5px var(--own),0 0 6px rgba(0,0,0,.6)}
.drow i.blank{border:1px dashed rgba(232,239,229,.3)}
/* the board's infrastructure disc, out of its absolute board placement */
.dsc.inl{position:relative;transform:none;z-index:auto;flex:0 0 auto;width:26px;height:26px;font-size:13px}
.dsc.inl.tipd{float:right;width:34px;height:34px;margin:-2px 0 4px 8px}
/* .drow i would otherwise out-rank these and blank the swatch */
.drow i.starswatch,.starswatch{position:relative;background:radial-gradient(circle at 50% 50%,#fffdf3 0%,#fff7dc 40%,#ffeab0 68%,#f6cf74 100%);
  box-shadow:0 0 10px 2px rgba(255,196,74,.4)}
.drow i.starswatch.harsh,.starswatch.harsh{background:radial-gradient(circle at 50% 50%,#fff 0%,#eef6ff 40%,#cfe3ff 68%,#9dc2f5 100%);
  box-shadow:0 0 10px 2px rgba(130,180,255,.45)}
.drow i.starswatch.temperate,.starswatch.temperate{background:radial-gradient(circle at 50% 50%,#fffdf0 0%,#fff4c9 40%,#ffe08e 68%,#efb84e 100%);
  box-shadow:0 0 10px 2px rgba(255,180,70,.45)}
.drow i.starswatch.dying,.starswatch.dying{background:radial-gradient(circle at 50% 50%,#fff2e9 0%,#ffcdb2 40%,#ff9163 68%,#d9512c 100%);
  box-shadow:0 0 10px 2px rgba(255,104,64,.45)}
.drow span{min-width:0}
/* a world readout's own places open in place */
.drow[data-inline]{cursor:pointer}
.drow[data-inline].open{background:rgba(141,255,90,.07);box-shadow:inset 2px 0 0 var(--phosphor)}
.ddrop{margin:0 0 10px 36px;padding:8px 10px;border-left:1px solid var(--phosphor-dim);background:rgba(141,255,90,.03)}
.drow b{display:block;font-weight:700;color:var(--paper);font-size:11px;line-height:1.25}
.drow s{display:block;text-decoration:none;color:var(--dimmer);font-size:9px;letter-spacing:.8px}
.plrow{display:flex;align-items:center;gap:7px;margin:3px 0;font-size:11px}
.plrow .lb{width:44px;color:var(--dimmer);font-size:9.5px;letter-spacing:1px}
.plrow .tr{flex:1;height:8px;background:var(--raised)}
.plrow .tr i{display:block;height:100%;background:var(--bc)}
.plrow .vv{width:14px;text-align:right;font-weight:700}
.tag{display:inline-block;border:1px solid var(--edge);padding:1px 6px;font-size:9.5px;color:var(--dim);margin:2px 3px 2px 0;letter-spacing:.6px}
.tag.b{border-color:var(--blue);color:var(--blue)}
.tag.r{border-color:var(--red);color:var(--red)}
.tag.g{border-color:var(--green);color:var(--green)}
.empty{color:var(--dimmer);font-size:11px;font-style:normal;padding:8px 0}

/* --------------------------------------------------------- engagement */
.eng{border:1px solid var(--hostile);background:rgba(255,93,108,.06);padding:12px 13px;margin-top:14px}
.eng .eh{font-size:10px;letter-spacing:2.4px;color:var(--hostile);font-weight:700;margin-bottom:8px}
.combat{border-top:1px solid var(--edge);padding-top:9px;margin-top:9px}
.combat.first{border-top:0;padding-top:0;margin-top:0}
.combat .cn{font-size:11.5px;font-weight:700}
.combat .cm{font-size:9.5px;color:var(--dimmer);letter-spacing:.8px;margin:2px 0 6px}
.mini{font-size:10px}
.mini .ms{color:var(--amber);letter-spacing:1.4px;font-size:9px;margin:6px 0 2px}
.mini .mu{display:flex;justify-content:space-between;gap:10px;color:var(--dim);padding:1px 0}
.mini .mu b{color:var(--paper);font-weight:400}

/* ------------------------------------------------------------- tables */
@media(max-width:600px){
  /* the one width where the columns cannot all fit, so the table may reach
     sideways again — vertically it still shows every row */
  .tw{overflow-x:auto}
}
/* A table is NOT its own viewport. It was overflow-x:auto, which makes it a
   scroll container, which sets its flex auto-minimum-size to zero — and as a
   flex item of .mainbody it was then crushed to a sliver. RULES was showing
   0 of 5 list-building constraints, 0 of 5 campaign operations and 0 of 6
   attack types; STATE was showing 2 of 9 fleet orders. The data was on the
   page and unreachable without finding a 60px scrollbox.
   Cells wrap instead, and the field scrolls. */
.tw{border:1px solid var(--edge);background:var(--panel)}
table{border-collapse:collapse;width:100%;font-size:11px;table-layout:auto}
th,td{text-align:left;padding:7px 11px;border-bottom:1px solid var(--edge);
  vertical-align:top}
/* headers wrap when there is no room. nowrap made "Crusade Leader / Warlord"
   and "Fortification Lines" force the whole table 108-305px past the screen
   edge at 390px. With table-layout:auto they still sit on one line wherever
   the width allows. */
th{color:var(--amber);font-size:9.5px;letter-spacing:1.6px;font-weight:700;background:var(--raised)}
tr:last-child td{border-bottom:0}
td b{font-weight:700}

/* ------------------------------------------------------------ rosters */
/* ---- crusade forces: one band per alliance, players in columns ---- */
.aband{margin:0 0 20px;border:1px solid var(--edge);border-top:2px solid var(--c);
  border-radius:3px;background:rgba(255,255,255,.012);padding:14px 15px 15px}
.abh{display:flex;align-items:baseline;flex-wrap:wrap;gap:10px 14px}
.abn{font-size:14px;letter-spacing:2.6px;font-weight:700;color:var(--c)}
.abt{font-size:11px;letter-spacing:1.4px;color:var(--dim);font-style:italic}
.abstat{margin-left:auto;display:flex;gap:16px}
.abstat span{display:flex;flex-direction:column;align-items:flex-end;line-height:1.3}
.abstat s{text-decoration:none;font-size:8.5px;letter-spacing:1.2px;color:var(--dimmer)}
.abstat b{font-size:13px;font-weight:700;color:var(--paper);font-variant-numeric:tabular-nums}
.abtag{font-size:10px;color:var(--dimmer);letter-spacing:.6px;margin:5px 0 0;line-height:1.5}
.acols{display:grid;gap:13px;margin-top:13px;
  grid-template-columns:repeat(auto-fit,minmax(290px,1fr));align-items:stretch}
.acols .rcard{display:flex;flex-direction:column;min-width:0}
.acols .rcard .rnote{margin-top:auto}
@media(max-width:760px){.acols{grid-template-columns:minmax(0,1fr)}
  .abstat{margin-left:0;width:100%;justify-content:flex-start}}
.rdet{margin-top:7px;padding-top:7px;border-top:1px solid var(--edge);
  display:flex;flex-direction:column;gap:2px}
.rdet s{text-decoration:none;font-size:8.5px;letter-spacing:1.4px;color:var(--dimmer)}
.rdet b{font-size:11px;font-weight:700;color:var(--paper);line-height:1.4}
.rdet .undecl{color:var(--dim);font-weight:400;font-style:italic}
.rkey{font-size:10px;color:var(--dim);margin-top:6px;line-height:1.5}
.funits{margin-top:6px}
.funits>summary{cursor:pointer;list-style:none;font-size:9px;letter-spacing:1.2px;
  color:var(--dim);padding:3px 0;border-top:1px solid var(--edge)}
.funits>summary::-webkit-details-marker{display:none}
.funits>summary::before{content:'\25B8 ';color:var(--phosphor)}
.funits[open]>summary::before{content:'\25BE '}
.funits>summary:hover{color:var(--phosphor)}
.rcard{background:var(--panel);border:2px solid var(--c);padding:14px 15px}
.rcard .rh{position:relative;text-align:center;min-height:46px;padding:0 66px;display:flex;align-items:center;justify-content:center}
.rcard .rh .stat{position:absolute;right:0;top:0}
/* the crest is centred on the whole header — team line, faction and warlord */
.rcard .rtop{position:relative}
/* bottom:4px, not 0: centred on the box the logo sits 2px low against the visible
   text, since the team line's box carries more empty space above its capitals.
   Tuned with .rm's -4px top margin collapsing through .rtop: give .rtop any top
   padding or border and that collapse stops, so re-measure this offset */
.rcard .rtop > .fmark.crest{position:absolute;left:0;top:0;bottom:4px;float:none;margin:auto 0}
.rcard .rf{font-size:18px;color:var(--fc,var(--c));letter-spacing:1.4px;font-weight:700;line-height:1.25}
.rcard .rhc{display:flex;flex-direction:column;align-items:center}
/* text a screen reader hears but the page does not show */
.srt{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;user-select:none}
.rcard .rwl{font-size:11px;font-weight:700;letter-spacing:1px;color:var(--gold);margin-top:6px;line-height:1.3}
.force .rdet{margin:2px 0 6px;padding-top:0;border-top:0}
.rcard .rn{font-size:11px;letter-spacing:1.6px;font-weight:700;color:var(--paper);margin-top:3px}
.rcard .rm{font-size:9px;color:var(--dim);text-align:center;letter-spacing:1.4px;margin:-4px 0 3px;padding:0 66px;text-transform:uppercase}
.rcard .rnote{font-size:10px;color:var(--amber);margin-top:8px;border-top:1px solid var(--edge);padding-top:8px;line-height:1.5}
.force{margin-top:11px;border-top:1px solid var(--edge);padding-top:10px}
.force .fh{display:flex;justify-content:space-between;font-size:9.5px;letter-spacing:1.4px;color:var(--amber);margin-bottom:5px}
.force .fh .fp{color:var(--paper);font-weight:700}
.usec{color:var(--dimmer);font-size:9px;letter-spacing:1.4px;margin:7px 0 2px}
.unit{display:flex;justify-content:space-between;gap:10px;font-size:10.5px;padding:1px 0}
.unit .un{color:var(--paper)}
.unit .up{color:var(--dim)}
.stat{font-size:9.5px;letter-spacing:.8px;padding:2px 6px;border:1px solid var(--edge);color:var(--dim)}
.stat.ok{border-color:var(--phosphor-dim);color:var(--phosphor)}
.stat.no{border-color:var(--amber);color:var(--amber)}
.prose{font-family:var(--sans);font-size:12.5px;line-height:1.62;color:var(--dim);max-width:70ch}
.kv{font-size:11px;color:var(--dim);margin:3px 0}
.kv b{color:var(--paper);font-weight:400}
.warnbox{border:1px solid var(--amber);background:rgba(255,179,71,.05);padding:11px 13px;font-size:11px;color:var(--paper);margin-bottom:16px;line-height:1.6}
.warnbox b{color:var(--amber);letter-spacing:1.2px}

/* ==================================================================== CRT */
/* The whole window is the tube, not just the map box — that is the only way
   the rail and the text views read as the same screen. Every layer is inside
   one pointer-events:none overlay, so nothing here makes a target harder to
   hit. Sits above the tooltip layer (z-index 60) deliberately: a tip drawn
   on a screen is still on the screen. */
.crt{position:fixed;inset:0;z-index:80;pointer-events:none;overflow:hidden}
body.nocrt .crt{display:none}
@media(max-width:860px){.crt,.crtbtn{display:none}}
.crt > *{position:absolute;inset:0;pointer-events:none}
/* fine scanlines, 1px on / 4px off */
.crt .lines{
  background:repeating-linear-gradient(to bottom,
    rgb(0 0 0 / .30) 0 1px, rgb(0 0 0 / 0) 1px 5px);
  mix-blend-mode:multiply;
  animation:crtflick 3.7s steps(1) infinite;
}
/* hairline interlace field, stepping between the two fields */
.crt .ilace{
  background:repeating-linear-gradient(to bottom,
    rgb(0 0 0 / .16) 0 .5px, rgb(0 0 0 / 0) .5px 3px);
  animation:crtilace 2.4s steps(2) infinite;
}
/* the refresh band, sweeping down */
.crt .roll{
  background:linear-gradient(to bottom,
    rgb(141 255 90 / 0) 0%, rgb(141 255 90 / .045) 42%,
    rgb(141 255 90 / .075) 50%, rgb(141 255 90 / .045) 58%,
    rgb(141 255 90 / 0) 100%);
  height:34%;top:-34%;
  animation:crtroll 13s linear infinite;
}
/* phosphor wash, blooming toward the centre of the tube */
.crt .tint{
  background:radial-gradient(ellipse 78% 66% at 50% 46%,
    rgb(141 255 90 / .052) 0%, rgb(141 255 90 / .018) 55%, rgb(141 255 90 / 0) 100%);
}
/* the tube corners */
.crt .vig{
  background:radial-gradient(ellipse 104% 100% at 50% 50%,
    rgb(2 5 3 / 0) 52%, rgb(2 5 3 / .20) 82%, rgb(2 5 3 / .58) 100%);
  box-shadow:inset 0 0 64px 14px rgb(2 5 3 / .48);
}
@keyframes crtroll{
  0%  {transform:translateY(0)}
  100%{transform:translateY(calc(100vh + 34%))}
}
@keyframes crtilace{
  0%  {transform:translateY(0)}
  50% {transform:translateY(1.5px)}
  100%{transform:translateY(0)}
}
/* mains hum, not a fade — the step is the point */
@keyframes crtflick{
  0%,100%{opacity:1}
  17%    {opacity:.82}
  34%    {opacity:1}
  51%    {opacity:.90}
  68%    {opacity:1}
  85%    {opacity:.86}
}
@media(prefers-reduced-motion:reduce){
  .crt .lines,.crt .ilace,.crt .roll{animation:none}
  .crt .roll{display:none}
}

/* every scroller wears the same phosphor livery — a stock OS scrollbar is
   the one element that visibly is not part of the tube */
/* scrollbar-width does not inherit, so every scroller is named explicitly —
   .nav is the loudest of them below 860px */
.mainbody,.detail,.ovlbody,.rail,.nav,.panwrap,.tw,.depmap{
  scrollbar-width:thin;scrollbar-color:var(--edge) transparent}
.mainbody::-webkit-scrollbar,.detail::-webkit-scrollbar,.ovlbody::-webkit-scrollbar,
.rail::-webkit-scrollbar,.nav::-webkit-scrollbar,.panwrap::-webkit-scrollbar,
.tw::-webkit-scrollbar,.depmap::-webkit-scrollbar{width:9px;height:9px}
.mainbody::-webkit-scrollbar-track,.detail::-webkit-scrollbar-track,
.ovlbody::-webkit-scrollbar-track,.rail::-webkit-scrollbar-track,
.nav::-webkit-scrollbar-track,.panwrap::-webkit-scrollbar-track,
.tw::-webkit-scrollbar-track,.depmap::-webkit-scrollbar-track{background:transparent}
.mainbody::-webkit-scrollbar-thumb,.detail::-webkit-scrollbar-thumb,
.ovlbody::-webkit-scrollbar-thumb,.rail::-webkit-scrollbar-thumb,
.nav::-webkit-scrollbar-thumb,.panwrap::-webkit-scrollbar-thumb,
.tw::-webkit-scrollbar-thumb,.depmap::-webkit-scrollbar-thumb{
  background:var(--edge);border:2px solid transparent;background-clip:content-box;
  border-radius:0}
.mainbody::-webkit-scrollbar-thumb:hover,.detail::-webkit-scrollbar-thumb:hover,
.ovlbody::-webkit-scrollbar-thumb:hover,.rail::-webkit-scrollbar-thumb:hover,
.nav::-webkit-scrollbar-thumb:hover,.panwrap::-webkit-scrollbar-thumb:hover,
.tw::-webkit-scrollbar-thumb:hover,.depmap::-webkit-scrollbar-thumb:hover{
  background:var(--phosphor-dim);background-clip:content-box}
.mainbody::-webkit-scrollbar-corner,.detail::-webkit-scrollbar-corner,
.panwrap::-webkit-scrollbar-corner{background:transparent}


/* ============================================================ map, mobile */
/* Phones only. On a 390x844 screen the board was rendering 304x214 — a fifth
   of the display — with 366px of chrome above it. Immersive mode gives the
   board the whole viewport.

   ORIENTATION POLICY. The reader is never asked to turn the phone. Held
   UPRIGHT, the map is simply built to work upright: the board takes the
   dominant share of the viewport and the readout sits under it, scrolled to.
   Turned SIDEWAYS, that is read as intent — the map goes immersive on its own
   and gives up the last of the chrome. Turning back restores the upright
   layout. Orientation itself is never locked; the phone stays the reader's.
   Everything here is inside a max-width:860px query, so the desktop layout is
   untouched. */
/* Declared OUTSIDE the query and BEFORE it. Every rule for these lives inside
   the max-width block, so without this they are unstyled divs on a desktop —
   the tap menu rendered into the map view. And the base must come first, or a
   later rule of equal specificity wins at every width and the button never
   appears at all. */
.fsbtn{display:none}
@media(max-width:860px){
  .fsbtn{display:inline-block}

  .shell.mapfull{padding:0;grid-template-columns:1fr;grid-template-rows:1fr}
  .shell.mapfull > .rail,
  .shell.mapfull .ticker,
  .shell.mapfull .head,
  .shell.mapfull .warnbox,
  .shell.mapfull .frame,
  .shell.mapfull .bracket{display:none}
  .shell.mapfull .main{grid-column:1;grid-row:1;margin:0;padding:0;overflow:hidden}
  .shell.mapfull .main::before{display:none}
  .shell.mapfull .mainbody{padding:0;overflow:hidden}
  .shell.mapfull .mapview{height:100%}
  .shell.mapfull .mapwrap{grid-template-columns:1fr;grid-template-rows:1fr;gap:0;height:100%}
  .shell.mapfull .boardscroll{border:0;min-height:0;height:100%}
  .shell.mapfull .crtbox{padding:0}

  /* ---- NOT full screen, on a phone: the board sits above the fold ----
     The readout used to stack in a second grid row below the map; it now
     lives in the navigator overlay instead, so this row is just the board's
     own. Upright is a first-class layout, not a consolation: the board
     claims the dominant share of the viewport instead of a 200px floor, so
     it is legible without any rotation. svh so the browser's collapsing
     address bar cannot resize the board mid-pan; vh first as the fallback.
     Desktop is untouched; all of this is inside the max-width query. */
  .shell:not(.mapfull) .mapwrap{grid-template-rows:minmax(200px,auto)}
  @media(orientation:portrait){
    .shell:not(.mapfull) .mapwrap{grid-template-rows:minmax(58vh,auto)}
    .shell:not(.mapfull) .mapwrap{grid-template-rows:minmax(58svh,auto)}
    .shell:not(.mapfull) .boardscroll{min-height:58vh}
    .shell:not(.mapfull) .boardscroll{min-height:58svh}
  }
  .shell:not(.mapfull) .mapview{flex:0 0 auto}
  .shell:not(.mapfull) .mainbody{overflow-y:auto}

}

/* =================================================================== boot */
/* The panel draws itself when a view comes up: the bezel rules grow out, the
   boxes slide in, the text types.

   Rows are staggered but deliberately OVERLAP. The row step (7-26ms) is far
   shorter than any single element's own animation (200-420ms), so a dozen
   rows are always in flight — a row never waits for the one above it to
   finish.

   NOTHING here is hidden by a static rule. Every element is only ever held
   back by an animation's backwards fill, so if the animations are disabled,
   fail, or are cut short, the page is simply *visible*. That is the failsafe:
   a half-drawn panel is impossible. */
@keyframes btchar{ from{opacity:0} to{opacity:1} }
@keyframes btbox { from{opacity:0;transform:translateY(9px)} to{opacity:1;transform:none} }
@keyframes btfade{ from{opacity:0} to{opacity:1} }
@keyframes btlineh{ from{transform:scaleX(0)} to{transform:scaleX(1)} }
@keyframes btlinev{ from{transform:scaleY(0)} to{transform:scaleY(1)} }
@keyframes btdraw { from{stroke-dashoffset:var(--len)} to{stroke-dashoffset:0} }
@keyframes btcaret{ 0%,49%{opacity:1} 50%,100%{opacity:0} }

/* one character. Phosphor does not snap on, so it is a short strike rather
   than an instant switch */
.bt{animation:btchar 46ms linear both}
/* a box slides in. .bo is the opacity-only variant for anything that already
   carries a transform of its own — every board plate is translate(-50%,-50%)
   and animating transform would fling it across the map */
.bb{animation:btbox 260ms cubic-bezier(.2,.72,.3,1) both}
.bo{animation:btfade 200ms linear both}
/* a whole text node at once, for everything past the character budget */
.bw{animation:btfade 220ms linear both}

/* the caret rides the heading and the ticker while they come up */
.bcar::after{content:'\2588';display:inline-block;margin-left:.1em;
  color:var(--phosphor);text-shadow:var(--bloom);
  animation:btcaret .46s steps(1) infinite}

/* the bezel draws left to right, inner rule chasing the outer, then the
   corner elbows strike in */
.frame.btgo::before{transform-origin:0 50%;animation:btlineh 300ms cubic-bezier(.2,.8,.2,1) both}
.frame.btgo::after {transform-origin:0 50%;animation:btlineh 300ms cubic-bezier(.2,.8,.2,1) 90ms both}
.frame.btgo i{animation:btfade 200ms linear 300ms both}
/* the field graticule grows down its gutter */
.main.btgo::before{transform-origin:50% 0;animation:btlinev 420ms cubic-bezier(.2,.8,.2,1) 130ms both}
/* the elbow draws itself, which is what a trace on a real set does */
.bracket.btgo path{animation:btdraw 380ms cubic-bezier(.3,.8,.3,1) 260ms both}
.bracket.btgo circle{animation:btfade 160ms linear 620ms both}

@media(prefers-reduced-motion:reduce){
  .bt,.bb,.bo,.bw,.frame.btgo::before,.frame.btgo::after,.frame.btgo i,
  .main.btgo::before,.bracket.btgo path,.bracket.btgo circle{animation:none}
  .bcar::after{display:none}
}

/* ---- the navigator overlay ------------------------------------------- */
/* Sits in the shell's main grid cell on a desktop, so the rail stays reachable
   beside it; takes the whole viewport on a phone, where the rail is a scrolling
   strip that would only steal height. */
.ovl{grid-column:2;grid-row:1;position:relative;z-index:60;
     display:flex;flex-direction:column;min-width:0;min-height:0;
     background:var(--void);border-left:1px solid var(--edge)}
.ovlbar{flex:0 0 auto;display:flex;align-items:center;gap:10px;
        padding:9px 12px;border-bottom:1px solid var(--edge);
        background:var(--raised)}
.ovlback,.ovlclose{font:inherit;font-size:11px;letter-spacing:1.4px;font-weight:700;
        min-height:44px;padding:0 12px;cursor:pointer;
        background:transparent;color:var(--amber);
        border:1px solid var(--edge);border-radius:3px}
.ovlback:hover,.ovlclose:hover,
.ovlback:focus-visible,.ovlclose:focus-visible{border-color:var(--phosphor);color:var(--phosphor)}
.ovlclose{margin-left:auto;color:var(--dim)}
.ovltitle{flex:1 1 auto;min-width:0;text-align:center;
        font-size:11px;letter-spacing:2px;font-weight:700;color:var(--phosphor);
        overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* THE only scroller in the overlay. Giving an inner block its own height is
   what produced two nested scrollers in the old phone sheet and left the body
   unreachable. */
.ovlbody{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;
        touch-action:pan-y;padding:14px 16px 28px;-webkit-overflow-scrolling:touch}
.ovlbody > .detail{height:auto;max-height:none;overflow:visible}
.ovlbody.ovlbody-pdf{overflow:hidden;display:flex;flex-direction:column}
.ovlbody.ovlbody-pdf .ovpdf{flex:1 1 auto;height:auto}
.ovpdfbar{display:flex;justify-content:flex-end;margin:0 0 8px}
.ovpdflink{font-size:10px;letter-spacing:1px;color:var(--amber)}
.ovpdf{display:block;width:100%;height:calc(100% - 28px);min-height:60vh;border:1px solid var(--edge);border-radius:3px;background:#fff}

/* SHOW ON MAP: while peeking, this bar is the way back to the readout. */
.ovpeekbar{position:fixed;left:50%;top:calc(env(safe-area-inset-top,0px) + 10px);transform:translateX(-50%);
  z-index:61;min-height:44px;padding:0 16px;font:inherit;font-size:11px;letter-spacing:1.4px;font-weight:700;
  color:var(--amber);background:rgba(8,12,9,.94);border:1px solid var(--amber);border-radius:4px;cursor:pointer}
.ovpeekbar:hover,.ovpeekbar:focus-visible{color:var(--phosphor);border-color:var(--phosphor)}
.ovpeekbtn{display:inline-block;margin:0 0 10px;min-height:44px;padding:0 14px;font:inherit;font-size:11px;
  letter-spacing:1.4px;font-weight:700;color:var(--amber);background:transparent;border:1px solid var(--amber);
  border-radius:3px;cursor:pointer}
.ovpeekbtn:hover,.ovpeekbtn:focus-visible{color:var(--phosphor);border-color:var(--phosphor)}

/* ---- index tiles ------------------------------------------------------ */
.ovindex{display:grid;gap:10px;margin-top:14px;
         grid-template-columns:repeat(auto-fill,minmax(210px,1fr))}
.ovtile{font:inherit;text-align:left;cursor:pointer;
        display:flex;flex-direction:column;gap:4px;
        padding:12px 13px;min-height:44px;
        background:rgba(255,255,255,.025);border:1px solid var(--edge);border-radius:3px}
.ovtile:hover,.ovtile:focus-visible{border-color:var(--phosphor);background:var(--raised)}
.ovcrest{display:block;margin-bottom:4px}
.ovtn{display:block;font-size:9px;letter-spacing:1.4px;color:var(--amber);font-weight:700}
.ovtt{display:block;font-size:12px;letter-spacing:1.6px;color:var(--phosphor);font-weight:700}
.ovts{display:block;font-size:10.5px;line-height:1.45;color:var(--dim)}
@media(max-width:860px){
  .ovindex{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px}
}
/* A mission tile carries a layout preview with its own A/B/C picker. The tile
   itself is a button and a button may hold no button, so the card is the grid
   item and the tile sits inside it, above the picker. */
.ovcard{display:flex;flex-direction:column;
        background:rgba(255,255,255,.025);border:1px solid var(--edge);border-radius:3px}
.ovcard:hover,.ovcard:focus-within{border-color:var(--phosphor)}
.ovcard > .ovtile{background:none;border:0;border-radius:0}
.ovcard > .ovtile:hover,.ovcard > .ovtile:focus-visible{background:none}
.ovprev{display:flex;align-items:center;gap:8px;padding:0 13px 11px}
.ovptabs{display:flex;flex-direction:column;gap:3px}
.ovptab{font:inherit;font-size:9px;letter-spacing:1.2px;font-weight:700;cursor:pointer;
        min-width:22px;padding:2px 0;color:var(--dimmer);background:transparent;
        border:1px solid var(--edge);border-radius:2px}
.ovptab:hover{color:var(--phosphor);border-color:var(--phosphor-dim)}
.ovptab.on{color:var(--phosphor);border-color:var(--phosphor);background:rgba(111,255,142,.12)}
.ovpimg{display:none;height:78px;width:auto;border:1px solid var(--edge);border-radius:2px}
.ovpimg.on{display:block}
.ovlays{display:flex;gap:6px;margin-top:8px}
.ovlaymap{display:block;height:62px;width:auto;border:1px solid var(--edge);border-radius:2px}
@media(max-width:860px){
  .ovindex--missions{margin-top:6px}
  .ovtile[data-ov="mission"]{padding:9px 10px;gap:3px}
  .ovtile[data-ov="mission"] .ovts{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
  .ovprev{padding:0 10px 9px;gap:6px}
  .ovpimg{height:46px}
  .ovptab{font-size:8px;min-width:18px}
  .ovlays{margin-top:5px;gap:4px}
  .ovlaymap{height:30px}
}

@media(max-width:860px){
  /* 59: above .boardbar (58) so the overlay still covers the board chrome, but
     below .tip (60), .statcard (70) and the CRT filter (80) so a stat card or
     tip opened from within the overlay is not hidden underneath it. */
  .ovl{position:fixed;inset:0;z-index:59;border-left:0;
       padding-top:env(safe-area-inset-top,0px);
       padding-bottom:env(safe-area-inset-bottom,0px)}
  .ovlbar{position:sticky;top:0;z-index:1;
          padding-left:max(12px,env(safe-area-inset-left,0px));
          padding-right:max(12px,env(safe-area-inset-right,0px))}
  .ovlbody{padding:12px 16px 32px}
}

[hidden]{display:none !important}
