/* ==========================================================================
   Icarus Field Guide — field-terminal theme.
   Dark drop-pod HUD: deep space-slate panels, ICARUS orange accents,
   angular uppercase headings, subtle scanlines. Single stylesheet.
   ==========================================================================

   PAGE CONTRACT — every page follows this so the site reads as one system.
   (See CLAUDE.md for the prose version.)

     • Structure: page_header($title, $navKey) → content → page_footer()
       (inc/layout.php). Page files never emit their own <head>/nav.
     • One <h1> per page, inside .pagehead (title left, optional primary
       .btn right). Design exceptions: home .herostrip, auth .authbox.
     • Headings: .sech (orange rule) for sections; .subhead / .subhead2 for
       sub-levels; .tierhead.tier-N for tier-scoped headings.
     • Tables: <table class="datatable"> inside <div class="tablescroll">;
       secondary columns get .col-opt so they fold away on phones.
     • Buttons: .btn / .btn-primary / .btn-ghost, grouped in .btnrow.
       Chips: .chip / .chiprow. Empty states: .empty. Muted text: .dim.
     • "Browse everything" pages render all rows once and filter client-side
       with state in the URL hash (items.php, tree.php, guides.php).
     • Colour/spacing come from the :root tokens below. Static styling lives
       here, not inline. Inline `style=` is only for per-element DATA — CSS
       custom properties (--accent), computed positions/sizes, progress
       widths — never for styling that could be a class.
   ========================================================================== */

:root {
  --bg: #0c0f12;
  --bg2: #11161b;
  --panel: #161d24;
  --panel2: #1c242d;
  --line: #29343f;
  --text: #d7dee6;
  --dim: #8b98a5;
  --orange: #e8862a;
  --orange-hi: #ffa94d;
  --green: #7fc25c;
  --red: #e06052;
  --blue: #58a6d8;
  --t1: #8bc34a;
  --t2: #58a6d8;
  --t3: #b07fe0;
  --t4: #e8862a;
  --t5: #e0556f;
  --to: #ffd75e;
  --mono: ui-monospace, "Cascadia Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  /* .wrap's side padding. Shared so full-bleed furniture (.pinbar) and the
     .topbar header can match it exactly instead of hardcoding a value that
     drifts at other widths. */
  --wrap-pad: 18px;
}

* { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; overflow-y: scroll; background: var(--bg);
  scrollbar-color: var(--line) var(--bg2);
}
body {
  margin: 0;
  min-height: 100vh;
  /* Layered, back to front: CRT scanlines, a faint HUD targeting grid, then
     the original top-right glow. All three are subtle enough to read as
     texture, not decoration — dense pages (item tables, the tech tree) need
     to stay legible, so this lives once here rather than per-page. */
  background:
    repeating-linear-gradient(180deg, rgba(0, 0, 0, .18) 0px, rgba(0, 0, 0, .18) 1px, transparent 1px, transparent 3px),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, .012) 0px, transparent 1px, transparent 48px),
    repeating-linear-gradient(180deg, rgba(255, 255, 255, .012) 0px, transparent 1px, transparent 48px),
    radial-gradient(1200px 500px at 70% -10%, #182029 0%, var(--bg) 60%);
  background-repeat: repeat, repeat, repeat, no-repeat;
  background-attachment: fixed;
  background-color: var(--bg);
  color: var(--text);
  font: 15px/1.55 system-ui, "Segoe UI", Roboto, sans-serif;
}
a { color: var(--orange-hi); text-decoration: none; }
a:hover { text-decoration: underline; }
code, pre { font-family: var(--mono); background: var(--bg2); border: 1px solid var(--line); border-radius: 4px; }
code { padding: 1px 5px; }
pre { padding: 10px 12px; overflow-x: auto; }
h1, h2, h3 { line-height: 1.2; }
h1 { font-size: 26px; margin: 6px 0 8px; letter-spacing: .5px; }
img { max-width: 100%; }
mark { background: rgba(232, 134, 42, .35); color: inherit; border-radius: 2px; }
::selection { background: rgba(232, 134, 42, .35); color: #fff; }
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--bg2); }
::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; border: 2px solid var(--bg2); }
::-webkit-scrollbar-thumb:hover { background: #384654; }

/* ------------------------------------------------------------- topbar */
.topbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px max(var(--wrap-pad), calc((100% - 1200px) / 2 + var(--wrap-pad)));
  background: linear-gradient(180deg, #12181f, #0e1318);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 rgba(232, 134, 42, .25), 0 6px 18px rgba(0, 0, 0, .35);
  position: sticky; top: 0; z-index: 50;
}
.brand { display: flex; align-items: center; gap: 9px; color: var(--orange); line-height: 1; }
.brand:hover { text-decoration: none; }
.brand-mark { display: block; width: 30px; height: 30px; flex: 0 0 30px; align-self: center; }
.brand-word { font-weight: 300; letter-spacing: 2.2px; font-size: 15px; color: var(--text); white-space: nowrap; }
.brand-word b { color: var(--orange); font-weight: 700; }
.mainnav { display: flex; flex-wrap: wrap; gap: 2px; }
.mainnav a {
  padding: 6px 6px; color: var(--dim); border-radius: 4px;
  font-size: 12px; letter-spacing: .6px; text-transform: uppercase; font-weight: 600;
}
.mainnav a:hover { color: var(--text); background: var(--panel); text-decoration: none; }
.mainnav a.active { color: var(--orange); border-bottom: 2px solid var(--orange); border-radius: 4px 4px 0 0; }
.navuser { display: flex; align-items: center; gap: 9px; margin-left: auto; }
.feedbackbtn {
  flex: 0 0 auto; white-space: nowrap; padding: 6px 8px;
  font-size: 11px; letter-spacing: .35px;
}

/* nav dropdown groups (Items ▾, Ops ▾) — <details> disclosures */
.navgroup { position: relative; }
.navgroup > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 4px;
  padding: 6px 6px; color: var(--dim); border-radius: 4px;
  font-size: 12px; letter-spacing: .6px; text-transform: uppercase; font-weight: 600;
}
.navgroup > summary::-webkit-details-marker { display: none; }
.navgroup > summary:hover { color: var(--text); background: var(--panel); }
.navgroup.active > summary { color: var(--orange); }
.navcaret { font-size: 9px; opacity: .8; transition: transform .15s; }
.navgroup[open] .navcaret { transform: rotate(180deg); }
.navgroup-panel {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 60;
  min-width: 180px; display: flex; flex-direction: column; gap: 2px; padding: 6px;
  background: linear-gradient(180deg, #12181f, #0e1318);
  border: 1px solid var(--line); border-radius: 6px; box-shadow: 0 8px 22px rgba(0, 0, 0, .5);
}
.navgroup-panel a {
  padding: 8px 12px; border-radius: 4px; color: var(--dim);
  font-size: 12px; letter-spacing: 1px; text-transform: uppercase; font-weight: 600; white-space: nowrap;
}
.navgroup-panel a:hover { color: var(--text); background: var(--panel); text-decoration: none; }
.navgroup-panel a.active { color: var(--orange); }

@media (min-width: 1231px) {
  .topbar { flex-wrap: nowrap; }
  .mainnav { flex: 1 1 auto; flex-wrap: nowrap; min-width: 0; }
  .mainnav a, .navgroup > summary { white-space: nowrap; }
}

/* hamburger — desktop hides it; the phone block below takes over the nav */
.navtoggle {
  display: none; align-items: center; justify-content: center;
  width: 40px; height: 40px; margin-left: -8px; padding: 0;
  background: none; border: 1px solid transparent; border-radius: 6px; cursor: pointer;
}
.navtoggle:hover { border-color: var(--line); }
.navtoggle-bars, .navtoggle-bars::before, .navtoggle-bars::after {
  display: block; width: 19px; height: 2px; border-radius: 2px;
  background: var(--text); transition: transform .18s ease, opacity .18s ease;
}
.navtoggle-bars { position: relative; }
.navtoggle-bars::before, .navtoggle-bars::after { content: ""; position: absolute; left: 0; }
.navtoggle-bars::before { top: -6px; }
.navtoggle-bars::after { top: 6px; }
.navopen .navtoggle-bars { background: transparent; }
.navopen .navtoggle-bars::before { transform: translateY(6px) rotate(45deg); background: var(--orange); }
.navopen .navtoggle-bars::after { transform: translateY(-6px) rotate(-45deg); background: var(--orange); }
.navscrim { display: none; }
.navsearch { display: flex; color: var(--dim); }
.navsearch:hover { color: var(--orange); }
.bell { position: relative; font-size: 16px; }
.bell.has { filter: drop-shadow(0 0 4px rgba(232, 134, 42, .8)); }
.bellcount {
  position: absolute; top: -6px; right: -9px; background: var(--orange); color: #14100a;
  font-size: 10px; font-weight: 700; border-radius: 8px; padding: 0 5px;
}

/* user avatar dropdown */
.usermenu { position: relative; }
.usermenu > summary { list-style: none; cursor: pointer; display: flex; }
.usermenu > summary::-webkit-details-marker { display: none; }
.avatar {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--panel2); border: 1px solid var(--line); color: var(--orange);
  font-size: 14px; font-weight: 700; letter-spacing: 0; text-transform: uppercase;
}
.usermenu[open] > summary .avatar,
.usermenu > summary:hover .avatar { border-color: var(--orange); }
/* With an uploaded picture the circle holds an <img> instead of the initial.
   store_avatar() already writes a square, so object-fit is belt-and-braces for
   any file that predates it. overflow:hidden is what actually rounds it. */
.avatar { overflow: hidden; flex: 0 0 auto; }
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* account.php's preview — same circle, bigger. */
.avatar-lg { width: 72px; height: 72px; font-size: 30px; }
/* unit.php roster — the avatar replaces the ◈ that used to prefix each name. */
.avatar-sm { width: 22px; height: 22px; font-size: 11px; }
.rostername { display: flex; align-items: center; gap: 8px; }
.avatarrow { display: flex; align-items: flex-start; gap: 18px; flex-wrap: wrap; }
.avatarforms { flex: 1 1 260px; }
.avatarforms .filterbar { margin-top: 8px; }
.usermenu-panel {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 60;
  min-width: 160px; padding: 6px; display: flex; flex-direction: column; gap: 2px;
  background: linear-gradient(180deg, #12181f, #0e1318);
  border: 1px solid var(--line); border-radius: 6px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .5);
}
.usermenu-name {
  color: var(--dim); font-size: 13px; padding: 6px 10px;
  border-bottom: 1px solid var(--line); margin-bottom: 4px;
}
.usermenu-panel a {
  color: var(--dim); font-size: 13px; padding: 7px 10px; border-radius: 4px;
}
.usermenu-panel a:hover { color: var(--text); background: var(--panel); text-decoration: none; }
.usermenu-panel a.active { color: var(--orange); }

/* --------------------------------------------------------------- layout */
.wrap { max-width: 1200px; margin: 0 auto; padding: 22px var(--wrap-pad) 60px; }
.sitefoot {
  text-align: center; color: #55626f; font-size: 11px; letter-spacing: 2px;
  text-transform: uppercase; padding: 20px; border-top: 1px solid var(--line);
}
.footstatus {
  display: inline-block; width: 6px; height: 6px; margin-right: 8px; border-radius: 50%;
  background: var(--green); box-shadow: 0 0 5px var(--green); vertical-align: 1px;
  animation: footpulse 2.6s ease-in-out infinite;
}
@keyframes footpulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .footstatus { animation: none; } }
.pagehead { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 12px; }
/* Intro line sitting directly under a .pagehead <h1>. */
.pagesub { margin: 2px 0 0; }
.twocol { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
/* Grid/flex children default to min-width:auto, i.e. never narrower than their
   content's own minimum — a long <select>, item name or other unbroken text
   would otherwise force the column wider than the page and break every margin
   below it. min-width:0 lets them actually respect the column they're given. */
.twocol > * { min-width: 0; }
@media (max-width: 860px) { .twocol { grid-template-columns: 1fr; } }
.sech {
  font-size: 13px; letter-spacing: 2px; text-transform: uppercase; color: var(--orange);
  border-bottom: 1px solid var(--line); padding-bottom: 5px; margin: 26px 0 12px;
}
.sech::before { content: "▸ "; opacity: .75; }
.tierhead {
  font-size: 15px; letter-spacing: 1.6px; text-transform: uppercase;
  border-left: 3px solid var(--orange); padding-left: 10px; margin: 26px 0 12px;
}
.tierhead.tier-1 { border-color: var(--t1); }
.tierhead.tier-2 { border-color: var(--t2); }
.tierhead.tier-3 { border-color: var(--t3); }
.tierhead.tier-4 { border-color: var(--t4); }
.tierhead.tier-5 { border-color: var(--t5); }
.tierhead.tier-o { border-color: var(--to); }
.subhead {
  font-size: 12px; letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--dim); margin: 16px 0 8px;
}
.subhead2 {
  font-size: 13px; font-weight: 600; margin: 12px 0 6px;
}
.subhead2 .dim { font-weight: 400; font-size: 12px; }
.dim { color: var(--dim); }
.empty { color: var(--dim); font-style: italic; padding: 14px 4px; }
.lede { font-size: 16px; color: #bfcad4; }
.spacer { flex: 1; }
.flash { padding: 10px 14px; border-radius: 4px; margin-bottom: 14px; border: 1px solid; }
.flash-ok { background: rgba(127, 194, 92, .12); border-color: rgba(127, 194, 92, .4); color: #b9e2a3; }
.flash-err { background: rgba(224, 96, 82, .12); border-color: rgba(224, 96, 82, .45); color: #f0b0a8; }

/* ---------------------------------------------------- HUD corner brackets
   Two opposite corners via pseudo-elements — no markup changes needed, so
   every page using these classes picks it up for free. Two corners already
   read as a targeting/HUD frame; four would need real elements to avoid
   ::before/::after fighting each other. .authbox/.herostrip wear it always
   (they're the calm, low-density pages); .card/.tile/.heropanel only light
   up on hover/focus, so dense grids of them don't turn busy. */
.authbox, .herostrip, .card, .tile, .heropanel { position: relative; }
.authbox::before, .authbox::after,
.herostrip::before, .herostrip::after,
.card::before, .card::after,
.tile::before, .tile::after,
.heropanel::before, .heropanel::after {
  content: ""; position: absolute; width: 14px; height: 14px; pointer-events: none;
  border: 2px solid var(--orange);
}
.authbox::before, .herostrip::before, .card::before, .tile::before, .heropanel::before {
  top: -1px; left: -1px; border-right: none; border-bottom: none;
}
.authbox::after, .herostrip::after, .card::after, .tile::after, .heropanel::after {
  bottom: -1px; right: -1px; border-left: none; border-top: none;
}
.authbox::before, .authbox::after, .herostrip::before, .herostrip::after { opacity: .5; }
.card::before, .card::after, .tile::before, .tile::after, .heropanel::before, .heropanel::after {
  opacity: 0; transition: opacity .15s;
}
.card:hover::before, .card:hover::after, .card:focus-visible::before, .card:focus-visible::after,
.tile:hover::before, .tile:hover::after, .tile:focus-visible::before, .tile:focus-visible::after,
.heropanel:hover::before, .heropanel:hover::after, .heropanel:focus-visible::before, .heropanel:focus-visible::after {
  opacity: .8;
}

/* --------------------------------------------------------------- forms */
input, select, textarea, button { font: inherit; color: var(--text); }
input, select, textarea {
  background: var(--bg2); border: 1px solid var(--line); border-radius: 4px; padding: 8px 10px;
}
input:focus, select:focus, textarea:focus { outline: 1px solid var(--orange); border-color: var(--orange); }
textarea { width: 100%; resize: vertical; font-family: var(--mono); font-size: 13.5px; }
label { display: block; margin: 10px 0 4px; color: var(--dim); font-size: 13px; }
label input, label select, label textarea { display: block; width: 100%; margin-top: 4px; color: var(--text); }
label.chk { display: inline-flex; align-items: center; gap: 6px; }
label.chk input { width: auto; display: inline; }
.formrow { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; }
.editform { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 16px 18px; }
/* edit.php's objective grid-ref table: a sub-panel inside .editform, headed
   like a .sech so it reads as its own section of the form. The refs are the
   only part of the seeded objective manifest that is edited by hand. */
.objgrids { border: 1px solid var(--line); border-radius: 6px; padding: 4px 14px 12px; margin: 18px 0 0; }
.objgrids legend {
  font-size: 13px; letter-spacing: 2px; text-transform: uppercase; color: var(--orange); padding: 0 6px;
}
.objgrids p { margin: 8px 0 0; font-size: 12px; }
.objgrids .datatable td { padding: 4px 10px; }
.objgrids input { text-transform: uppercase; }
.btn {
  display: inline-block; cursor: pointer; padding: 8px 16px;
  background: var(--panel2); border: 1px solid var(--line); color: var(--text);
  font-size: 13px; letter-spacing: .6px; text-transform: uppercase; font-weight: 600;
  /* Cut corner instead of a rounded one — reads as a console button, not a
     web button. Kept off .linkbtn/.chip/.pager, which want their own shapes. */
  clip-path: polygon(9px 0, 100% 0, 100% calc(100% - 9px), calc(100% - 9px) 100%, 0 100%, 0 9px);
  transition: filter .1s;
}
.btn:hover { border-color: var(--orange); color: var(--orange-hi); text-decoration: none; }
.btn:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.btn-primary {
  background: linear-gradient(180deg, #f09a3e, #d9781f); color: #17110a; border-color: #b4641a;
  text-shadow: none; box-shadow: 0 0 0 rgba(232, 134, 42, 0);
}
.btn-primary:hover { background: linear-gradient(180deg, #ffb35c, #ea8b2c); color: #17110a; box-shadow: 0 0 16px rgba(232, 134, 42, .35); }
.btn-ghost { background: transparent; }
.btnrow { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 10px 0; }
.linkbtn { background: none; border: none; color: var(--dim); cursor: pointer; font-size: 12px; padding: 2px 4px; }
.linkbtn:hover { color: var(--orange-hi); }
.linkbtn.danger:hover { color: var(--red); }
.inlineform { display: inline-flex; gap: 6px; align-items: center; }
.formhint { font-size: 12px; }
.filterbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 12px 0; }
.filterbar input[type=search], .filterbar input:not([type]) { min-width: 170px; }
/* items.php: span the content column instead of huddling left, so the row's
   edges line up with the topbar's brand and avatar. Search eats the slack. */
.filterbar-full input[type=search] { flex: 1 1 260px; }
.filterbar-full select { flex: 0 1 auto; }
.addbar { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 10px 12px; }
.bigsearch input { flex: 1; font-size: 16px; padding: 12px 14px; }
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 20px;
  color: var(--dim); font-size: 12.5px; padding: 4px 12px; cursor: pointer;
}
.chip:hover { border-color: var(--orange); color: var(--orange-hi); text-decoration: none; }
.chip.on { border-color: var(--orange); color: var(--orange); background: rgba(232, 134, 42, .1); }
.chiprow { display: flex; flex-wrap: wrap; gap: 7px; margin: 8px 0; }
.tag { background: rgba(88, 166, 216, .12); border: 1px solid rgba(88, 166, 216, .35); color: #9fd0ec; border-radius: 3px; font-size: 12px; padding: 1px 8px; }
.tagrow { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
.pager { display: flex; gap: 5px; margin: 18px 0; }
.pager a { padding: 4px 11px; border: 1px solid var(--line); border-radius: 4px; color: var(--dim); }
.pager a.cur { border-color: var(--orange); color: var(--orange); }
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin: 14px 0 20px; flex-wrap: wrap; }
.tabs a { padding: 8px 16px; color: var(--dim); text-transform: uppercase; font-size: 12.5px; letter-spacing: 1px; font-weight: 600; }
.tabs a.on { color: var(--orange); border-bottom: 2px solid var(--orange); }
.tabs a.tabhub { border-right: 1px solid var(--line); margin-right: 6px; padding-right: 14px; opacity: .8; }
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 16px 18px; margin: 14px 0; }
/* inferred, not wiki-documented — set apart from the sourced bench line above it */
.benchderived { font-size: 12.5px; color: #bfcad4; margin: 10px 0 0; padding-top: 10px; border-top: 1px dashed var(--line); }

/* --------------------------------------------------------------- auth */
.authbox { max-width: 420px; margin: 8vh auto; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 28px 30px; box-shadow: 0 0 40px rgba(232, 134, 42, .07); }
.feedbackbox { max-width: 760px; margin-top: 5vh; }
.feedbackbox .formrow { align-items: start; }
.authbox h1 { letter-spacing: 1px; }
.authbox .btn { width: 100%; margin-top: 14px; padding: 11px; }
.botcatch { position: absolute; left: -10000px; top: auto; width: 1px; height: 1px; overflow: hidden; }
/* File inputs, wherever they appear (feedback screenshots, account.php's
   avatar, admin uploads): the browser's default button is a white OS chip that
   reads as a foreign object on a dark terminal, so it gets .btn's shape. */
input[type=file] { padding: 9px 10px; color: var(--dim); font-size: 13px; cursor: pointer; }
input[type=file]::file-selector-button {
  margin-right: 10px; padding: 6px 12px; border-radius: 4px; cursor: pointer;
  background: var(--panel2); border: 1px solid var(--line); color: var(--text);
  font: inherit; font-size: 12px; letter-spacing: .6px; text-transform: uppercase;
}
input[type=file]::file-selector-button:hover { border-color: var(--orange); color: var(--orange-hi); }
/* feedback.php's picker sits outside its <label> (label input{width:100%}
   would stretch the browser's own button), so it needs the block layout and
   top margin the wrapped inputs get for free. */
.feedbackbox input[type=file] { display: block; width: 100%; margin-top: 4px; }
.uploadlist { margin: 6px 0 0; font-size: 12px; line-height: 1.5; min-height: 1em; }
.uploadlist-err { color: var(--red); }
/* Turnstile renders a fixed-size iframe; just give it room in the form flow. */
.captcha { margin: 18px 0 4px; min-height: 65px; }
/* Admin-only "this form has no bot check" notice — see captcha_widget(). */
.captcha-off { margin: 18px 0 4px; padding: 9px 12px; font-size: 12.5px; line-height: 1.5;
  color: #f0b0a8; background: rgba(224, 96, 82, .12); border: 1px solid rgba(224, 96, 82, .45); border-radius: 4px; }
.captcha-off code { font-family: var(--mono); font-size: 12px; color: var(--orange-hi); }
/* toggle switch (styled checkbox) */
label.toggle { display: flex; align-items: center; gap: 10px; margin-top: 16px; color: var(--text); font-size: 14px; cursor: pointer; }
label.toggle input { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
.toggle-track { flex: 0 0 auto; position: relative; width: 40px; height: 22px; border-radius: 22px; background: var(--line); border: 1px solid var(--line); transition: background .15s; }
.toggle-thumb { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: var(--dim); transition: transform .15s, background .15s; }
label.toggle input:checked + .toggle-track { background: var(--orange); border-color: var(--orange); }
label.toggle input:checked + .toggle-track .toggle-thumb { transform: translateX(18px); background: #fff; }
label.toggle input:focus-visible + .toggle-track { outline: 2px solid var(--orange); outline-offset: 2px; }
.toggle-text { -webkit-user-select: none; user-select: none; }

/* --------------------------------------------------------------- cards */
.cardlist { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 14px; margin: 14px 0; }
.card {
  display: block; background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
  padding: 14px 16px; color: var(--text); position: relative;
  transition: border-color .15s, transform .15s;
}
.card:hover { border-color: var(--orange); text-decoration: none; transform: translateY(-1px); }
.card h2, .card h3 { margin: 6px 0; font-size: 17px; }
.card.slim h3 { font-size: 15px; }
.card.sel { border-color: var(--orange); box-shadow: 0 0 0 1px var(--orange); }
.cardtype {
  display: inline-block; font-size: 10.5px; letter-spacing: 1.6px; text-transform: uppercase;
  color: var(--dim); border: 1px solid var(--line); border-radius: 3px; padding: 1px 8px;
}
.cardtype.t-build { color: var(--t2); border-color: var(--t2); }
.cardtype.t-guide, .cardtype.t-mission { color: var(--orange); border-color: var(--orange); }
.cardtype.t-operation { color: var(--t3); border-color: var(--t3); }
.cardtype.t-map { color: var(--t2); border-color: var(--t2); }
.cardtype.t-system { color: var(--to); border-color: var(--to); }
.cardtype.t-active { color: var(--green); border-color: var(--green); }
.cardtype.t-planning { color: var(--blue); border-color: var(--blue); }
.cardtype.t-done { color: var(--dim); }
.cardmeta { display: flex; flex-wrap: wrap; gap: 12px; color: var(--dim); font-size: 12.5px; margin-top: 6px; }
.cardmeta a { color: inherit; }
.progress { height: 5px; background: var(--bg2); border-radius: 3px; margin: 10px 0 4px; overflow: hidden; }
.progress div { height: 100%; background: linear-gradient(90deg, var(--orange), var(--orange-hi)); }

/* --------------------------------------------------------------- hero */
.herostrip {
  margin-bottom: 26px; padding: 24px 26px 28px; overflow: hidden;
  border: 1px solid var(--line); border-radius: 8px;
  background:
    radial-gradient(500px 220px at 88% -25%, rgba(232, 134, 42, .12), transparent 65%),
    radial-gradient(420px 200px at -5% 115%, rgba(88, 166, 216, .09), transparent 65%),
    linear-gradient(160deg, var(--panel2), var(--panel) 65%);
}
/* A handful of fixed "stars" behind the copy — cheap texture, not a photo,
   so it never risks being an actual (spoiler) game screenshot. Kept to this
   one calm hero rather than tiled globally, or it'd fight the item tables. */
.herostrip > * { position: relative; z-index: 1; }
.herostrip::before, .herostrip::after { border: none; width: auto; height: auto; }
.herostrip::before {
  content: ""; position: absolute; inset: 0; opacity: .8; pointer-events: none;
  background-image:
    radial-gradient(1.4px 1.4px at 18% 22%, #fff, transparent),
    radial-gradient(1px 1px at 34% 55%, #fff, transparent),
    radial-gradient(1.6px 1.6px at 62% 15%, #fff, transparent),
    radial-gradient(1px 1px at 76% 42%, #fff, transparent),
    radial-gradient(1.2px 1.2px at 91% 28%, #fff, transparent),
    radial-gradient(1px 1px at 52% 78%, #fff, transparent),
    radial-gradient(1.3px 1.3px at 8% 68%, #fff, transparent);
  background-repeat: no-repeat;
}
.hero-line1 { font-family: var(--mono); font-size: 11px; letter-spacing: 4px; color: var(--orange); opacity: .8; }
.herolinks { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin-top: 12px; }
.heropanel {
  display: flex; flex-direction: column; gap: 4px; padding: 14px 16px;
  background: linear-gradient(160deg, var(--panel2), var(--panel));
  border: 1px solid var(--line); border-left: 3px solid var(--orange); border-radius: 6px; color: var(--text);
}
.heropanel:hover { text-decoration: none; border-color: var(--orange); }
.heropanel b { font-size: 11px; letter-spacing: 2px; text-transform: uppercase; color: var(--orange); }
.heropanel span { color: var(--dim); font-size: 13.5px; }

/* --------------------------------------------------------------- feed */
.feed { display: flex; flex-direction: column; }
.feedrow {
  display: flex; gap: 12px; align-items: baseline; padding: 9px 10px;
  border-bottom: 1px solid #1a222b; color: var(--text); border-radius: 4px;
}
.feedrow:hover { background: var(--panel); text-decoration: none; }
.feedrow.unseen { background: rgba(232, 134, 42, .07); }
.feedicon { flex: 0 0 24px; text-align: center; }
.feedwhat { flex: 1; min-width: 0; }
.feedwhat b { color: var(--orange-hi); font-weight: 600; }
.feedtime { color: var(--dim); font-size: 12px; white-space: nowrap; }

/* --------------------------------------------------------------- home */
/* Spans the full content column, like every other search bar and the header
   above it — no narrower cap. */
.homesearch { margin-top: 14px; }

/* Field guide tiles. Each carries a live count, so the tile itself shows the tool
   has something in it — these read off seeded tables and are never empty. */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin: 12px 0 16px; }
.tile {
  display: flex; flex-direction: column; gap: 2px; padding: 14px 16px;
  background: linear-gradient(160deg, var(--panel2), var(--panel));
  border: 1px solid var(--line); border-top: 3px solid var(--orange);
  border-radius: 6px; color: var(--text);
}
.tile:hover { text-decoration: none; border-color: var(--orange); transform: translateY(-1px); }
.tile b { font-size: 11px; letter-spacing: 2px; text-transform: uppercase; color: var(--orange); }
.tilestat { font-family: var(--mono); font-size: 25px; line-height: 1.25; letter-spacing: -.5px; }
.tilesub { color: var(--dim); font-size: 12.5px; }

/* Proportional read of the same $byTier data as the chips below — segment
   widths are per-request data (inline style), colours are static per tier. */
.tierbar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: var(--bg2); margin: 4px 0 10px; }
.tierbar-seg { height: 100%; }
.tierbar-seg.tier-1 { background: var(--t1); }
.tierbar-seg.tier-2 { background: var(--t2); }
.tierbar-seg.tier-3 { background: var(--t3); }
.tierbar-seg.tier-4 { background: var(--t4); }
.tierbar-seg.tier-5 { background: var(--t5); }
.tierbar-seg.tier-o { background: var(--to); }
.tierbar-seg.tier-n { background: var(--dim); }

.tierchips { margin-bottom: 24px; }
.tierchips .chip.tier-1 { border-color: rgba(139, 195, 74, .5); color: var(--t1); }
.tierchips .chip.tier-2 { border-color: rgba(88, 166, 216, .5); color: var(--t2); }
.tierchips .chip.tier-3 { border-color: rgba(176, 127, 224, .5); color: var(--t3); }
.tierchips .chip.tier-4 { border-color: rgba(232, 134, 42, .5);  color: var(--t4); }
.tierchips .chip.tier-5 { border-color: rgba(224, 85, 111, .5);  color: var(--t5); }
.tierchips .chip.tier-o { border-color: rgba(255, 215, 94, .5);  color: var(--to); }
.tierchips .chip:hover { border-color: currentColor; }

/* ------------------------------------------------------------ markdown */
.mdbody { max-width: 860px; }
.mdbody h1, .mdbody h2, .mdbody h3 { color: var(--orange-hi); letter-spacing: .4px; }
.mdbody blockquote { border-left: 3px solid var(--orange); margin-left: 0; padding-left: 14px; color: var(--dim); }
.mdbody table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  margin: 12px 0;
  font-size: 14px;
}
.mdbody th {
  text-align: left;
  font-size: 11px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--dim);
  border: 0;
  border-bottom: 2px solid var(--line);
  padding: 7px 10px;
}
.mdbody td {
  border: 0;
  border-bottom: 1px solid #1a222b;
  padding: 7px 10px;
  vertical-align: top;
}
.mdbody th:nth-child(2),
.mdbody th:nth-child(3),
.mdbody td:nth-child(2),
.mdbody td:nth-child(3) {
  text-align: center;
  white-space: nowrap;
}
.mdbody td:first-child {
  overflow-wrap: anywhere;
}
.mdbody tr:hover td { background: rgba(255, 255, 255, .015); }
.mdbody.flashed { animation: liveflash .8s; }
@keyframes liveflash { 0% { background: rgba(232, 134, 42, .15); } 100% { background: transparent; } }
.mention { color: var(--orange); background: rgba(232, 134, 42, .12); border-radius: 3px; padding: 0 3px; font-weight: 600; }
.item-link { border-bottom: 1px dotted rgba(255, 169, 77, .5); }
.presence { color: var(--green); font-size: 12.5px; }
.savestate { font-family: var(--mono); font-size: 12px; color: var(--dim); }
.savestate.ok { color: var(--green); }
.savestate.err { color: var(--red); }
.conflict { border: 1px solid var(--red); background: rgba(224, 96, 82, .08); border-radius: 6px; padding: 14px 18px; margin: 16px 0; }
.conflict pre { max-height: 300px; overflow: auto; }

.favform { display: inline-flex; gap: 6px; align-items: center; }
.rateselect { font-size: 12px; padding: 4px 6px; }

/* --------------------------------------------------------------- tables */
.datatable { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 14px; }
.datatable th {
  text-align: left; font-size: 11px; letter-spacing: 1.6px; text-transform: uppercase;
  color: var(--dim); border-bottom: 2px solid var(--line); padding: 7px 10px;
}
.datatable td { border-bottom: 1px solid #1a222b; padding: 7px 10px; vertical-align: top; }
.datatable tr:hover td { background: rgba(255, 255, 255, .015); }
.datatable.totals { max-width: 480px; }

/* map.php spawn lists: one table per item category, name column carries the
   item icon so a row is scannable without reading it. */
.spawngroup { margin-bottom: 22px; }
.spawncat {
  font-size: 13px; letter-spacing: 1.6px; text-transform: uppercase;
  border-left: 3px solid currentColor; padding-left: 10px; margin: 18px 0 4px;
}
.countnote { font-size: 12px; letter-spacing: 0; text-transform: none; }
.spawntable td:first-child { width: 34%; }
.spawntable td:nth-child(2) { width: 33%; }
.spawnname { display: inline-flex; align-items: center; gap: 8px; }
.spawnname .itemicon { flex: none; }
/* Tags sit inside a cell here, so drop .tagrow's block margins. */
.spawntable .tagrow { display: inline-flex; margin: 0; }
.pager {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 18px 0; max-width: 100%;
}
.pager a, .pager span {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 30px; padding: 0 8px; border-radius: 4px;
  font-size: 13px; color: var(--text); border: 1px solid var(--line);
}
.pager a:hover { background: var(--panel); text-decoration: none; border-color: var(--orange); }
.pager a.cur { background: var(--orange); border-color: var(--orange); color: #14100a; font-weight: 700; }
.pager .pgnav { color: var(--dim); }
.pager .pgnav.disabled { opacity: .35; }
.pager .pgdots { border: none; color: var(--dim); min-width: auto; padding: 0 2px; }
.matrix td.hit { color: var(--green); font-size: 12.5px; }
.matrix td.miss { color: #3a4652; text-align: center; }
.tierbadge {
  display: inline-block; font-family: var(--mono); font-size: 11px; font-weight: 700;
  border: 1px solid var(--line); border-radius: 3px; padding: 1px 7px; letter-spacing: 1px;
}
.tierbadge.tier-1 { color: var(--t1); border-color: var(--t1); }
.tierbadge.tier-2 { color: var(--t2); border-color: var(--t2); }
.tierbadge.tier-3 { color: var(--t3); border-color: var(--t3); }
.tierbadge.tier-4 { color: var(--t4); border-color: var(--t4); }
.tierbadge.tier-5 { color: var(--t5); border-color: var(--t5); }
.tierbadge.tier-o { color: var(--to); border-color: var(--to); }
.tierbadge.tier-g { color: var(--dim); border-color: var(--dim); }

/* --------------------------------------------------------- recipe tree */
.recipetree { font-size: 14px; background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 12px 16px; }
.treenode > summary { cursor: pointer; padding: 3px 0; list-style: none; }
.treenode > summary::before { content: "▸ "; color: var(--orange); }
.treenode[open] > summary::before { content: "▾ "; }
.treekids { margin-left: 22px; border-left: 1px dashed #2c3742; padding-left: 14px; }
.treeleaf { padding: 3px 0; }
.qty { color: var(--orange-hi); font-family: var(--mono); }
.craftmeta { color: var(--dim); font-size: 12px; }
.rawmeta { color: var(--t1); font-size: 11px; letter-spacing: 1px; text-transform: uppercase; }
.qtyform { display: flex; gap: 8px; align-items: end; }
.qtyform input { width: 90px; }
.foundon { margin: 4px 0 12px; }
.wayvia { margin: -4px 0 12px; font-size: 13px; color: var(--dim); }

/* ------------------------------------------------------- ways to craft
   One row per distinct recipe. Ingredients share a column across rows so the
   difference between two ways of making something is what the eye lands on;
   the bench column is secondary. Rows with documented inputs are links that
   re-drive the breakdown calculator (?recipe=). */
.recipeways { border: 1px solid var(--line); border-radius: 6px; overflow: hidden; background: var(--panel); }
.wayrow {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) minmax(0, 15em);
  gap: 4px 12px; align-items: center;
  padding: 11px 14px; border-top: 1px solid var(--line);
  color: var(--text); font-size: 13.5px;
}
.wayrow:first-child { border-top: 0; }
.recipeways.single .wayrow { grid-template-columns: minmax(0, 1fr) minmax(0, 15em); }
a.wayrow:hover { background: var(--panel2); text-decoration: none; }
a.wayrow:hover .waymark { color: var(--orange-hi); }
.wayrow.sel { background: rgba(232, 134, 42, .07); box-shadow: inset 3px 0 0 var(--orange); }
.waymark { color: var(--dim); font-size: 13px; text-align: center; }
.wayrow.sel .waymark { color: var(--orange); }

.wayings { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 7px; min-width: 0; }
.waying { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.waying .itemicon { border-radius: 3px; }
.waying b { color: var(--orange-hi); font-family: var(--mono); font-size: 12.5px; }
.wayplus { color: var(--dim); font-size: 12px; }
.waynote {
  color: var(--dim); font-size: 10.5px; letter-spacing: 1px; text-transform: uppercase;
  border: 1px solid var(--line); border-radius: 3px; padding: 1px 6px; white-space: nowrap;
}
.wayrow.sel .waynote { color: var(--orange); border-color: rgba(232, 134, 42, .5); }
.wayundoc { color: var(--dim); font-style: italic; }
.waybench { color: var(--dim); font-size: 12.5px; text-align: right; line-height: 1.45; }
.waybn { white-space: nowrap; }   /* wrap between bench names, never inside one */
.waysep { opacity: .5; }
.waymore { white-space: nowrap; color: #6f7a86; border-bottom: 1px dotted #4a5561; cursor: help; }
/* inferred, never blended into the documented benches beside it (see schema.sql) */
.wayderived { display: block; color: #7d8894; font-size: 11.5px; border-top: 1px dashed var(--line); margin-top: 4px; padding-top: 4px; }
.wayshint { font-size: 12.5px; margin: 8px 0 0; }

@media (max-width: 700px) {
  .wayrow, .recipeways.single .wayrow { grid-template-columns: 22px minmax(0, 1fr); }
  .recipeways.single .wayrow { grid-template-columns: minmax(0, 1fr); }
  .waybench { grid-column: 2; text-align: left; }
  .recipeways.single .waybench { grid-column: 1; }
}

/* ------------------------------------------------- item icons / pictures */
.itemicon {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  border-radius: 6px; overflow: hidden;
  background: color-mix(in srgb, currentColor 12%, var(--bg2));
  border: 1px solid color-mix(in srgb, currentColor 38%, var(--line));
}
.itemicon.has-img { background: #0a0d10; border-color: var(--line); color: inherit; }
.itemicon img { width: 100%; height: 100%; object-fit: cover; display: block; }
.itemcell { display: inline-flex; align-items: center; gap: 9px; color: var(--text); min-width: 0; }
.itemcell:hover { text-decoration: none; }
.itemcell:hover .item-link { text-decoration: underline; }
.itemhero { display: flex; align-items: center; gap: 16px; }
.itemhero > .itemicon { border-radius: 10px; }
.itemhero h1 { margin: 5px 0 6px; }
.itemimg-field { display: flex; align-items: center; gap: 14px; margin: 12px 0 4px; }
.itemimg-field label { margin: 0; }

/* ---------------------------------------------------------- compare */
/* Items are columns, attributes are rows. The first column (labels) and the
   header row (item names) both stick, so you never lose track of which stat or
   which item a cell belongs to while scrolling a wide comparison. */
.cmpadd { display: flex; gap: 14px; align-items: flex-start; justify-content: center; flex-wrap: wrap; margin: 12px 0 16px; }
.cmpsearchform { flex: 1 1 320px; max-width: 420px; }
.cmpsearchwrap { position: relative; }
.cmpsearchwrap input { width: 100%; }
/* Live matches for the "add an item" search, dropped below the input. Each
   match is a real submit button (name=add_id) styled like a feed row rather
   than a native <select>/datalist, so it can show a tier badge and never
   depends on a browser's own autocomplete rendering. */
.cmpresults {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 6; margin-top: 4px;
  display: flex; flex-direction: column; gap: 2px; padding: 4px;
  background: var(--bg2); border: 1px solid var(--line); border-radius: 8px;
  max-height: 320px; overflow-y: auto; box-shadow: 0 8px 20px rgba(0, 0, 0, .45);
}
/* Same reason as .attopt[hidden] above: .cmpresults sets its own `display`,
   which otherwise beats the [hidden] attribute the search script toggles. */
.cmpresults[hidden] { display: none; }
button.cmpresult { background: none; border: none; font: inherit; text-align: left; cursor: pointer; width: 100%; }
.cmpresult.active, .cmpresult:hover { background: var(--panel); }
/* table-layout: fixed + an unsized <col> per item column (compare.php's
   colgroup) splits the remaining width evenly across however many items are
   in the set (max COMPARE_MAX = 4) — otherwise the browser sizes each column
   to its own content and columns end up lopsided next to each other, which
   defeats the point of a side-by-side comparison. The table's own min-width
   (inline style, ~210px/item) is a floor under that even split so columns
   never get fixed-layout-squeezed on a phone; below it .tablescroll's
   overflow-x takes over instead, same as any other wide table here. */
.cmptable { font-variant-numeric: tabular-nums; table-layout: fixed; }
.cmpcornercol { width: 190px; }
.cmptable th, .cmptable td { border-right: 1px solid #1a222b; }
.cmptable th:last-child, .cmptable td:last-child { border-right: 0; }
/* Not top-sticky: thead sits inside .tablescroll, whose own overflow-x:auto
   forces its computed overflow-y to auto too (CSS Overflow's "visible next to
   non-visible becomes auto" rule) — that makes .tablescroll, not the
   viewport, the containing block for any sticky descendant. A `top` sticky
   here would anchor to .tablescroll (which never scrolls independently) and
   just render permanently offset, overlapping the row below, without ever
   actually sticking during a real page scroll. Left-sticky (.cmpcorner,
   .cmplabel, .cmpsection th below) is unaffected — that axis genuinely is
   .tablescroll's own scroll axis. */
.cmptable thead th {
  background: var(--bg2); vertical-align: bottom; text-align: center;
}
.cmpcorner { position: sticky; left: 0; z-index: 4; }
.cmplabel {
  position: sticky; left: 0; z-index: 2;
  background: var(--bg2); text-transform: none; letter-spacing: .3px;
  font-size: 12.5px; color: var(--dim);
}
.cmphead { min-width: 150px; }
.cmpitem { display: inline-flex; flex-direction: column; align-items: center; gap: 6px; color: var(--text); }
.cmpitem:hover { text-decoration: none; }
.cmpitem:hover .cmpname { text-decoration: underline; color: var(--orange-hi); }
.cmpname { font-size: 13px; letter-spacing: 0; text-transform: none; text-align: center; max-width: 160px; }
.cmphead > div { margin: 6px 0 2px; }
.cmpremove { margin: 0; }
.cmpremove .linkbtn { text-transform: uppercase; letter-spacing: .8px; font-size: 10.5px; }
.cmptable td { text-align: center; }
.cmpsection th {
  background: var(--panel); color: var(--orange);
  font-size: 11px; letter-spacing: 1.6px; border-bottom: 1px solid var(--line);
  position: sticky; left: 0;
}
/* Winning cell in a numeric row. */
.cmptable td.best {
  color: #cdeab3; font-weight: 700;
  background: rgba(127, 194, 92, .14);
  box-shadow: inset 0 0 0 1px rgba(127, 194, 92, .35);
}

/* item.php intrinsic-stats panel — the same grouping as compare.php, read as a
   spec sheet. The grid packs several stats per row on wide screens and stacks
   on phones; each stat is its own bordered cell (label left, value right). */
.statpanel { margin: 12px 0 4px; }
.statgroup { margin-bottom: 14px; }
.statgroup:last-child { margin-bottom: 0; }
.statgroup-h {
  margin: 0 0 8px; color: var(--orange);
  font-size: 11px; font-weight: 700; letter-spacing: 1.6px; text-transform: uppercase;
}
.statgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 6px;
}
.statrow {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  padding: 7px 11px; background: var(--bg2);
  border: 1px solid var(--line); border-radius: 6px;
}
.statlabel { color: var(--dim); font-size: 12.5px; }
.statval { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }

/* items.php compare column + floating tray */
.col-cmp { width: 30px; text-align: center; padding-left: 4px !important; padding-right: 4px !important; }
.col-cmp input { width: 16px; height: 16px; accent-color: var(--orange); cursor: pointer; }
.cmptray {
  position: sticky; bottom: 14px; z-index: 20; margin: 16px auto 0; width: max-content; max-width: 100%;
  display: flex; align-items: center; gap: 12px; padding: 10px 14px;
  background: linear-gradient(180deg, #12181f, #0e1318);
  border: 1px solid var(--orange); border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .5);
}
.cmptray-n { font-size: 12.5px; letter-spacing: 1px; text-transform: uppercase; color: var(--dim); }
.btn-disabled { opacity: .45; pointer-events: none; }

/* per-category accent (drives icon tint + tile edge + chip highlight) */
.cat-material   { --cc: #9aa7b4; }
.cat-tool       { --cc: #58a6d8; }
.cat-weapon     { --cc: #e0556f; }
.cat-ammo       { --cc: #e8862a; }
.cat-armor      { --cc: #b07fe0; }
.cat-bench      { --cc: #7fc25c; }
.cat-building   { --cc: #c9a06a; }
.cat-deployable { --cc: #5bc0c0; }
.cat-farming    { --cc: #8bc34a; }
.cat-food       { --cc: #ffd75e; }
.cat-furniture  { --cc: #d98cb3; }
.cat-medical    { --cc: #e06052; }
.cat-storage    { --cc: #b98f5a; }
.cat-livestock  { --cc: #9c6b43; }

/* ------------------------------------------------------------ tech tree */
.treetools { display: flex; gap: 12px; align-items: center; justify-content: space-between; flex-wrap: wrap; margin: 14px 0 12px; }
.treesearch { flex: 1; min-width: 220px; font-size: 15px; padding: 10px 14px; }

/* the display rules below (grid/flex) would otherwise beat the [hidden]
   attribute the JS toggles to filter and switch views — re-assert it.
   .datatable rows are here for items.php, which filters the same way. */
.cathub[hidden], .drillbar[hidden], #panel[hidden],
.tiergroup[hidden], .benchblock[hidden], .itemtile[hidden],
.treeloading[hidden],
.maplayer[hidden], .mapgrid-overlay[hidden], .mapmeasure[hidden],
.spawnlist li[hidden],
.datatable tbody tr[hidden] { display: none; }

/* Shown only while a category/search panel is actually being fetched — a
   cached one swaps in without it, so this should rarely be seen. */
.treeloading { color: var(--dim); font-style: italic; padding: 14px 4px; }

/* NB: `table-layout: fixed` looks like the obvious fix for this table (the
   browser measures all 2801 rows to size six columns), but it was measured at
   only ~9% off the load — the cost is DOM construction, not column sizing —
   and it collapses on a phone, where the two col-opt columns drop out and the
   rest get squeezed until names break one letter per line. Left on auto. */

/* items.php reuses the tree's toolbar: search + selects pinned on top, tier
   chips and the live count in the drill bar under it. The selects sit next to
   the box rather than stretching, so the search keeps the width it earns. */
.treetools select { flex: 0 1 auto; }
/* .tierfilter already claims margin-left:auto; without this the second auto
   margin on .drillact would split the gap and strand the chips mid-bar. */
.drillbar .tierfilter + .drillact { margin-left: 0; }

/* category hub — the landing grid of clickable category cards */
.cathub { display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); gap: 12px; margin: 4px 0; }
.catcard {
  display: flex; flex-direction: column; gap: 8px; padding: 14px 15px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 6px; color: var(--text); transition: border-color .15s, transform .15s;
}
.catcard:hover { text-decoration: none; border-color: var(--orange); transform: translateY(-1px); }
.catcard-top { display: flex; align-items: center; gap: 10px; }
.catcard-name { font-size: 15px; font-weight: 600; }
.catcard-count { font-size: 12.5px; color: var(--dim); margin-top: -3px; }
.catcard-bar { display: flex; height: 7px; border-radius: 4px; overflow: hidden; background: var(--bg2); margin: 1px 0; }
.catcard-bar .seg { display: block; height: 100%; }
.catcard-tiers { display: flex; flex-wrap: wrap; gap: 3px 9px; font-family: var(--mono); font-size: 11px; }
.catcard-tiers .tt { color: var(--dim); }
.seg.tier-1 { background: var(--t1); } .tt.tier-1, .tchip.tier-1 { color: var(--t1); }
.seg.tier-2 { background: var(--t2); } .tt.tier-2, .tchip.tier-2 { color: var(--t2); }
.seg.tier-3 { background: var(--t3); } .tt.tier-3, .tchip.tier-3 { color: var(--t3); }
.seg.tier-4 { background: var(--t4); } .tt.tier-4, .tchip.tier-4 { color: var(--t4); }
.seg.tier-5 { background: var(--t5); } .tt.tier-5, .tchip.tier-5 { color: var(--t5); }
.seg.tier-o { background: var(--to); } .tt.tier-o, .tchip.tier-o { color: var(--to); }
.seg.tier-g { background: var(--dim); } .tt.tier-g, .tchip.tier-g { color: var(--dim); }

/* drill toolbar shown when inside a category / search view */
.drillbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 4px 0 14px; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
.drilltitle { font-size: 17px; font-weight: 600; letter-spacing: .3px; }
.tierfilter { display: flex; gap: 6px; flex-wrap: wrap; margin-left: auto; }
.tchip {
  font-family: var(--mono); font-size: 11.5px; font-weight: 700; letter-spacing: 1px; padding: 4px 10px;
  border: 1px solid var(--line); border-radius: 4px; background: var(--panel); color: var(--dim); cursor: pointer;
}
.tchip:hover { border-color: currentColor; }
.tchip.on { border-color: currentColor; background: color-mix(in srgb, currentColor 16%, var(--panel)); }

.tiergroup {
  border: 1px solid var(--line); border-left: 3px solid var(--tclr, var(--orange));
  border-radius: 8px; margin: 12px 0; overflow: hidden; background: var(--panel);
}
.tiergroup.tier-1 { --tclr: var(--t1); }
.tiergroup.tier-2 { --tclr: var(--t2); }
.tiergroup.tier-3 { --tclr: var(--t3); }
.tiergroup.tier-4 { --tclr: var(--t4); }
.tiergroup.tier-5 { --tclr: var(--t5); }
.tiergroup.tier-o { --tclr: var(--to); }
.tiergroup.tier-g { --tclr: var(--dim); }
.tiergroup-head {
  display: flex; align-items: center; gap: 12px; padding: 12px 16px; cursor: pointer;
  -webkit-user-select: none; user-select: none;
  background: linear-gradient(180deg, var(--panel2), var(--panel));
}
.tiergroup-head:hover { background: var(--panel2); }
.tiergroup-head:focus-visible { outline: 1px solid var(--orange); outline-offset: -2px; }
.tiergroup-head h2 { margin: 0; font-size: 16px; letter-spacing: 1px; text-transform: uppercase; }
.tiergroup-head .tg-count { margin-left: auto; font-size: 12.5px; }
.tg-caret { color: var(--dim); transition: transform .15s; font-size: 12px; }
.tiergroup.collapsed .tg-caret { transform: rotate(-90deg); }
.tiergroup.collapsed .tiergroup-body { display: none; }
.tiergroup-body { padding: 2px 16px 16px; }

.benchblock { margin-top: 12px; }
.benchname {
  font-size: 11.5px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--orange);
  margin: 16px 0 9px; padding-bottom: 5px; border-bottom: 1px solid var(--line);
}
.itemtiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(185px, 1fr)); gap: 8px; }
.itemtile {
  display: flex; align-items: center; gap: 10px; min-width: 0; padding: 7px 11px;
  background: var(--bg2); border: 1px solid var(--line); border-left: 3px solid var(--cc, var(--line));
  border-radius: 6px; color: var(--text); transition: border-color .12s, transform .12s, background .12s;
  /* The tech tree ships every item as a tile (~2800 of them), so drilling a
     big category or searching can lay out hundreds at once. Skip layout and
     paint for the ones scrolled out of view. "auto" in contain-intrinsic-size
     means the browser remembers each tile's real height after it has been
     rendered once, so the 56px estimate (40px icon + padding + border) only
     ever applies to a tile that has not been on screen yet — scroll height
     stays honest. Tiles remain in the DOM and keep matching
     querySelectorAll('.itemtile'), which is what the filter in tree.php
     drives off, and in-page find still reaches them. */
  content-visibility: auto;
  contain-intrinsic-size: auto 56px;
}
.itemtile:hover {
  text-decoration: none; background: var(--panel2);
  border-color: var(--orange); border-left-color: var(--orange); transform: translateY(-1px);
}
.itemtile-name { font-size: 13.5px; line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* marks a tier item that is ALSO sold at the Orbital Workshop */
.wsflag {
  margin-left: auto; flex: 0 0 auto; font-family: var(--mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: .5px; color: var(--to); border: 1px solid var(--to); border-radius: 3px; padding: 0 4px;
}

/* --------------------------------------------------------------- maps */
.mapgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; margin: 14px 0 26px; }
.mapcard { display: block; background: var(--panel); border: 1px solid var(--line); border-radius: 6px; overflow: hidden; color: var(--text); }
.mapcard:hover { border-color: var(--orange); text-decoration: none; }
.mapcard-body { padding: 10px 14px; }
.mapcard-body h2 { margin: 2px 0 6px; font-size: 16px; }
.mapcard-body p { font-size: 12.5px; margin: 6px 0 2px; }
.mapholder {
  height: 130px; position: relative; display: flex; align-items: center; justify-content: center;
  color: #45525f; font-size: 13px; letter-spacing: 2px; text-transform: uppercase;
  background:
    linear-gradient(rgba(232, 134, 42, .04) 1px, transparent 1px),
    linear-gradient(90deg, rgba(232, 134, 42, .04) 1px, transparent 1px), var(--bg2);
  background-size: 24px 24px;
}
.mapholder .mapscene, .mapholder img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.mapholder.big { height: 340px; border: 1px solid var(--line); border-radius: 6px; margin: 12px 0; }

/* map.php's way in to mapview.php. It sits over the curated scene art, so it
   carries its own scrim rather than trusting the image underneath to be dark
   enough — the whole holder is the click target, the button just anchors the
   eye. */
.mapenter {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  background: radial-gradient(ellipse at center, rgba(12, 15, 18, .34), rgba(12, 15, 18, .8));
  transition: background .15s;
  text-decoration: none;
}
.mapenter:hover { background: radial-gradient(ellipse at center, rgba(12, 15, 18, .18), rgba(12, 15, 18, .72)); }
.mapenter-cta {
  background: linear-gradient(180deg, #f09a3e, #d9781f); color: #17110a;
  border: 1px solid #b4641a; border-radius: 4px;
  padding: 12px 26px; font-size: 15px; font-weight: 700;
  letter-spacing: 1.4px; text-transform: uppercase;
  box-shadow: 0 4px 20px rgba(232, 134, 42, .35);
  transition: transform .15s, box-shadow .15s;
}
.mapenter:hover .mapenter-cta { transform: translateY(-1px); box-shadow: 0 6px 26px rgba(232, 134, 42, .5); }
.mapenter:focus-visible .mapenter-cta { outline: 2px solid var(--orange-hi); outline-offset: 3px; }
.mapenter-sub { font-size: 12px; color: #c3ccd6; letter-spacing: 1px; text-transform: uppercase; }

/* Coordinate overlay — the game's own 16x16 A-P/1-16 map grid (see
   map_grid_overlay() in inc/helpers.php, which sets --grid-n as data).
   Borders sit on the top/left edge of every cell so there is no trailing edge
   to strip: the old :nth-child(8n)/:nth-child(n+57) trick was hardcoded to an
   8-wide grid and strips the wrong cells at any other size. */
.mapgrid-overlay {
  position: absolute; inset: 0; display: grid;
  grid-template-columns: repeat(var(--grid-n, 16), 1fr);
  grid-template-rows: repeat(var(--grid-n, 16), 1fr);
  pointer-events: none;
}
.mapgrid-cell {
  position: relative;
  border-top: 1px solid rgba(255, 255, 255, .18);
  border-left: 1px solid rgba(255, 255, 255, .18);
}
/* 16x16 is 256 labels — unreadable mush at fit-to-viewport, so they fade in
   only once mapview.js says we are zoomed in far enough for them to mean
   something. The rules themselves always show. */
.mapgrid-label {
  position: absolute; top: 2px; left: 3px;
  font-size: 11px; font-weight: 700; letter-spacing: .5px;
  color: #fff; background: rgba(10, 12, 16, .72); border-radius: 3px; padding: 0 3px; line-height: 1.5;
  opacity: 0; transition: opacity .12s;
  /* Counter-scale the canvas transform so the pill is the same size on screen
     at every zoom level (mapview.js publishes --zoom-inv = 1/scale). */
  transform: scale(var(--zoom-inv, 1)); transform-origin: 0 0;
}
.mapviewport.showlabels .mapgrid-label { opacity: 1; }

/* -------------------------------------------------------------- planner */
.plannerlayout { display: grid; grid-template-columns: 1fr 300px; gap: 18px; }
@media (max-width: 980px) { .plannerlayout { grid-template-columns: 1fr; } }
.plannertoolbar {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  background: var(--panel); border: 1px solid var(--line); border-bottom: none;
  border-radius: 6px 6px 0 0; padding: 8px 12px;
}
.symchoice input { display: none; }
/* Uniform boxed icon buttons, same footprint whether selected or not — an
   always-visible border so the row reads as one button group (like
   .legendrow's chips) instead of loose emoji that only gain a box once picked. */
.symchoice span {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px;
  border: 1px solid var(--line); background: var(--bg2);
  border-radius: 4px; cursor: pointer;
}
.symchoice span img { width: 18px; height: 18px; }
.symchoice:hover span { border-color: var(--orange); }
.symchoice input:checked + span { border-color: var(--orange); background: rgba(232, 134, 42, .15); }

/* Same radio-behind-a-label pattern as .symchoice, but the "icon" is just the
   colour itself — round like .swatch (stickies' own colour picker) rather
   than boxed, so the two pickers stay visually distinct at a glance. */
.colorchoice input { display: none; }
.colorchoice span {
  display: inline-block; width: 22px; height: 22px; border-radius: 50%;
  background: var(--swatch-color); border: 2px solid var(--line); cursor: pointer;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .4);
}
.colorchoice:hover span { border-color: var(--orange); }
.colorchoice input:checked + span { border-color: var(--orange-hi); box-shadow: 0 0 0 1px rgba(0, 0, 0, .4), 0 0 0 3px rgba(232, 134, 42, .35); }

/* Same swatch look as .colorchoice, as a plain button instead of a radio —
   the marker-edit popup (#mp-color) drives selection with a JS .active class
   the way stickies.php's own .swatch picker already does. Own class rather
   than reusing .sdot/.swatch: those size their colour from a fixed .c-name
   class, and this one needs an arbitrary --swatch-color per pin colour. */
.pinswatch {
  width: 20px; height: 20px; border-radius: 50%; padding: 0;
  background: var(--swatch-color); border: 2px solid var(--line); cursor: pointer;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .4);
}
.pinswatch:hover { border-color: var(--orange); }
.pinswatch.active { border-color: var(--orange-hi); box-shadow: 0 0 0 1px rgba(0, 0, 0, .4), 0 0 0 3px rgba(232, 134, 42, .35); }

/* panzoom.js's cooperative-scroll hint — shared by .mapviewport and
   .plannerviewport, appended into whichever viewport a bare (non-ctrl) wheel
   fires over. Fades in/out rather than popping, so a wheel-scrolling user
   sees it as a passing nudge, not a dialog blocking the page scroll it just let through. */
.pz-zoomhint {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  z-index: 4; pointer-events: none;
  padding: 8px 16px; border-radius: 6px; white-space: nowrap;
  background: rgba(5, 7, 10, .85); border: 1px solid var(--line);
  color: var(--text); font-size: 13px; letter-spacing: .3px;
  opacity: 0; transition: opacity .2s ease;
}
.pz-zoomhint.show { opacity: 1; }

.plannerviewport {
  position: relative; overflow: hidden; height: 68vh; min-height: 380px;
  background: var(--bg2); border: 1px solid var(--line); border-radius: 0 0 6px 6px;
  cursor: crosshair; -webkit-user-select: none; user-select: none;
  /* the JS drives pan/pinch itself — stop the browser also scrolling the page */
  touch-action: none;
}
.plannercanvas { position: absolute; transform-origin: 0 0; }
/* Direct-child selector: #markers .pin img.pinface is nested one level
   deeper and sizes via .pinface instead, not the 1:1 map-image rule. */
.plannercanvas > img { display: block; max-width: none; width: 1000px; }
.plannergrid {
  width: 1000px; height: 1000px; display: flex; align-items: center; justify-content: center;
  color: #45525f; letter-spacing: 2px; text-transform: uppercase; font-size: 13px; text-align: center;
  background:
    linear-gradient(rgba(232, 134, 42, .06) 1px, transparent 1px),
    linear-gradient(90deg, rgba(232, 134, 42, .06) 1px, transparent 1px), #10151a;
  background-size: 50px 50px;
}
#markers { position: absolute; inset: 0; }
.pin { position: absolute; transform: translate(-50%, -90%); cursor: grab; text-align: center; touch-action: none; pointer-events: auto; }
.pin:active { cursor: grabbing; }
/* A pin's glyph is ~20px — fine to click, too small to reliably hit with a
   thumb. Pad the hit area out on touch without moving the pin's point. */
@media (hover: none) {
  .pinface { padding: 6px; margin: -6px; width: 23px; height: 23px; }
}
/* Dropped pins are a <span>, tinted via mask rather than an <img> src, so one
   white silhouette PNG can render in whatever colour the Prospector picked
   (--pin-color) — see map_pin_html() / mapview.js / planner.js. The crew-log
   marker is a plain <img class="pinface"> instead (not user-colourable): its
   own bitmap paints normally since --pin-icon/--pin-color are simply unset
   there, which is valid — an unset var() falls mask-image back to `none`. */
.pinface {
  display: inline-block; width: 20px; height: 20px;
  background-color: var(--pin-color, #fff);
  -webkit-mask-image: var(--pin-icon); -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat; -webkit-mask-position: center;
  mask-image: var(--pin-icon); mask-size: contain;
  mask-repeat: no-repeat; mask-position: center;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .8));
}
.pinlabel {
  display: block; font-size: 10px; background: rgba(10, 13, 16, .85); border: 1px solid var(--line);
  border-radius: 3px; padding: 0 5px; white-space: nowrap; margin-top: -3px;
}
.legend { display: flex; flex-wrap: wrap; gap: 12px; color: var(--dim); font-size: 12px; margin-top: 8px; align-items: center; }
.legend img { width: 14px; height: 14px; vertical-align: -3px; }
.markerpop {
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 90; width: 260px;
  background: var(--panel); border: 1px solid var(--orange); border-radius: 6px; padding: 12px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, .6);
}
.markerpop input, .markerpop textarea { width: 100%; margin-bottom: 8px; }
.plannerside { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 12px 16px; align-self: start; }
.tasklist { list-style: none; margin: 0 0 10px; padding: 0; }
.tasklist li { padding: 5px 0; border-bottom: 1px solid #1a222b; }
.tasklist li.done label { text-decoration: line-through; color: var(--dim); }
.tasklist label { display: inline; color: var(--text); font-size: 14px; }
.taskdel { float: right; }
.taskform { display: flex; gap: 6px; }
.taskform input { flex: 1; }
.notesbox { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 10px 16px; margin-bottom: 14px; }
.notesbox summary { cursor: pointer; color: var(--orange); font-size: 13px; letter-spacing: 1px; text-transform: uppercase; }

/* -------------------------------------------------------------- stickies */
/* A bright, airy note board — its own light surface inside the dark terminal. */
.stickyboard {
  background: #f4f6f8; border: 1px solid #e2e6eb; border-radius: 16px; overflow: hidden;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 14px 34px rgba(0, 0, 0, .32);
}
.boardhead {
  display: flex; align-items: baseline; gap: 9px; padding: 14px 20px;
  border-bottom: 1px solid #e7eaee;
  font-size: 12px; letter-spacing: 1.6px; text-transform: uppercase; font-weight: 700; color: #57606b;
}
.boardhead span { font-weight: 500; letter-spacing: .2px; text-transform: none; color: #9aa4b0; font-size: 12px; }
.boardhead.boardtabs { padding: 9px 12px; gap: 8px; }
.boardtab {
  appearance: none; background: none; border: 1px solid transparent; border-radius: 9px;
  display: inline-flex; align-items: baseline; gap: 8px; padding: 6px 14px; cursor: pointer;
  font: inherit; font-size: 12px; letter-spacing: 1.6px; text-transform: uppercase; font-weight: 700; color: #9aa4b0;
}
.boardtab:hover { color: #57606b; }
.boardtab.active {
  background: #fff; border-color: #e2e6eb; color: #57606b;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .08);
}
.boardarea { position: relative; min-height: max(640px, calc(100vh - 330px)); touch-action: none; }
.boardempty {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  text-align: center; color: #9aa4b0; padding: 0 34px; margin: 0; font-size: 14px;
}

.sticky {
  position: absolute; display: flex; flex-direction: column;
  padding: 14px 15px 9px; border-radius: 9px; color: #33302a;
  font-size: 15px; line-height: 1.5; cursor: grab;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12), 0 4px 10px rgba(0, 0, 0, .10);
  transition: box-shadow .14s ease, transform .14s ease;
}
.sticky:hover { box-shadow: 0 4px 10px rgba(0, 0, 0, .14), 0 12px 26px rgba(0, 0, 0, .14); }
.sticky.dragging { cursor: grabbing; transform: scale(1.03); box-shadow: 0 16px 34px rgba(0, 0, 0, .22); }
.sticky.pinned { box-shadow: 0 1px 2px rgba(0, 0, 0, .12), 0 4px 10px rgba(0, 0, 0, .10), 0 0 0 2px var(--orange); cursor: default; }

.stickybody { flex: 1 1 auto; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: anywhere; overflow: hidden; }
.stickyph { color: rgba(51, 48, 42, .4); }
.stickyedit {
  flex: 1 1 auto; width: 100%; min-height: 70px; background: rgba(255, 255, 255, .6); color: #33302a;
  border: none; border-radius: 5px; padding: 6px; font-family: inherit; font-size: 15px;
  line-height: 1.5; resize: none;
}
.stickyedit:focus { outline: 2px solid rgba(0, 0, 0, .14); }

.stickymeta { display: flex; align-items: center; gap: 6px; min-height: 20px; margin-top: 10px; font-size: 11.5px; color: rgba(51, 48, 42, .5); transition: opacity .12s ease; }
.sticky:hover .stickymeta, .sticky:focus-within .stickymeta { opacity: 0; }
.stickywho { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stickytime { white-space: nowrap; }

/* per-note tools swap in over the footer on hover, so the resting card stays calm */
.stickytools { position: absolute; left: 14px; bottom: 8px; display: flex; align-items: center; gap: 7px; opacity: 0; pointer-events: none; transition: opacity .12s ease; }
.sticky:hover .stickytools, .sticky:focus-within .stickytools { opacity: 1; pointer-events: auto; }
.sdots { display: flex; gap: 6px; }
.sdot { width: 15px; height: 15px; border-radius: 50%; border: 1px solid rgba(0, 0, 0, .16); cursor: pointer; padding: 0; }
.sdot.active { box-shadow: 0 0 0 2px rgba(51, 48, 42, .55); }
.spin, .sdel {
  display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px;
  padding: 0; border: none; border-radius: 6px; background: none; cursor: pointer; color: rgba(51, 48, 42, .5);
}
.spin svg, .sdel svg { display: block; }
.spin:hover { color: #33302a; background: rgba(0, 0, 0, .06); }
.sdel:hover { color: var(--red); background: rgba(224, 96, 82, .12); }
.spin.on { color: var(--orange); }
.sresize {
  position: absolute; right: 3px; bottom: 3px; width: 14px; height: 14px; cursor: nwse-resize;
  opacity: 0; transition: opacity .12s ease;
  background: linear-gradient(135deg,
    transparent 46%, rgba(51, 48, 42, .35) 46%, rgba(51, 48, 42, .35) 54%,
    transparent 54%, transparent 70%, rgba(51, 48, 42, .35) 70%, rgba(51, 48, 42, .35) 78%, transparent 78%);
}
.sticky:hover .sresize { opacity: .6; }

/* new-note colour picker in the page header (sits on the dark bar) */
.swatchpick { display: inline-flex; gap: 7px; align-items: center; }
.swatch { width: 22px; height: 22px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; padding: 0; box-shadow: 0 0 0 1px rgba(255, 255, 255, .16); }
.swatch:hover { border-color: rgba(255, 255, 255, .5); }
.swatch.active { border-color: var(--orange); box-shadow: 0 0 0 2px rgba(232, 134, 42, .35); }

/* soft pastel note colours (also used by the dots + swatches) */
.c-amber  { background: #ffe6a3; }
.c-green  { background: #d4edbc; }
.c-blue   { background: #c8e4f6; }
.c-pink   { background: #fbd4e0; }
.c-purple { background: #e5d6f6; }

/* -------------------------------------------------------------- guides */
.post { width: 100%; min-width: 0; }
.post .mdbody { max-width: none; }
.guidefacts {
  display: flex; gap: 10px; flex-wrap: nowrap;
  width: 100%; margin: 12px 0;
}
.fact {
  flex: 1 1 0; min-width: 0; white-space: nowrap;
  background: var(--panel); border: 1px solid var(--line); border-left: 3px solid var(--green);
  border-radius: 4px; padding: 7px 12px; font-size: 13.5px;
}
.fact b {
  display: inline; margin-right: 8px;
  font-size: 10.5px; letter-spacing: 1.4px; text-transform: uppercase; color: var(--dim);
}
.fact.hazard { border-left-color: var(--red); }
.loadout { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 4px 18px; }
.objectivegrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; margin: 12px 0; }
.objectivecol h3 {
  font-size: 13px; text-transform: uppercase; letter-spacing: 1.4px;
  color: var(--dim); margin: 0 0 6px;
}
.objectivetable {
  table-layout: fixed;
  min-width: 300px;
  margin: 0;
}
.objectivetable .obj-grid { width: 58px; }
.objectivetable .obj-qty { width: 56px; }
.objectivetable th:nth-child(2),
.objectivetable th:nth-child(3),
.objectivetable td:nth-child(2),
.objectivetable td:nth-child(3) {
  text-align: center;
  white-space: nowrap;
}
.objectivetable td:first-child {
  overflow-wrap: anywhere;
}
.objectivetable .grouprow {
  text-align: left;
  font-weight: 700;
  color: var(--orange);
  background: rgba(232, 134, 42, .08);
  border-top: 1px solid rgba(232, 134, 42, .24);
  padding-top: 10px !important;
}
.grouprow { font-weight: 700; color: var(--orange); background: rgba(255, 255, 255, .02); padding-top: 12px !important; }
.guidegroup { margin-bottom: 8px; }
.searchhits { margin-bottom: 10px; }
/* Same reason as the tech tree's rule: .tabs/.cardlist/.card set a display,
   which would otherwise beat the [hidden] the guide filter toggles. */
.tabs[hidden], .guidegroup[hidden], .guideblock[hidden],
.cardlist[hidden], .cardlist > .card[hidden] { display: none; }
/* push the full-text fallback to the far end of the toolbar, like .tierfilter */
.drillbar .drillact { margin-left: auto; }

/* -------------------------------------------------------------- sessions */
.sessioncard { display: flex; gap: 16px; margin-bottom: 12px; }
.sessiondate {
  flex: 0 0 84px; text-align: center; font-family: var(--mono); color: var(--orange);
  border-right: 1px solid var(--line); padding-right: 14px; font-size: 13px;
}
.sessiondate b { display: block; font-size: 20px; color: var(--text); }
.sessionbody { flex: 1; }
.sessionbody h3 { margin: 0 0 4px; }
.rsvprow { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-top: 8px; }
.newbox { margin: 10px 0 20px; }
.newbox summary { list-style: none; display: inline-block; }
.newbox summary::-webkit-details-marker { display: none; }
.newbox form { margin-top: 12px; }
.pastbox { margin-top: 24px; color: var(--dim); }
.pastbox summary { cursor: pointer; padding: 8px 0; }

/* -------------------------------------------------------------- misc */
@media (max-width: 700px) {
  .pagehead { flex-direction: column; }
}



/* ============================================================ talent builds */
.buildsummary { color: var(--dim); margin: 4px 0 10px; font-size: 15px; }
.carddesc { color: var(--dim); font-size: 13px; margin: 4px 0 6px; }
.bigtitle {
  font-size: 26px; font-weight: 700; background: var(--panel2);
  border: 1px solid var(--line); border-radius: 6px; color: var(--text);
  padding: 4px 10px; width: 100%; max-width: 520px; margin: 2px 0;
}
.buildmeta { margin: 10px 0 4px; }
.buildmeta .grow { flex: 1 1 240px; }
.pt-total { font-family: var(--mono); color: var(--orange-hi); }
.notesarea {
  width: 100%; background: var(--panel2); border: 1px solid var(--line);
  color: var(--text); border-radius: 6px; padding: 10px 12px; font: inherit;
}

/* how-to strip -------------------------------------------------------------- */
.talent-howto {
  display: flex; flex-wrap: wrap; gap: 6px 16px; align-items: center;
  margin: 10px 0; padding: 9px 14px; background: var(--bg2);
  border: 1px solid var(--line); border-radius: 8px; font-size: 12.5px; color: var(--dim);
}
.talent-howto b { color: var(--text); font-weight: 600; }
.talent-howto span { position: relative; padding-left: 16px; }
.talent-howto span::before { content: "\25B8"; position: absolute; left: 2px; color: var(--orange); }

/* talent control bar: stat chips · category tabs · Talents/Solo toggle ------ */
.tal-top {
  display: flex; align-items: center; gap: 12px 16px; flex-wrap: wrap;
  margin: 8px 0 16px; padding: 10px 12px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
}
.tal-stats { display: flex; gap: 10px; }
/* the display rules below would otherwise beat the [hidden] attribute the PHP
   and pool toggle set on the inactive pool's chip / category tabs */
.statchip[hidden], .cattab[hidden] { display: none; }
.statchip {
  --accent: var(--green);
  display: inline-flex; align-items: center; gap: 9px;
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 7px; padding: 5px 12px 5px 11px; background: var(--bg2);
}
.statchip b { font-family: var(--mono); font-size: 22px; font-weight: 700; line-height: 1; }
.statchip .statlabel {
  font-size: 9.5px; line-height: 1.15; text-transform: uppercase; letter-spacing: .5px; color: var(--dim);
}
.availpool b { color: var(--accent); }
.availpool.none b { color: var(--orange-hi); }
/* off by default (most builds haven't earned it) — dashed and dim until
   clicked, then it reads like any other active stat chip */
.bonustoggle { cursor: pointer; font: inherit; border-style: dashed; opacity: .55; }
.bonustoggle:hover { opacity: .8; border-left-color: var(--accent); }
.bonustoggle b { color: var(--dim); }
.bonustoggle.on { opacity: 1; border-style: solid; box-shadow: 0 0 0 1px var(--accent); }
.bonustoggle.on b { color: var(--accent); }

.cattabs { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 auto; }
.cattab {
  cursor: pointer; background: var(--bg2); color: var(--dim);
  border: 1px solid var(--line); border-radius: 7px; padding: 8px 15px;
  font-size: 12.5px; font-weight: 700; letter-spacing: .4px; text-transform: uppercase;
  display: flex; align-items: center; gap: 7px;
}
.cattab:hover { color: var(--text); border-color: color-mix(in srgb, var(--orange) 40%, var(--line)); }
.cattab.on {
  color: var(--orange-hi); border-color: var(--orange);
  background: color-mix(in srgb, var(--orange) 16%, var(--panel));
  box-shadow: inset 0 -2px 0 var(--orange);
}
.cat-glyph { width: 16px; height: 16px; object-fit: contain; }

.poolswitch { display: inline-flex; border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }
.pswitch {
  cursor: pointer; background: var(--bg2); color: var(--dim);
  border: none; padding: 8px 16px; font-size: 12.5px; font-weight: 700; letter-spacing: .3px;
}
.pswitch:hover { color: var(--text); }
.pswitch + .pswitch { border-left: 1px solid var(--line); }
.pswitch.on { background: color-mix(in srgb, var(--orange) 24%, var(--panel)); color: var(--orange-hi); }

/* category page: several sub-tree columns side by side ---------------------- */
.catpanes {
  border: 1px solid var(--line); border-radius: 10px;
  background:
    repeating-linear-gradient(0deg, transparent 0 3px, rgba(255,255,255,.012) 3px 4px),
    radial-gradient(130% 90% at 50% -20%, #141c24, var(--panel));
  padding: 18px 16px 26px;
}
.catpane { display: none; }
.catpane.on { display: block; }
/* Each column is sized by its tree's fixed pixel canvas (the game's own grid).
   A wide category (Combat has 4 trees) can exceed the pane, so talents.js
   scales the whole row down uniformly to fit (see fitPanes) rather than
   wrapping the last tree onto a second line or scrolling — a scroll container
   (overflow) would clip the node tooltips that float above the edge tiles.
   align-items:stretch keeps every column the same height as the tallest. */
.subtrees {
  display: flex; gap: 14px; align-items: stretch; flex-wrap: nowrap; justify-content: center;
}
.subtree {
  --accent: var(--blue);
  flex: 0 0 auto;
  background: rgba(255,255,255,.014); border: 1px solid var(--line);
  border-top: 2px solid var(--accent); border-radius: 8px; padding: 12px 14px 16px;
}
/* the trees are wide — on big screens let the talent screen span the viewport
   instead of the 1200px page column, like the in-game full-screen layout */
@media (min-width: 1280px) {
  .talents { width: min(1980px, calc(100vw - 44px)); margin-left: 50%; transform: translateX(-50%); }
}
.subtree-head { margin-bottom: 12px; }
.st-title { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.st-namewrap { display: flex; align-items: center; gap: 6px; min-width: 0; }
.st-icon { width: 18px; height: 18px; object-fit: contain; flex: 0 0 auto; border-radius: 4px; }
.st-name { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .6px; color: var(--accent); }
.st-count { font-family: var(--mono); font-size: 11px; color: var(--text); }
.st-count .dim { color: var(--dim); }
.subbar { height: 5px; background: var(--bg2); border: 1px solid var(--line); border-radius: 3px; overflow: hidden; margin-top: 6px; }
.subfill { display: block; height: 100%; width: 0; background: var(--accent); transition: width .16s ease; }

/* node canvas inside a sub-tree column — talents sit at the game's own
   [row, col] pixel coordinates (inline left/top), so fractional columns and
   the real branching web render exactly like the in-game screen. */
.subgrid { position: relative; overflow: visible; margin: 2px auto 0; }
.subgrid .tnode { position: absolute; }
.edges { position: absolute; inset: 0; pointer-events: none; z-index: 0; overflow: visible; }
.edge { fill: none; stroke: #6b5a2f; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.edge.live { stroke: var(--orange); stroke-width: 3; filter: drop-shadow(0 0 5px color-mix(in srgb, var(--orange) 75%, transparent)); }

/* square talent tile — icon only, name lives in the hover tooltip (like the
   in-game screen), so tiles pack tight and branch lines stay clean ---------- */
.tnode {
  position: relative; z-index: 1;
  display: grid; place-items: center; cursor: default;
}
.talents[data-edit="1"] .tnode { cursor: pointer; }

.tn-tile {
  position: relative; width: 52px; height: 52px; border-radius: 9px;
  border: 1px solid #6b5a2f; /* same brown-gold as .edge, the prerequisite lines */
  background: var(--bg2);
  display: grid; place-items: center; transition: transform .1s ease, filter .15s ease, background .15s ease, border-color .15s ease;
}
.tn-glyph { width: 25px; height: 25px; color: var(--dim); transition: color .15s ease; }
.tn-icon { display: grid; place-items: center; border-radius: 7px; overflow: hidden; }
.tn-icon.has-img img { width: 100%; height: 100%; object-fit: contain; display: block; filter: saturate(.55) brightness(.85); transition: filter .15s ease; }
.tnode.avail .tn-icon.has-img img, .tnode.on .tn-icon.has-img img { filter: none; }
.tn-rank {
  position: absolute; top: -7px; left: -7px; z-index: 3;
  min-width: 16px; height: 16px; padding: 0 3px; border-radius: 4px;
  background: var(--panel2); border: 1px solid var(--line); color: var(--dim);
  font-family: var(--mono); font-size: 9.5px; font-weight: 700; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.tnode-name { display: none; }

/* node states --------------------------------------------------------------- */
.talents[data-edit="1"] .tnode.avail .tn-tile { border-color: color-mix(in srgb, var(--accent) 60%, var(--line)); }
.talents[data-edit="1"] .tnode.avail .tn-glyph { color: color-mix(in srgb, var(--accent) 80%, var(--text)); }
.talents[data-edit="1"] .tnode.avail:hover .tn-tile { transform: translateY(-2px) scale(1.05); }

.tnode.on .tn-tile { background: color-mix(in srgb, var(--accent) 22%, var(--bg2)); border-color: var(--accent); filter: drop-shadow(0 0 7px color-mix(in srgb, var(--accent) 55%, transparent)); }
.tnode.on .tn-glyph { color: #fff; }
.tnode.on .tn-rank { background: var(--accent); border-color: var(--accent); color: #14100a; }
.tnode.maxed .tn-tile { background: color-mix(in srgb, var(--accent) 34%, var(--bg2)); filter: drop-shadow(0 0 12px var(--accent)); }

/* Dim only the tile/rank chrome, not the whole .tnode — .tnode-tip is a
   sibling that pops up on hover, and CSS opacity on an ancestor caps every
   descendant's max opacity, so putting it on .tnode made locked nodes' own
   tooltips render semi-transparent and hard to read. */
.tnode.locked .tn-tile, .tnode.locked .tn-rank { opacity: .5; }
.tnode.locked .tn-glyph { color: #4a5560; }
.tnode.locked .tnode-name { color: var(--dim); }
.tnode.locked .tn-tile::after {
  content: "\1F512"; position: absolute; top: 1px; right: 3px; font-size: 10px; opacity: .8;
}

.tnode.nope .tn-tile { animation: hex-nope .4s; }
.tnode.nope::after {
  content: attr(data-msg); position: absolute; top: -22px; left: 50%; transform: translateX(-50%);
  background: var(--red); color: #fff; font-size: 10px; padding: 1px 6px; border-radius: 3px; white-space: nowrap; z-index: 6;
}
@keyframes hex-nope { 0%,100%{transform:translateX(0);} 25%{transform:translateX(-3px);} 75%{transform:translateX(3px);} }

/* hover tooltip ------------------------------------------------------------- */
.tnode-tip {
  position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%);
  width: 210px; background: #0b1015; border: 1px solid var(--accent); border-radius: 7px;
  padding: 9px 11px; z-index: 30; display: none; text-align: left;
  box-shadow: 0 10px 28px rgba(0,0,0,.55);
}
.tnode-tip::after {
  content: ""; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  border: 6px solid transparent; border-top-color: var(--accent);
}
.tnode:hover .tnode-tip, .tnode:focus-within .tnode-tip { display: block; }
.tnode-tip b { display: block; color: var(--text); font-size: 12.5px; margin-bottom: 3px; }
.tip-eff { display: block; color: var(--dim); font-size: 11.5px; line-height: 1.35; margin-bottom: 6px; }
.tip-meta { display: block; color: var(--accent); font-family: var(--mono); font-size: 10.5px; }
.rank-badge { width: 14px; height: 14px; object-fit: contain; vertical-align: -3px; }

.build-errors {
  margin: 14px 0 0; border: 1px solid var(--red); border-radius: 6px;
  background: color-mix(in srgb, var(--red) 12%, var(--panel)); padding: 8px 12px;
  color: var(--red); font-size: 13px;
}
.build-errors div { margin: 2px 0; }
.dangerzone { margin: 26px 0 0; padding-top: 14px; border-top: 1px solid var(--line); }
.linkbtn.danger { color: var(--red); }

@media (max-width: 760px) {
  .tal-top { gap: 10px; }
  .cattabs { margin: 2px 0 0; order: 3; width: 100%; justify-content: center; }
  .cattab { padding: 6px 10px; font-size: 11.5px; }
  /* the tree canvases are fixed-size (game coordinates) — on phones scroll the
     category sideways rather than shrinking the tiles off their grid */
  .catpanes { overflow-x: auto; }
  .subtrees { gap: 10px; flex-wrap: nowrap; justify-content: flex-start; }
}

/* ============================================================ phone layout
   The desktop bar packs ten nav links, brand and avatar onto one row, which
   measures out to ~1192px of min-content before it stops fitting (verified
   with Playwright — below that the nowrap links spill out of .mainnav and
   paint over .navuser instead of wrapping, since the min-width:1231px query
   above forces nowrap). Below the 1230px breakpoint they move into a drawer
   instead, which also keeps --topbar-h honest (the drawer is fixed, so it
   never counts toward the bar's measured height).
   ========================================================================= */
@media (max-width: 1230px) {
  .navtoggle { display: flex; }
  .topbar { gap: 10px; padding: 6px var(--wrap-pad); flex-wrap: nowrap; }
  .brand-word { letter-spacing: 1.4px; font-size: 14px; }

  .mainnav {
    position: fixed; left: 0; right: 0; top: var(--topbar-h, 52px);
    display: none; flex-direction: column; gap: 0; z-index: 55;
    max-height: calc(100vh - var(--topbar-h, 52px)); overflow-y: auto;
    padding: 6px 0 10px;
    background: linear-gradient(180deg, #12181f, #0e1318);
    border-bottom: 1px solid var(--line);
    box-shadow: 0 14px 28px rgba(0, 0, 0, .5);
    overscroll-behavior: contain;
  }
  .navopen .mainnav { display: flex; }
  .mainnav a {
    padding: 13px 20px; border-radius: 0; font-size: 13px;
    border-bottom: 1px solid rgba(41, 52, 63, .5);
  }
  .mainnav a.active {
    color: var(--orange); border-bottom: 1px solid rgba(41, 52, 63, .5);
    background: rgba(232, 134, 42, .08); box-shadow: inset 3px 0 0 var(--orange);
  }
  /* Nav groups become accordions inside the drawer instead of dropdowns. */
  .navgroup { position: static; width: 100%; }
  .navgroup > summary {
    justify-content: space-between; width: 100%;
    padding: 13px 20px; border-bottom: 1px solid rgba(41, 52, 63, .5); border-radius: 0;
  }
  .navgroup.active > summary { color: var(--orange); background: rgba(232, 134, 42, .08); box-shadow: inset 3px 0 0 var(--orange); }
  .navgroup-panel {
    position: static; min-width: 0; padding: 0; gap: 0;
    background: rgba(0, 0, 0, .22); border: 0; border-radius: 0; box-shadow: none;
  }
  .navgroup-panel a { padding-left: 38px; }
  .navopen .navscrim {
    display: block; position: fixed; inset: 0; z-index: 54;
    top: var(--topbar-h, 52px); background: rgba(0, 0, 0, .5);
  }
  .navopen { overflow: hidden; }
}

@media (max-width: 700px) {
  :root { --wrap-pad: 14px; }
  .wrap { padding-top: 16px; padding-bottom: 48px; }
  h1 { font-size: 22px; }
  .lede { font-size: 15px; }

  /* iOS Safari zooms the whole page when you focus a control under 16px.
     Ends up wider than the viewport and needs a manual pinch back out. The
     class selectors are listed because each sets its own smaller size and
     would otherwise outrank a bare element rule. */
  input, select, textarea,
  .treesearch, .rateselect, .stickyedit, .bigtitle, .notesarea { font-size: 16px; }
  .btn { padding: 10px 16px; }
  .btnrow .btn { flex: 1 1 auto; text-align: center; }
  /* an input's default intrinsic width (~20 chars) plus a button beats a
     320px screen — let it shrink, and wrap if it still can't fit */
  .taskform, .qtyform { flex-wrap: wrap; }
  .taskform input { flex: 1 1 140px; min-width: 0; }

  /* thumb-sized hit areas — the audit found 9-115 targets under 32px a page */
  .chip, .tchip { padding: 8px 14px; }
  .tabs a { padding: 11px 14px; }
  .pager a, .pager span { min-width: 38px; height: 38px; }
  .linkbtn { padding: 8px 6px; }
  .usermenu-panel a { padding: 11px 12px; }
  .navuser { gap: 16px; }
  .bell { display: flex; align-items: center; justify-content: center; width: 34px; height: 40px; }
  .usermenu > summary { align-items: center; min-height: 40px; }
  .swatch { width: 30px; height: 30px; }
  .sdot { width: 20px; height: 20px; }

  /* Roomier rows: the item name in a table cell is the tap target for the
     whole row, and at 19px tall it was a coin-flip to hit. Padding the anchor
     itself grows the hit box without moving the text off its baseline —
     these read as row targets, not the prose links WCAG 2.5.8 exempts. */
  .datatable td { padding: 11px 10px; }
  .datatable th { padding: 9px 10px; }
  /* .itemcell is already inline-flex (icon beside name) — pad it without
     touching display, or the name unwraps beneath its own icon. */
  .spawnname a, .datatable td > a:not(.itemcell) { display: inline-block; }
  .spawnname a, .datatable td > a { padding-block: 7px; }

  /* Secondary columns fold away rather than being scrolled to. A wrapped
     description column stretches every row to several times its height, which
     costs more than the column is worth — the detail pages carry it all. */
  .col-opt { display: none; }

  /* stacked reading order beats squeezed columns */
  .plannerlayout, .objectivegrid, .formrow { grid-template-columns: 1fr; }
  .cardlist { grid-template-columns: 1fr; }
  .sessioncard { gap: 12px; }
  .sessiondate { flex-basis: 62px; padding-right: 10px; }
  .itemhero { gap: 12px; }
  .mapholder.big { height: 220px; }
  /* (.mapholder.big.mapview keeps its square full-width box — the whole point
     of that view is seeing the map.) A phone-width cell is only ~40px, so the
     full-size coordinate pill would swallow the terrain under it. */
  .mapgrid-label { font-size: 9px; padding: 0 2px; top: 2px; left: 2px; }
  .plannerviewport { height: 60vh; min-height: 300px; }

  /* the marker editor is a centered 260px box — on a phone let it span the
     width of the screen instead, still centered vertically */
  .markerpop { left: 12px; right: 12px; width: auto; transform: translateY(-50%); }

  /* hover-only affordances never resolve on touch: the sticky's colour dots,
     pin and delete would be unreachable. Show them, and stop the metadata
     line hiding underneath them. */
  .stickytools { opacity: 1; pointer-events: auto; }
  .sresize { opacity: .6; }
  .stickymeta { display: none; }
  .boardarea { min-height: max(460px, calc(100vh - 300px)); }
}

@media (max-width: 520px) {
  .brand-word { display: none; }
  .navuser { gap: 6px; }
  .navuser .btn { padding: 7px 9px; font-size: 11px; }
  .navuser .feedbackbtn { padding-inline: 7px; font-size: 10.5px; }
}

/* Wide data tables scroll inside their own rail rather than pushing the page
   sideways — a horizontally scrolling body makes every other page feel broken.
   .tablescroll is the wrapper; the shadow hints there is more to the right. */
.tablescroll {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  background:
    linear-gradient(90deg, var(--bg) 30%, rgba(12, 15, 18, 0)) left / 22px 100% no-repeat,
    linear-gradient(90deg, rgba(12, 15, 18, 0), var(--bg) 70%) right / 22px 100% no-repeat,
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .5), transparent) left / 11px 100% no-repeat,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .5), transparent) right / 11px 100% no-repeat;
  background-attachment: local, local, scroll, scroll;
}
.tablescroll > .datatable { margin-top: 0; }

/* ------------------------------------- pinned search bars (items, tree) */
/* Search furniture pins under the topbar so filters stay reachable while
   scrolling a long list. --topbar-h is published by layout.php because the
   topbar wraps to 2-3 rows on phones; the fallback covers a no-JS load. The
   negative margin lets the opaque backing span .wrap's side padding, so rows
   scrolling underneath don't peek out at the edges. Declared last because
   .filterbar/.treetools set `margin` shorthand, which would otherwise win. */
.pinbar {
  position: sticky; top: var(--topbar-h, 52px); z-index: 30;
  margin-inline: calc(-1 * var(--wrap-pad)); padding: 10px var(--wrap-pad);
  /* Same gradient as .topbar, not flat --bg: the body's radial gradient is
     viewport-fixed, so a flat fill reads as a dark patch once pinned. This
     way the bar looks like an extension of the header. */
  background: linear-gradient(180deg, #12181f, #0e1318);
  border-bottom: 1px solid var(--line);
}
/* When .pinbar wraps a search row AND its drill toolbar (items.php, tree.php),
   the two rows pin together as one unit, so the tier chips, category/map
   selects and live count stay reachable while scrolling a long list — not just
   the search box. The inner rows drop their own outer margins, and the drillbar
   loses its own divider, so the .pinbar's single border is the only seam. (On
   guides.php .pinbar sits on the search form itself, not a wrapper, so these
   child rules don't apply there and its drillbar keeps scrolling away.) */
.pinbar > .treetools { margin: 0; }
.pinbar > .drillbar { margin: 10px 0 0; padding-bottom: 0; border-bottom: 0; }
/* On phones the full pinned toolbar would swallow most of the screen, so trim
   it to essentials: the search, selects and tier chips stay pinned, the chips
   drop onto one horizontally-scrollable row, and the redundant bulk goes —
   Expand/Collapse all (each tier header is tappable) and the items Search
   button (Enter in the box already submits the form). */
@media (max-width: 700px) {
  .pinbar > .drillbar { gap: 8px; margin-top: 8px; }
  .pinbar .tierfilter { margin-left: 0; width: 100%; flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .pinbar .tierfilter .tchip { flex: 0 0 auto; }
  .pinbar .btnrow, .pinbar .drillact { display: none; }
}

/* ----------------------------------------------------- alteration bench */
.altbase { display: flex; align-items: center; gap: 12px; margin: 6px 0 14px; }
.altattcard { display: flex; align-items: center; gap: 10px; margin: 6px 0 14px; padding: 8px 10px;
  background: var(--panel2); border: 1px solid var(--line); border-radius: 6px; }
.altattcard > div { flex: 1; min-width: 0; }
/* Attachment picker: every compatible attachment is its own clickable card
   (icon + name + granted perk) inside one shared form — clicking re-fits
   immediately (button value = att_id), no separate select-then-Update step,
   and the perk is visible before you pick rather than after. Capped height
   with its own scroll so a 20+-option class (knives, sledgehammers) can't run
   away with the column. */
.attgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 8px; margin: 10px 0 18px; max-height: 380px; overflow-y: auto; padding: 2px 2px 2px 0;
}
.attopt {
  display: flex; align-items: center; gap: 8px; text-align: left; min-width: 0;
  background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
  padding: 7px 10px; color: var(--text); cursor: pointer;
}
/* Same reason as .cardlist > .card[hidden] above: .attopt sets its own
   `display`, which otherwise beats the [hidden] attribute the picker filter
   script toggles — without this, "hidden" items stay rendered. */
.attopt[hidden], .basegroup-head[hidden] { display: none; }
.attopt:hover { border-color: var(--orange); }
.attopt.sel { border-color: var(--orange); box-shadow: 0 0 0 1px var(--orange); background: rgba(232, 134, 42, .08); }
.attopt-empty { color: var(--dim); font-style: italic; justify-content: center; }
.attopt-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.attopt-name { font-size: 12.5px; font-weight: 600; }
/* New-build base-item picker reuses .attgrid/.attopt (same icon+name card,
   same one-click-submits shape) at a taller cap — it's browsing the full
   ~225-item gear catalogue instead of one class's handful of attachments —
   grouped under a full-width gear_class heading per group. */
.basegrid { max-height: 460px; }
.basegroup-head {
  grid-column: 1 / -1; margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--line);
  font-size: 11px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--dim);
}
.basegroup-head:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.attopt-perk { font-size: 11px; color: var(--orange-hi); }
.altcardbase { display: flex; align-items: center; gap: 8px; margin: 4px 0; font-size: 13px; }
/* Quick-glance strip at the top of a build's detail view (pieces, fitted
   count, weight, craft cost) — reuses the talent tree's .statchip so both
   pages read as the same "key numbers" widget. */
.altsummary { display: flex; gap: 10px; flex-wrap: wrap; margin: 6px 0 22px; }
.stackform { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; }
.stackform label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
/* Attachment bonus value: no good/bad colour, since a negative number here can
   be the desirable direction (-50% weapon weight) as often as not — only the
   label says which. */
.altbonus { color: var(--orange-hi); font-weight: 700; }
/* Divider row inside the merged Stats table marking where the fitted
   attachment's own bonuses start, so one table can hold both without a
   separate heading/table per source. */
.datatable tr.stat-group th {
  padding-top: 16px; border-top: 1px solid var(--line);
  color: var(--dim); font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
}

/* ------------------------------------------------------ interactive map */
/* mapview.php. Controls sit ABOVE the map, not beside it, so the map gets the
   full content width. The source image is a 1536x1536 square (.mapcanvas), so
   the viewport is forced to that same 1:1 ratio instead of a wide rectangle —
   otherwise panzoom.js's fit() scales the square canvas to whichever side is
   smaller and letterboxes the rest as dead space. Stays inside .wrap's normal
   column (same left/right margins as the header content and every other page)
   rather than breaking out full-bleed. The viewport clones .plannerviewport's
   gesture setup (the JS drives pan/pinch, so the browser must not also scroll
   the page under it). */
.mapstage {
  background: #05070a; border: 1px solid var(--line); border-radius: 6px; overflow: hidden;
}
/* Positioning context for .mapinspect — the viewport's box alone, so the panel
   floats over the map rather than over the toolbar above it. */
.mapviewwrap { position: relative; }
.mapviewport {
  position: relative; overflow: hidden;
  width: 100%; aspect-ratio: 1 / 1;
  background: #05070a; cursor: grab;
  -webkit-user-select: none; user-select: none;
  touch-action: none;
}
.mapviewport:active { cursor: grabbing; }
.mapviewport.measuring { cursor: crosshair; }
.mapviewport:focus-visible { outline: 2px solid var(--orange); outline-offset: -2px; }
/* Native size: the source PNGs are 1536px square, so the deepest zoom is
   pixel-crisp rather than an upscale. panzoom.js fits it to the viewport.
   Direct-child selector so this doesn't also catch the game-icon <img>s
   nested inside .maplayer .pin (those size via .pinface instead). */
.mapcanvas { position: absolute; transform-origin: 0 0; width: 1536px; height: 1536px; }
.mapcanvas > img { display: block; max-width: none; width: 1536px; height: 1536px; }
.mapcanvas .empty { color: var(--dim); padding: 40px; }
/* Each layer is a full-canvas stack (crew under mine under unit) so a later,
   mostly-empty layer would otherwise swallow clicks meant for a pin in an
   earlier one — pointer-events:none here plus :auto on .pin lets clicks fall
   through empty space to whatever pin (or the bare map) is actually under
   the cursor. */
.maplayer { position: absolute; inset: 0; pointer-events: none; }

/* Same counter-scale as the grid labels: a pin marks a point, so it should
   stay the same size on screen and keep its tip on that point at any zoom. */
.mapcanvas .pin { transform: translate(-50%, -90%) scale(var(--zoom-inv, 1)); transform-origin: 50% 90%; }
/* A crew log's honest position is also the whole 504 m cell, not a point
   inside it — resources.php only ever validates a grid ref, never an exact
   spot, so this gets a box treatment rather than the tip-anchor transform
   (which would drop the pick icon at one arbitrary point in the cell,
   claiming precision nobody reported). */
.mapcanvas .pin.crewlog {
  transform: none;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: rgba(88, 166, 216, .16);
  border: 1px solid rgba(88, 166, 216, .55);
  border-radius: 2px;
  cursor: pointer;
}
.mapcanvas .pin.crewlog .pinface { transform: scale(var(--zoom-inv, 1)); filter: drop-shadow(0 0 4px rgba(88, 166, 216, .9)); }
.mapcanvas .pin.crewlog .pinlabel { transform: scale(var(--zoom-inv, 1)); }
.pin.crewlog:hover, .pin.crewlog.sel, .pin.crewlog:focus-visible {
  background: rgba(88, 166, 216, .3); border-color: rgba(88, 166, 216, .9); outline: none;
}
.pin.sel .pinface, .pin:focus-visible .pinface { filter: drop-shadow(0 0 5px var(--orange-hi)); outline: none; }

/* Named-region labels (Grove, Oasis, ...) — plain text, no icon, not
   clickable (inherits .maplayer's pointer-events:none). Same counter-scale
   as pins so the text stays a constant on-screen size at any zoom. */
.arealabel {
  position: absolute; transform: translate(-50%, -50%) scale(var(--zoom-inv, 1));
  font-size: 12px; font-weight: 600; letter-spacing: 1.2px; text-transform: uppercase;
  color: #f0e6d2; text-shadow: 0 1px 3px rgba(0, 0, 0, .95), 0 0 8px rgba(0, 0, 0, .8);
  white-space: nowrap; pointer-events: none;
}

/* Measure overlay. viewBox 0 0 100 100 + preserveAspectRatio:none means the
   JS can draw straight in map-percent coordinates. */
.mapmeasure { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.mapmeasure line { stroke: var(--orange-hi); stroke-width: .25; stroke-dasharray: 1 .6; }
.mapmeasure circle { fill: var(--orange-hi); }

/* Sits in .maptop, above the Pin/Key rows — a .maptoprow itself (see below),
   so it wraps and spaces the same way instead of needing its own layout. */
.mapzoombar .btn { padding: 5px 11px; font-size: 12px; }
.mapzoombar .btn.on { border-color: var(--orange); color: var(--orange); }
.gridjump {
  background: var(--bg2); border: 1px solid var(--line); border-radius: 4px;
  color: var(--text); padding: 5px 9px; font-size: 12px; width: 92px;
}
.coordread { font-family: var(--mono); font-size: 12px; white-space: nowrap; }

/* Zoom bar, pin picker and key, stacked above the map. All three rows wrap
   rather than scrolling, so they cost the map as little height as possible. */
.maptop { background: var(--panel); border-bottom: 1px solid var(--line); }
.maptoprow {
  display: flex; align-items: center; flex-wrap: wrap; gap: 5px 10px;
  padding: 3px 10px;
}
.maptoprow + .maptoprow { border-top: 1px solid #1a222b; }
.maptoplabel {
  flex: none; width: 46px;
  font-size: 10.5px; letter-spacing: 1.6px; text-transform: uppercase;
}
.mapsidenote { font-size: 12px; margin: 6px 0; }
.mapnotes { font-size: 12px; margin: 8px 2px; }

/* The key doubles as the legend: swatch + label + count, inline chips so the
   whole thing is one wrapped row instead of a column beside the map. */
.maplegend { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 6px; }
.legendrow {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px 3px 7px; border-radius: 20px; cursor: pointer; font-size: 12.5px;
  border: 1px solid var(--line); background: var(--bg2);
}
.legendrow:hover { border-color: var(--orange); color: var(--orange-hi); }
/* An unchecked layer reads as off without needing a second control. */
.legendrow:has(input:not(:checked)) { opacity: .45; }
/* These rows are <label>s, so the generic `label input { display:block;
   width:100% }` rule up top would stretch the checkbox across the row and
   crush the text beside it. Opt back out. */
.legendrow input[type=checkbox] {
  display: inline-block; width: auto; flex: none; margin: 0;
}
.legendrow .countnote { font-size: 11px; }
.legendswatch {
  width: 11px; height: 11px; flex: none; border-radius: 2px;
  background: var(--swatch, var(--dim));
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .5);
}
.spawnlist .legendlabel, .legendrow .legendlabel { flex: none; }

/* Floats over the top-right of the map — a sibling of the viewport, so
   interacting with it never reaches the pan/pin handlers underneath. */
.mapinspect {
  position: absolute; top: 10px; right: 10px; z-index: 5; width: 262px;
  font-size: 12.5px; background: var(--panel);
  border: 1px solid var(--orange); border-radius: 6px; padding: 10px 12px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, .6);
}
.mapinspect h3 { margin: 0 14px 4px 0; }
.mapinspect p { margin: 4px 0; }
.mapinspect-x {
  position: absolute; top: 4px; right: 6px;
  background: none; border: none; color: var(--dim);
  cursor: pointer; font-size: 13px; line-height: 1; padding: 2px 4px;
}
.mapinspect-x:hover { color: var(--orange-hi); }

/* Collapsed by default: these rows have no coordinates, so the panel cannot
   interact with the map — it is a reference list, and map.php already has the
   full tables. Open by default it would be the page's biggest clutter. */
.spawnpanel { margin-top: 16px; border-top: 1px solid var(--line); padding-top: 10px; }
.spawnpanel > summary {
  cursor: pointer; font-size: 12px; letter-spacing: 1.4px;
  text-transform: uppercase; color: var(--dim);
}
.spawnpanel > summary:hover { color: var(--orange-hi); }
.spawnpanel .treesearch { width: 100%; margin: 8px 0; }
.spawnlist { list-style: none; margin: 0; padding: 0; max-height: 46vh; overflow-y: auto; }
.spawnlist li {
  display: flex; align-items: center; gap: 7px;
  padding: 4px 2px; border-bottom: 1px solid #1a222b; font-size: 12.5px;
}
.spawnlist li .dim { margin-left: auto; text-align: right; font-size: 11px; }

/* Same opt-out as .legendrow: this is a <label>, so the select would go
   display:block and drop under its own caption. */
.mapvis { display: inline-flex; align-items: center; gap: 6px; }
.mapvis select {
  display: inline-block; width: auto; margin-top: 0;
  background: var(--bg2); border: 1px solid var(--line); color: var(--text);
  border-radius: 4px; padding: 3px 6px; font-size: 12px;
}
.markerpop.hidden { display: none; }
.markerpop select { width: 100%; margin-bottom: 8px; }

@media (max-width: 640px) {
  /* The floating inspector would cover most of a phone screen — dock it to the
     bottom edge full-width instead. (.markerpop stays centered but goes
     full-width — see its own mobile rule above.) */
  .mapinspect { top: auto; bottom: 0; left: 0; right: 0; width: auto; border-radius: 0; border-width: 1px 0 0; }
  .maptoplabel { width: 100%; }
  .mapenter-cta { font-size: 13px; padding: 10px 18px; letter-spacing: 1px; }
  .mapenter-sub { font-size: 10.5px; }
  .coordread { display: none; }
}
