/* The Blood of Dawnwalker — skill tree planner.
   Dark UI modelled on the in-game skill screen. Sans throughout for
   legibility; nothing is ever set below 12px. */

:root{
  --ink:#0b0908;
  --panel:#17120e;
  --panel-2:#1e1813;
  --line:#3a302a;
  --line-2:#584639;
  --text:#ece3d6;
  --text-dim:#b3a695;
  --text-faint:#8a7e70;
  --gold:#e7b356;
  --gold-2:#f6d79a;
  --gold-dim:#8a6c34;
  --blood:#a4262c;
  --blood-2:#e05a5c;
  --green:#8fc06e;
  /* synergy tiers — green carries impact, amber caution, red "do not read much
     into this". Each is lifted off the dark ground rather than a pure hue. */
  --tier-green:#6fc48a;
  --tier-yellow:#e0b153;
  --tier-red:#c4626a;
  --accent:var(--gold);

  --node:92px;
  --icon:46px;
  --row:180px;
  --colmin:96px;
  /* A ceiling on the gap between adjacent columns as well as a floor. The board
     holds no moving parts and nothing lives between the columns, so stretching
     the tree across every pixel of a wide window bought nothing but distance
     between the nodes — and it took the width out of the side panel, which does
     have something to say. Columns now stop growing at `--colmax` and the tree
     centres in whatever room is left. */
  --colmax:150px;
  /* The panel grows with the window instead of sitting at a fixed 400px: on a
     wide screen the perk text was reading in a column half the width of the
     empty space beside the tree. It is not allowed to take the width out of the
     board, though — capping the board at 1000px to pay for a 640px panel made
     eleven Swordmastery columns shrink their nodes to fit, and a squeezed tree
     is a worse trade than a narrower column of text. */
  --side:clamp(360px,25vw,520px);

  --shadow:0 16px 40px rgba(0,0,0,.6);

  /* The page's own width, and the gutter it keeps inside it. The stage is
     capped here, so the top bar is measured against the same two numbers
     rather than against the window: the bar's background still runs edge to
     edge, but the brand on its left and the controls on its right land on the
     same two lines as the board and the side panel below them. Before this the
     controls sat in the window's corner, a wide screen's worth of empty margin
     away from the column they act on. */
  --shell:2240px;
  --gutter:18px;

  /* Every mark here is painted as an alpha mask, so a subpath with no area —
     a bare `M..v..` line with no stroke — contributes nothing and vanishes.
     Several of these were drawn as if they were strokable glyphs and rendered
     as featureless blobs: a sun with no rays, a clock with no hands, a book
     that came out a solid white bar. Outlines now say `stroke` outright, and
     the shapes that cut holes say `fill-rule='evenodd'`.

     Skill points are a four-pointed star, not the crescent they used to share
     with NIGHT ONLY and nothing else — one glyph cannot mean two things. */
  --m-sp:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 1l2.7 8.3L23 12l-8.3 2.7L12 23l-2.7-8.3L1 12l8.3-2.7z'/></svg>");
  --m-ts:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M4.5 2h15v2.4h-1.8c0 3.3-2 5-3.7 7.6 1.7 2.6 3.7 4.3 3.7 7.6h1.8V22h-15v-2.4h1.8c0-3.3 2-5 3.7-7.6C8.3 9.4 6.3 7.7 6.3 4.4H4.5z'/></svg>");
  --m-cor:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill-rule='evenodd' d='M12 1.6c4.2 0 7.7 1.3 8.7 2.8-.6 6.6-3.1 12.8-8.7 17.9C6.4 17.2 3.9 11 3.3 4.4 4.3 2.9 7.8 1.6 12 1.6zM8.2 7.4l1.7 3.7 1.7-3.7zm4.9 0 1.7 3.7 1.7-3.7z'/></svg>");
  --m-shr:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 1.8L2.8 8.4h18.4zM5.6 8.4v11.2M18.4 8.4v11.2M2.4 21.4h19.2M9.4 12.6h5.2v7h-5.2z' fill='none' stroke='black' stroke-width='2.1' stroke-linejoin='round'/></svg>");
  --m-sun:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10z'/><path d='M12 .9v3.3M12 19.8v3.3M.9 12h3.3M19.8 12h3.3M4.1 4.1l2.3 2.3M17.6 17.6l2.3 2.3M19.9 4.1l-2.3 2.3M6.4 17.6l-2.3 2.3' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round'/></svg>");
  --m-moon:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M15.6 2.4a9.6 9.6 0 1 0 6 18 7.8 7.8 0 0 1-6-18z'/></svg>");
  --m-any:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M8 7.4a4.6 4.6 0 1 0 0 9.2 4.6 4.6 0 0 0 0-9.2z'/><path d='M8 3.4v2.2M8 18.4v2.2M1.2 12h2.2M2.9 6.9l1.5 1.5M2.9 17.1l1.5-1.5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round'/><path d='M18.6 5.9a6.6 6.6 0 0 1 3.6 12.2A7.8 7.8 0 0 0 18.6 5.9z'/></svg>");
  --m-cd:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2.6a9.4 9.4 0 1 0 0 18.8 9.4 9.4 0 0 0 0-18.8zM12 6.8V12l3.9 2.7' fill='none' stroke='black' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  --m-chg:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 2l5.4 5.2L12 22 6.6 7.2zM6.6 7.2h10.8M12 2v20'/></svg>");
  --m-hp:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 21S3.2 15.2 3.2 9.4A4.8 4.8 0 0 1 12 6.7a4.8 4.8 0 0 1 8.8 2.7C20.8 15.2 12 21 12 21z'/></svg>");
  --m-bk:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 6.4C9.7 4.5 6.7 3.9 3.2 4.3v13.4c3.5-.4 6.5.2 8.8 2.1 2.3-1.9 5.3-2.5 8.8-2.1V4.3c-3.5-.4-6.5.2-8.8 2.1zm0 0v13.4' fill='none' stroke='black' stroke-width='2.1' stroke-linejoin='round'/></svg>");
  --m-lock:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M6 10h12v11H6zM9 10V7a3 3 0 0 1 6 0v3' fill='none' stroke='black' stroke-width='2.2'/></svg>");
}

/* ------------------------------------------------- the three tree colours
   The game gives each tree a colour and uses it for everything that tree
   touches — the hex frame around a perk icon, the ability cards, the panel.
   Witchcraft is purple, Swordmastery steel grey, Vampirism blood red. The
   planner used to paint all three gold and keep the tree colour for one tab
   mark, so the only thing on screen that said which tree you were reading was
   the word at the top.

   Five tokens per tree, and every tree-scoped rule below is written in terms of
   them, so switching tab repaints the board, the tabs, the drawers and the side
   panel together:

     --accent      the tree's own colour: rims, links, learned levels
     --accent-2    its bright end: hover, maxed, headings on dark
     --accent-dim  its dark end, the far side of every rim gradient
     --accent-deep the near-black tint the icon well is filled with
     --accent-ink  text laid ON the colour, so a filled hex reads at any hue

   Gold stays the app's own colour — the top bar, the brand, the resource
   meters — so the chrome does not change under you on every tab click. */
:root{
  --accent:var(--gold);
  --accent-2:var(--gold-2);
  --accent-dim:var(--gold-dim);
  --accent-deep:#2e2318;
  --accent-ink:#1a1209;
}
.t-wc,.tab.t-wc{
  --accent:#a878e0;--accent-2:#dcbcff;--accent-dim:#5d3f8c;
  --accent-deep:#241733;--accent-ink:#150c22;
}
.t-sm,.tab.t-sm{
  --accent:#a9b7c4;--accent-2:#e8f0f7;--accent-dim:#5b6773;
  --accent-deep:#1f262c;--accent-ink:#0d1115;
}
.t-vp,.tab.t-vp{
  --accent:#d94a45;--accent-2:#ff958b;--accent-dim:#7d2b28;
  --accent-deep:#2f1210;--accent-ink:#1b0a09;
}
/* A colour to glow with, once, rather than a colour-mix repeated in fifteen
   box-shadows. */
.t-wc,.t-sm,.t-vp,:root{--accent-glow:color-mix(in srgb,var(--accent) 55%,transparent)}

*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;
  display:flex;flex-direction:column;
  overflow:hidden;
  background:
    radial-gradient(1200px 700px at 50% -10%, #241a13 0%, rgba(36,26,19,0) 60%),
    radial-gradient(900px 700px at 100% 110%, #1d1210 0%, rgba(29,18,16,0) 60%),
    var(--ink);
  color:var(--text);
  font-family:Inter, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  font-size:16px;line-height:1.5;
  -webkit-font-smoothing:antialiased;
}
button,input,select{font:inherit;color:inherit}
a{color:var(--gold);text-decoration:none}
a:hover{color:var(--gold-2)}

/* ------------------------------------------------------- scrollbars
   The default bars are a bright system chrome that cuts straight through the
   dark skin, and they are most visible exactly where they are needed most —
   a short window, where every pane scrolls. Style them to match: a track that
   disappears into the panel it sits on, a thumb the colour of the borders,
   picking up the tree accent on hover. Firefox gets the two-keyword form,
   WebKit the full pseudo-elements. */
*{scrollbar-width:thin;scrollbar-color:var(--line-2) transparent}
*::-webkit-scrollbar{width:10px;height:10px}
*::-webkit-scrollbar-track{background:rgba(0,0,0,.26);border-radius:6px}
*::-webkit-scrollbar-thumb{
  background:var(--line-2);border-radius:6px;
  border:2px solid transparent;background-clip:padding-box;
}
*::-webkit-scrollbar-thumb:hover{background:var(--accent-dim);background-clip:padding-box}
*::-webkit-scrollbar-thumb:active{background:var(--accent);background-clip:padding-box}
*::-webkit-scrollbar-corner{background:transparent}

/* ------------------------------------------------------------ top bar
   Never more than one row. `flex-wrap:wrap` on five groups of single-height
   controls is what broke it into three stacked rows and took 167px off the top
   of the board; the groups are two lines tall each now and the bar does not
   wrap at all. Everything inside may shrink, and the things that carry no
   information on their own — the brand subtitle, the slider label, the button
   padding — give way in that order as the window narrows. The totals no longer
   ride here: they sit over the board, where the clicks that change them are. */
.topbar{
  flex:none;display:flex;align-items:center;gap:18px;flex-wrap:nowrap;
  padding-block:8px;border-bottom:1px solid var(--line);
  background:linear-gradient(180deg,rgba(28,21,16,.96),rgba(13,10,8,.92));
}
/* `max()` rather than a wrapper element: below the cap each bar keeps its plain
   gutter, above it the padding grows by exactly the margin the stage takes, so
   the two line up at every width with nothing extra in the DOM. */
.topbar,.loadedbar{
  padding-inline:max(var(--gutter),calc((100% - var(--shell)) / 2 + var(--gutter)));
}
/* The intro line under the bar: the page's <h1> and one sentence saying what it
   is, for a visitor arriving from a search. One line that never wraps; where the
   sentence will not fit it is cut, and on a phone it goes and the heading stays. */
.intro{
  flex:none;display:flex;align-items:baseline;gap:14px;min-width:0;
  padding-block:6px 0;
  padding-inline:max(var(--gutter),calc((100% - var(--shell)) / 2 + var(--gutter)));
}
.intro h1{margin:0;flex:none;font-size:13px;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--gold-2)}
.intro p{margin:0;min-width:0;font-size:13px;color:var(--text-faint);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@media (max-width:760px){
  .intro{padding-block:6px 2px}
  .intro h1{font-size:12px;letter-spacing:.08em}
  .intro p{display:none}
}
.brand{display:flex;align-items:center;gap:11px;min-width:0;flex:0 1 auto}
/* The hex ratio. A pointy-top hexagon is taller than it is wide — width is
   height x sqrt(3)/2 — so drawing this clip-path in a square box stretches it
   sideways by 15%, which reads as a hexagon squashed vertically. Every box the
   shape is cut from is 0.866 of its own height wide, so the shape is regular
   and the element's bounds are the shape's bounds. */
.brand-mark{
  width:calc(30px * .866);height:30px;flex:none;
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background:linear-gradient(150deg,var(--gold),var(--blood));
}
.brand-text{display:flex;flex-direction:column;line-height:1.2;min-width:0}
.brand-text b{font-size:14px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--gold-2);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.brand-text i{font-style:normal;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--text-faint);
  white-space:nowrap}

/* Two lines per group is the whole trick: the bar's height is set by the
   tallest group, and every group is two rows, so none of them costs anything
   the others were not already costing. */
.bargroup{display:grid;row-gap:4px;flex:none;align-content:center}
/* The bar's flexible gaps sit either side of the totals, not in front of the
   sliders: the brand stays left, the controls stay packed right, and the three
   numbers land in the middle of what is left rather than in the far corner. */
.sliders{grid-template-columns:auto;column-gap:0;min-width:0;flex:0 1 auto}
.checks{grid-template-columns:auto}
.acts{grid-template-columns:repeat(3,auto);column-gap:6px}

/* --------------------------------------------------------- the totals
   One line across the middle of the bar, not the 2x2 block in its corner they
   used to be: three numbers on a line read as three numbers, where a grid of
   them reads as a table to be studied. They are the only figures on the page
   that change on every click, so they are the one thing here worth a size you
   can read without looking for it. One line is the point of them, so wrapping
   is held off until 1080, where the bar has nothing left to give. */
.meters{
  display:flex;flex-wrap:nowrap;align-items:center;justify-content:center;
  column-gap:26px;row-gap:4px;flex:0 1 auto;min-width:0;margin-inline:auto;
}
.meter{display:flex;align-items:baseline;gap:9px;white-space:nowrap}
.meter-ico{width:17px;height:17px;flex:none;background:currentColor;align-self:center;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}
.ico-sp{color:var(--blood-2);-webkit-mask-image:var(--m-sp);mask-image:var(--m-sp)}
.ico-ts{color:var(--gold);-webkit-mask-image:var(--m-ts);mask-image:var(--m-ts)}
.ico-bk{color:var(--text-dim);-webkit-mask-image:var(--m-bk);mask-image:var(--m-bk)}
.meter-val{font-size:22px;font-weight:700;color:var(--gold-2);line-height:1.1;min-width:1ch;text-align:right}
.meter-lbl{font-size:12px;letter-spacing:.09em;text-transform:uppercase;color:var(--text-faint)}
/* Two labels per meter, one shown at a time. "Skill points" and "Time segments"
   are what they are called; "Skills" and "Time" are what they mean, and next to
   their own icon and a number that is all the word has left to do. The long
   form is worth the width while there is width to spare, so the short one waits
   for the first step of the ladder that actually runs out of room. */
.meter-lbl b{font-weight:inherit}
.meter-lbl i{font-style:normal;display:none}

.ctl{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-dim);min-width:0}
/* These two labels used to be blood red, which made a pair of quiet settings the
   loudest thing in a bar of gold chrome. They are meter labels in everything but
   name, so they are painted like the meter labels. */
.ctl-lbl{flex:none;width:8.2em;font-size:11px;letter-spacing:.07em;text-transform:uppercase;color:var(--text-faint)}

/* ------------------------------------------------------------- sliders
   Drawn by hand rather than left to `accent-color`, which gives the filled
   half the accent and the empty half the browser's own near-white — a bright
   bar sitting in the dark top bar, next to nothing else that is white. Both
   halves are ours now: the fill is the app's gold, the rest is the same warm
   near-black the panels are made of. `--fill` is the value as a percentage,
   written by the script on every input; Firefox would manage without it via
   ::-moz-range-progress, but one gradient for both engines is one thing to
   keep true rather than two. */
.ctl input[type=range]{
  -webkit-appearance:none;appearance:none;
  flex:1 1 auto;width:auto;min-width:70px;max-width:132px;margin:0;
  height:16px;background:transparent;cursor:pointer;
}
.ctl input[type=range]::-webkit-slider-runnable-track{
  height:6px;border-radius:999px;border:1px solid var(--line);
  background:linear-gradient(90deg,
    var(--gold-dim) 0%, var(--gold) var(--fill,100%), #221a13 var(--fill,100%));
}
.ctl input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;
  width:14px;height:14px;margin-top:-5px;border-radius:50%;
  background:radial-gradient(circle at 35% 30%,var(--gold-2),var(--gold-dim));
  border:1px solid #120e0a;box-shadow:0 0 0 1px rgba(231,179,86,.28);
}
.ctl input[type=range]::-moz-range-track{
  height:6px;border-radius:999px;border:1px solid var(--line);background:#221a13;
}
.ctl input[type=range]::-moz-range-progress{
  height:6px;border-radius:999px;background:linear-gradient(90deg,var(--gold-dim),var(--gold));
}
.ctl input[type=range]::-moz-range-thumb{
  width:13px;height:13px;border-radius:50%;
  background:radial-gradient(circle at 35% 30%,var(--gold-2),var(--gold-dim));
  border:1px solid #120e0a;box-shadow:0 0 0 1px rgba(231,179,86,.28);
}
.ctl input[type=range]:focus{outline:none}
.ctl input[type=range]:focus-visible{outline:2px solid var(--gold-dim);outline-offset:3px;border-radius:999px}
.ctl output{font-size:15px;font-weight:700;color:var(--gold-2);min-width:2ch;text-align:right}
.ctl-check{cursor:pointer;user-select:none;font-size:12.5px;white-space:nowrap}
.ctl-check input{width:15px;height:15px;flex:none;accent-color:var(--gold-dim);margin:0}

.btn{
  padding:7px 12px;border:1px solid var(--line-2);border-radius:3px;cursor:pointer;
  background:linear-gradient(180deg,rgba(231,179,86,.18),rgba(231,179,86,.06));
  color:var(--gold-2);font-size:11.5px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;
  white-space:nowrap;transition:.15s;
}
.btn:hover{border-color:var(--gold-dim);background:linear-gradient(180deg,rgba(231,179,86,.3),rgba(231,179,86,.1))}
.btn-ghost{background:none;color:var(--text-dim)}
.btn-ghost:hover{color:var(--text);background:rgba(255,255,255,.05)}
.btn:disabled{opacity:.4;cursor:not-allowed;filter:grayscale(.4)}

/* The pick-mode switch. A toggle rather than a third button: it does nothing on
   its own, it changes what every click on the board means, so it has to show
   which of the two it is in rather than what pressing it would do. Off is a
   ghost button, on is lit — the same on/off language as an ultimate card. */
.btn-toggle{display:inline-flex;align-items:center;gap:7px;justify-content:flex-start}
.btn-toggle[hidden]{display:none}
.btn-toggle .sw{
  width:24px;height:13px;flex:none;border-radius:999px;position:relative;
  background:#2a211a;box-shadow:inset 0 0 0 1px var(--line-2);transition:.15s;
}
.btn-toggle .sw::after{
  content:'';position:absolute;top:2px;left:2px;width:9px;height:9px;border-radius:50%;
  background:var(--text-faint);transition:.15s;
}
.btn-toggle[aria-pressed=true]{
  color:var(--gold-2);border-color:var(--gold-dim);
  background:linear-gradient(180deg,rgba(231,179,86,.28),rgba(231,179,86,.09));
}
.btn-toggle[aria-pressed=true] .sw{background:var(--gold-dim);box-shadow:inset 0 0 0 1px var(--gold)}
.btn-toggle[aria-pressed=true] .sw::after{left:13px;background:var(--gold-2)}

/* The give-way ladder. Each step drops the least informative thing left, so the
   bar holds one row down to 760px — a phone, and nothing wider. */
/* The step where the two-word labels stop being affordable: at full tracking the
   bar needs 1609px to hold them next to everything else, so they hand over just
   above that. Below it the icon, the number and one word carry the whole
   meaning, and the 127px they give back goes to the controls. */
@media (max-width:1600px){
  .meter-lbl b{display:none}
  .meter-lbl i{display:inline}
  .meters{column-gap:22px}
}
@media (max-width:1500px){
  .topbar{gap:14px}
  .brand-text i{display:none}
  .ctl-lbl{width:auto}
  .ctl input[type=range]{max-width:104px}
}
/* An ellipsised game title reads as a bug rather than as a fit, so the title
   drops its tracking here and everything around it gives up a few pixels a step
   earlier than it used to — the totals are a wider group on one line than they
   were as a 2x2 block in the corner, and the width has to come from somewhere.
   In order: the title's tracking, then the totals' own gaps, then the button
   padding and the slider length. */
@media (max-width:1440px){
  .brand-text b{font-size:13px;letter-spacing:.05em}
  .meters{column-gap:18px}
  .meter{gap:7px}
  .meter-val{font-size:20px}
}
@media (max-width:1400px){
  .topbar{gap:12px}
  .btn{padding:6px 9px;letter-spacing:.06em}
  .ctl input[type=range]{max-width:88px}
}
@media (max-width:1240px){
  .topbar{gap:11px}
  .meters{column-gap:14px}
  .meter-lbl{font-size:11px;letter-spacing:.06em}
}
/* The wordmark goes, the hex mark stays. It is the last thing on the bar that
   carries no information — the page's title is in the tab and the <h1> either
   way — and below this the alternative is ellipsising it, or taking the width
   out of the sliders' own labels, which do say what they set. */
@media (max-width:1300px){ .brand-text{display:none} }
/* Below this the bar genuinely runs out of room for three totals on one line.
   They wrap to two — which costs no height at all, because every other group in
   the bar is already two lines tall — rather than being squeezed until the
   labels clip. */
@media (max-width:1080px){
  .ctl-check span{font-size:12px}
  .meters{flex-wrap:wrap;column-gap:14px;row-gap:2px}
  .meter-val{font-size:19px}
  .meter-lbl{font-size:10.5px;letter-spacing:.05em}
}

/* The width where the bar is allowed a second row. It used to be 640, back when
   the totals were a narrow 2x2 block; one line of three is wider than that, and
   holding all four groups on one row below this squeezed the totals until their
   labels clipped. Two rows here, never three — the brand and the totals share
   the first, the controls take the second. */
@media (max-width:760px){
  .topbar{flex-wrap:wrap;row-gap:8px;gap:10px}
  .brand{order:1}
  .meters{order:2;margin-inline:auto 0;column-gap:16px;flex-wrap:nowrap}
  .sliders{order:3;margin-left:0;flex:1 1 180px}
  .checks{order:4}
  .acts{order:5;margin-left:auto}
}

/* --------------------------------------------------------------- tabs */
/* The tab strip belongs to the board, not to the page: it is the board's own
   header. Spanning the full width left the whole right-hand third of the row
   empty and pushed the side column down by the height of a row it had no part
   in, so the panel started 48px below the top bar with nothing in between. */
/* Nowrap: three tabs breaking into two rows put Vampirism on a line of its own
   under the other two, which reads as a heading rather than a third choice.
   They shrink instead, and only a window narrow enough that they genuinely
   cannot fit gets a sideways scroll. */
.tabs{flex:none;display:flex;gap:8px;justify-content:center;flex-wrap:nowrap;
  padding:0 0 8px;overflow-x:auto;scrollbar-width:none}
.tabs{justify-content:safe center}
.tabs::-webkit-scrollbar{height:0}
.tab{
  display:flex;align-items:center;gap:9px;padding:7px 15px;flex:none;
  border:1px solid transparent;border-radius:3px;background:none;cursor:pointer;
  color:var(--text-dim);font-size:13.5px;font-weight:600;letter-spacing:.11em;text-transform:uppercase;
  white-space:nowrap;transition:.15s;
}
/* Each tab carries its own tree's colour, selected or not — the strip is the
   legend for the three hues as well as the way between them. */
.tab:hover{color:var(--accent-2);border-color:color-mix(in srgb,var(--accent) 30%,transparent)}
.tab .tab-mark{
  width:calc(25px * .866);height:25px;flex:none;display:grid;place-items:center;
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background:linear-gradient(150deg,color-mix(in srgb,var(--accent) 30%,transparent),rgba(255,255,255,.04));
  color:var(--accent-2);
}
.tab .tab-mark svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.7}
.tab[aria-selected=true]{
  color:var(--accent-2);
  border-color:color-mix(in srgb,var(--accent) 55%,transparent);
  background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 16%,transparent),rgba(0,0,0,0));
}
.tab[aria-selected=true] .tab-mark{
  background:linear-gradient(150deg,var(--accent-2),var(--accent-dim));color:var(--accent-ink);
  box-shadow:0 0 12px color-mix(in srgb,var(--accent) 45%,transparent);
}
.tab .tab-stat{
  display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:500;
  letter-spacing:.02em;text-transform:none;color:var(--text-faint);
}
.tab[aria-selected=true] .tab-stat{color:color-mix(in srgb,var(--accent-2) 70%,var(--text-faint))}
.tab .tab-stat i{width:12px;height:12px;background:currentColor;font-style:normal;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}
.tab .tab-stat .s-sp{-webkit-mask-image:var(--m-sp);mask-image:var(--m-sp)}
.tab .tab-stat .s-bk{-webkit-mask-image:var(--m-bk);mask-image:var(--m-bk)}
/* When a whole tree works only by day, or only by night, that is a fact about
   the tree — it was printed again on all 19 of Swordmastery's perk cards and
   all 17 of Vampirism's, which is 36 lines saying one thing. It is read once,
   here, on the tree's own tab. A perk card carries it only where it disagrees
   with its tree, which in the shipped data is the seven ANYTIME perks in an
   otherwise DAY ONLY Witchcraft. */
.tab .tab-when{
  display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;
  letter-spacing:.1em;color:var(--text-faint);
}
.tab[aria-selected=true] .tab-when{color:color-mix(in srgb,var(--accent) 70%,var(--text-dim))}
.tab .tab-when i{width:12px;height:12px}
@media (max-width:1400px){
  .tab{padding:6px 11px;font-size:12.5px;letter-spacing:.07em;gap:7px}
  .tab .tab-mark{width:calc(22px * .866);height:22px}
  .tabs{gap:5px}
}

/* -------------------------------------------------------------- stage */
/* Past about 2240px neither the board nor the panel has anything left to do with
   the width: the widest tree stops growing at `--colmax` and the panel at its
   own clamp, so the rest would be margin either way. Centring the stage puts
   that margin on both sides rather than stranding the tree in the middle of an
   empty board. The top bar's background stays full-bleed — it is a header, and
   it reads as one — but its contents are padded onto this same width, so the
   brand lines up with the board and the buttons with the side panel. */
.stage{
  flex:1;min-height:0;width:100%;max-width:var(--shell);margin-inline:auto;
  display:grid;grid-template-columns:minmax(0,1fr) var(--side);gap:16px;
  padding:6px var(--gutter) 14px;
}
.board{
  display:flex;flex-direction:column;min-width:0;min-height:0;
  overflow-y:auto;overscroll-behavior-y:contain;scroll-behavior:smooth;
}
/* The tab strip is the board's header and stays put while the board scrolls
   under it — the tree is now allowed to be taller than the column, and losing
   the tree selector off the top of a scrolled board is not a trade worth making. */
.board>.tabs{
  position:sticky;top:0;z-index:3;padding-top:2px;
  background:linear-gradient(180deg,var(--ink) 62%,rgba(11,9,8,.86));
}
/* Horizontal only. `overflow-y:visible` next to `overflow-x:auto` is not honoured —
   the spec makes the browser compute it as `auto` — so it has to be said outright,
   or the board grows a second vertical scrollbar it never wanted. */
.boardscroll{flex:none;min-height:0;overflow-x:auto;overflow-y:hidden}
.tree{position:relative;margin:0 auto}
.links{position:absolute;inset:0;width:100%;height:100%;overflow:visible;pointer-events:none}
/* The wiring takes the tree's colour too, at three strengths: a dead link is
   the colour at a whisper, a lit one (parent learned, child not yet) is half,
   a finished chain is the colour itself. */
.link{fill:none;stroke:color-mix(in srgb,var(--accent) 20%,#2c251f);stroke-width:2}
.link.lit{stroke:color-mix(in srgb,var(--accent) 52%,#2c251f)}
.link.done{stroke:var(--accent);filter:drop-shadow(0 0 5px var(--accent-glow))}
.nodes{position:absolute;inset:0}

/* --------------------------------------------------------------- node */
.node{
  position:absolute;width:calc(var(--node) * .866);height:var(--node);
  transform:translate(-50%,-50%);
  background:none;border:0;padding:0;cursor:pointer;display:grid;place-items:center;
}
/* A hex is a rim and a well. The rim is the 2px the frame shows around its own
   ::after, and it is where the tree's colour lives — the game rims every node
   in its tree's hue whether or not you have spent anything on it. The well is
   near-black with the same hue mixed in, so the icon sits on tinted dark rather
   than on the same brown in all three trees. */
.node .frame{
  position:absolute;inset:0;
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background:linear-gradient(160deg,
    color-mix(in srgb,var(--accent) 42%,#443a31),
    color-mix(in srgb,var(--accent-dim) 45%,#241d18));
  transition:.18s;
}
.node .frame::after{
  content:"";position:absolute;inset:2px;
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background:linear-gradient(160deg,color-mix(in srgb,var(--accent-deep) 62%,#171310),#0d0b09);
  transition:.18s;
}
.node svg.glyph{
  position:relative;width:var(--icon);height:var(--icon);
  stroke:#86786a;fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;
  transition:.18s;
}
/* A node's mark is either a drawn glyph (stroked) or the game's icon as an
   alpha mask painted with currentColor. Both have to answer to the same seven
   state rules below, so the mask reads `color` where the glyph reads `stroke`. */
.mark{
  position:relative;display:inline-block;
  width:var(--icon);height:var(--icon);
  background:currentColor;color:#86786a;
  -webkit-mask:var(--mark) center/contain no-repeat;
          mask:var(--mark) center/contain no-repeat;
  transition:.18s;
}
.node.avail .mark{color:var(--text)}
.node.taken .mark{color:var(--accent-2)}
.node.gated .mark{color:color-mix(in srgb,var(--accent) 22%,#7a6f64)}
.node.locked .mark{color:#5a5148}
.node:hover .mark{color:var(--accent-2)}
.ult .ult-mark .mark{width:20px;height:20px;color:var(--text-faint)}
.abil .abil-mark .mark{width:22px;height:22px;color:#8d7f70}
.abil.avail .abil-mark .mark{color:var(--text)}
.abil.taken .abil-mark .mark{color:var(--accent-ink)}
.ult.on .ult-mark .mark{color:var(--accent-ink)}
.panel-hero .mark{width:40px;height:40px;color:var(--accent-2)}

.node .pips{position:absolute;left:50%;bottom:-11px;transform:translateX(-50%);display:flex;gap:5px}

/* Level markers. The game draws a diamond per level: filled for what you have,
   a hollow bright one on the level you can buy next, dark for the rest. Perk
   nodes and ability rows share the rule so the two read the same. */
.pip{width:8px;height:8px;flex:none;transform:rotate(45deg);
  background:#2b241e;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--accent) 34%,#4e4238)}
.pip.on{background:var(--accent);box-shadow:0 0 8px var(--accent-glow)}
.pip.next{background:transparent;box-shadow:inset 0 0 0 1.5px var(--accent-2)}
/* Nudged in from the rim now that the box is the hexagon's true width rather
   than 15% wider than it: at right:4px the manual badge sat on the slant. */
.node .badge{position:absolute;right:6px;bottom:18px;width:17px;height:17px;
  background:var(--text-faint);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:contain;mask-size:contain;-webkit-mask-image:var(--m-bk);mask-image:var(--m-bk)}
.node .badge.lock{background:var(--blood-2);-webkit-mask-image:var(--m-lock);mask-image:var(--m-lock)}
.node .badge.got{background:var(--green)}
.node .name{
  position:absolute;top:calc(100% + 16px);left:50%;transform:translateX(-50%);
  text-align:center;font-size:13px;font-weight:500;line-height:1.2;
  color:var(--text-faint);pointer-events:none;transition:.18s;
}
/* synergy marker — experimental */
.node .syn{
  position:absolute;bottom:16px;left:10px;width:7px;height:7px;border-radius:50%;
  background:#fff4d6;box-shadow:0 0 9px 2px color-mix(in srgb,var(--accent) 75%,transparent);
  opacity:0;transform:scale(.4);transition:.2s;
}
.node.synergy .syn{opacity:1;transform:scale(1)}
/* Same grading as the panel rows, so a glance at the board and a read of the
   list agree with each other. Weak links are not marked here at all. */
.node.syn-green .syn{background:var(--tier-green);
  box-shadow:0 0 9px 2px color-mix(in srgb,var(--tier-green) 70%,transparent)}
.node.syn-yellow .syn{background:var(--tier-yellow);
  box-shadow:0 0 9px 2px color-mix(in srgb,var(--tier-yellow) 60%,transparent)}

.node.avail .frame{background:linear-gradient(160deg,
  color-mix(in srgb,var(--accent-2) 72%,#cfc4b4),color-mix(in srgb,var(--accent-dim) 70%,#403528))}
.node.avail svg.glyph{stroke:var(--text)}
.node.avail .name{color:var(--text-dim)}
/* Steel grey is a quiet colour on a dark board, so learned nodes carry a
   little of their own light as well as the rim — in every tree, so the three
   read the same way. */
.node.taken .frame{background:linear-gradient(160deg,var(--accent),var(--accent-dim));
  filter:drop-shadow(0 0 7px color-mix(in srgb,var(--accent) 30%,transparent))}
.node.taken .frame::after{background:linear-gradient(160deg,
  color-mix(in srgb,var(--accent-deep) 88%,#20180f),#12100c)}
.node.taken svg.glyph{stroke:var(--accent-2)}
.node.taken .name{color:var(--text)}
.node.maxed .frame{background:linear-gradient(160deg,var(--accent-2),var(--accent));
  filter:drop-shadow(0 0 12px color-mix(in srgb,var(--accent) 40%,transparent))}
.node.gated svg.glyph{stroke:color-mix(in srgb,var(--accent) 22%,#7a6f64)}
.node.locked{cursor:pointer}
.node.locked .frame{background:linear-gradient(160deg,
  color-mix(in srgb,var(--accent) 14%,#2b241e),#15110e)}
.node.locked .frame::after{background:linear-gradient(160deg,#171310,#0d0b09)}
.node.locked svg.glyph{stroke:#5a5148}
.node.locked .name{color:#6f665c}
.node:hover .frame{background:linear-gradient(160deg,var(--accent-2),var(--accent))}
.node:hover svg.glyph{stroke:var(--accent-2)}
.node:hover .name{color:var(--accent-2)}
.node.selected .frame{background:linear-gradient(160deg,
  color-mix(in srgb,var(--accent-2) 55%,#fff),var(--accent));
  filter:drop-shadow(0 0 16px color-mix(in srgb,var(--accent) 60%,transparent))}
.node.selected .name{color:var(--accent-2)}
.node:focus-visible{outline:none}
.node:focus-visible .frame{background:linear-gradient(160deg,#fff,var(--accent-2))}
/* In confirm mode the selection is a standing state — it is what the Learn and
   Refund buttons will act on — rather than a highlight trailing the cursor, so
   it burns brighter than hover instead of merely differently. */
body.confirm-mode .node.selected .frame{
  filter:drop-shadow(0 0 20px color-mix(in srgb,var(--accent) 85%,transparent))}
body.confirm-mode .abil.selected{box-shadow:0 0 0 1px var(--accent),0 0 18px -4px var(--accent-glow)}

/* ------------------------------------------------------------ drawers */
.drawer{flex:none;border:1px solid var(--line);border-radius:4px;background:rgba(20,15,11,.72);margin-top:10px}
.drawer>summary{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:10px 14px;cursor:pointer;list-style:none;
}
.drawer>summary::-webkit-details-marker{display:none}
.drawer>summary::before{
  content:"";width:9px;height:9px;flex:none;border-right:2px solid var(--accent);
  border-bottom:2px solid var(--accent);transform:rotate(-45deg);transition:.18s;margin-right:2px;
}
.drawer[open]>summary::before{transform:rotate(45deg)}

/* Folded, the ultimates drawer is one row of counter. It only asks to be opened
   at the moment the requirement is met and nothing has been chosen yet — before
   that there is nothing to act on, and afterwards the choice is permanent. */
.drawer.ready{border-color:color-mix(in srgb,var(--accent) 55%,transparent);
  background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 10%,rgba(30,23,14,.8)),rgba(20,15,11,.8))}
.drawer.ready>summary::before{border-color:var(--accent-2)}
.drawer-title{font-size:13px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--accent-2)}
.drawer-note{font-size:12px;color:var(--text-faint);flex:1}
/* The counter sits on a folded summary, so it never wraps under the title and
   never shrinks away: it is the whole reason the row is readable closed. */
.ult-gate{
  flex:none;font-size:12px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--blood-2);white-space:nowrap;
}
.ult-gate.met{color:var(--green)}

/* The plain-text reference (generated by tools/build_seo.py). It is a reading
   list, not a control, so it is set as prose: the tree name as a heading, then
   one line per perk with the name in the tree's colour. */
.ref{padding:4px 14px 14px;border-top:1px solid var(--line);font-size:14px;line-height:1.5;color:var(--text-dim)}
.ref-intro{margin:12px 0 4px;color:var(--text)}
.ref h2{margin:18px 0 2px;font-size:14px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--gold-2)}
.ref h3{margin:12px 0 4px;font-size:12px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--text-faint)}
.ref p{margin:0 0 6px}
.ref-src{font-size:13px;color:var(--text-faint)}
.ref-lv{margin:2px 0 6px;padding-left:22px;font-size:13px}
.ref-lv li{margin:1px 0}
.ref ul{margin:0;padding-left:18px}
.ref li{margin:3px 0}
.ref li b{color:var(--text);font-weight:600}
.ref li i{color:var(--text-faint);font-style:normal}

.ults{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:10px;
  padding:12px 14px 14px;border-top:1px solid var(--line)}
.ult{
  display:flex;gap:12px;text-align:left;align-items:flex-start;padding:12px;
  border:1px solid var(--line);border-radius:3px;cursor:pointer;
  background:linear-gradient(180deg,rgba(33,26,19,.8),rgba(15,12,9,.8));transition:.15s;
}
.ult:hover{border-color:var(--line-2)}
.ult .ult-mark{width:calc(38px * .866);height:38px;flex:none;display:grid;place-items:center;
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);background:rgba(255,255,255,.07)}
.ult .ult-mark svg{width:20px;height:20px;stroke:var(--text-faint);fill:none;stroke-width:1.5}
.ult b{display:block;font-size:13px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text)}
.ult .alias{display:block;font-size:12px;font-style:italic;color:var(--text-faint);margin-top:2px;
  letter-spacing:0;text-transform:none;font-weight:400}
.ult .ult-eff{display:block;font-size:14px;color:var(--text-dim);margin-top:5px}
.ult .ult-cost{display:flex;gap:12px;margin-top:7px;font-size:13px}
.ult.locked{opacity:.45;cursor:not-allowed}
.ult.on{border-color:var(--accent);
  background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 20%,transparent),rgba(21,16,11,.85))}
.ult.on .ult-mark{background:linear-gradient(150deg,var(--accent-2),var(--accent-dim))}
.ult.on .ult-mark svg{stroke:var(--accent-ink)}
.ult.on b{color:var(--accent-2)}

/* ---------------------------------------------------------- abilities */
.abils{padding:0 14px 14px}
/* auto-FIT, not auto-fill. auto-fill keeps every track it can fit even when
   nothing is in it, so five cards in a seven-track row sat at their minimum
   width with two empty tracks of dead space beside them — and the text wrapped
   itself tall to fit a column it never needed to be. auto-fit drops the empty
   tracks and the cards take the width back, which costs rows rather than
   buying them. */
/* `align-items:start` sized every card to its own text, so a row of five ran
   five different heights and the group read as a ragged pile rather than a set.
   Stretching them to the tallest in the row costs the short ones some empty
   space at the bottom and buys a straight line along both edges. */
.abils-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:10px;
  align-items:stretch}
.abil-group + .abil-group{margin-top:16px}
.abil-head{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap;margin:0 0 8px;
  font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}
.abil-count{padding:1px 7px;border:1px solid var(--line-2);border-radius:10px;
  font-size:11px;letter-spacing:.04em;color:var(--text-dim)}
.abil-sub{flex:1;font-size:11.5px;font-weight:500;letter-spacing:.02em;
  text-transform:none;color:var(--text-faint)}
.abil{
  display:flex;gap:12px;align-items:flex-start;text-align:left;padding:11px 12px;
  border:1px solid var(--line);border-radius:3px;cursor:pointer;
  background:linear-gradient(180deg,rgba(33,26,19,.8),rgba(15,12,9,.8));transition:.15s;
}
.abil:hover{border-color:color-mix(in srgb,var(--accent) 55%,transparent)}
.abil .abil-mark{
  width:calc(42px * .866);height:42px;flex:none;display:grid;place-items:center;
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background:linear-gradient(160deg,color-mix(in srgb,var(--accent) 30%,#453a30),
    color-mix(in srgb,var(--accent-deep) 55%,#1d1712));
}
.abil .abil-mark svg{width:22px;height:22px;stroke:#8d7f70;fill:none;stroke-width:1.5}
.abil .abil-txt{flex:1;min-width:0}
.abil b{display:block;font-size:14px;font-weight:700;letter-spacing:.04em;color:var(--text)}
.abil .abil-use{display:block;font-size:12px;font-weight:600;color:var(--blood-2);margin-top:2px}
.abil .abil-eff{display:block;font-size:13px;color:var(--text-dim);margin-top:4px}
.abil .abil-foot{display:flex;align-items:center;gap:12px;margin-top:8px;flex-wrap:wrap}
.abil .pips{display:flex;gap:5px;align-items:center}
.abil .abil-next{display:flex;align-items:center;gap:8px;font-size:13px;flex-wrap:wrap}
.abil .abil-next.done{color:var(--text-faint);font-size:12px}
.abil.avail{border-color:color-mix(in srgb,var(--accent) 38%,#4a3d31)}
.abil.avail .abil-mark{background:linear-gradient(160deg,
  color-mix(in srgb,var(--accent-2) 70%,#cfc4b4),color-mix(in srgb,var(--accent-dim) 70%,#403528))}
.abil.avail .abil-mark svg{stroke:var(--text)}
.abil.taken .abil-mark{background:linear-gradient(160deg,var(--accent),var(--accent-dim))}
.abil.taken .abil-mark svg{stroke:var(--accent-ink)}
.abil.maxed{border-color:color-mix(in srgb,var(--accent) 45%,transparent)}
.abil.selected{border-color:var(--accent);
  background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 15%,transparent),rgba(17,13,9,.85))}
.abil.syn-green{border-left:3px solid var(--tier-green)}
.abil.syn-yellow{border-left:3px solid var(--tier-yellow)}

.hint{flex:none;margin:8px 0 0;font-size:12px;color:var(--text-faint);text-align:center}
/* The synergy key lives down here rather than in the side panel: it is read
   once and then never again, and the panel's space belongs to the perk. */
.hint-key{display:inline-flex;align-items:center;gap:4px;flex-wrap:wrap;
  margin-left:10px;padding-left:10px;border-left:1px solid var(--line)}
.hint-key b{font-weight:600;color:var(--text-dim)}
.hint-key .tier-dot{margin-right:2px}

/* --------------------------------------------------------- side panel */
/* ONE scroller for the whole column, and it is this element.

   There were three, nested: the panel scrolled inside the column, the build
   overview scrolled inside the panel's sibling, and each conditional-effects
   list scrolled inside that. Three bars stacked a few pixels apart, each owning
   a different slice of the same text, and reaching a line meant guessing which
   one it lived in. Every one of them was added to stop some box running off the
   bottom of the column — the right fix for which is to let the column itself
   scroll, once, and let everything inside size to its content.

   So: nothing below here sets overflow or a max-height. Boxes are as tall as
   what they contain, the column is as tall as its boxes, and one bar moves the
   lot. */
/* Two panes, and exactly one of them is open at a time. The open one takes the
   whole column and scrolls inside itself; the closed one is its header bar and
   nothing else. So there is still only ever one scrollbar in this column — the
   three nested ones this replaced were the real complaint, not the scrolling.

   The row template is the whole mechanism. `1fr auto` gives the column to the
   perk panel and leaves the overview its summary; `auto 1fr` swaps them. Both
   rows are `minmax(0,…)` so the open one may actually shrink to the column and
   scroll, rather than growing to its content and pushing the other off. */
.side{
  display:grid;grid-template-rows:minmax(0,1fr) auto;
  min-height:0;gap:10px;
}
.side.ov-open{grid-template-rows:auto minmax(0,1fr)}
.side>.drawer{margin-top:0}

.panel{
  min-height:0;overflow-y:auto;overscroll-behavior:contain;
  border:1px solid var(--line);border-radius:4px;
  background:linear-gradient(180deg,rgba(30,23,17,.96),rgba(14,11,8,.96));
  box-shadow:var(--shadow);
}
#ovdrawer{min-height:0;overflow-y:auto;overscroll-behavior:contain}
/* The pane scrolls, so its own header has to stay put — a scrolled pane whose
   title has gone is a pane you cannot tell apart from the other one. */
.panel-hero,#ovdrawer>summary{position:sticky;top:0;z-index:2}
#ovdrawer>summary{background:linear-gradient(180deg,#1b1510,#181209)}

/* Folded, the perk panel is its hero row, and that row is the way back. */
.side.ov-open>.panel{overflow:visible}
.side.ov-open>.panel>:not(.panel-hero){display:none}
.side.ov-open .panel-hero{cursor:pointer;border-bottom:0}
.side.ov-open .panel-hero:hover{filter:brightness(1.18)}
.side.ov-open .panel-hero::after{
  content:"";width:9px;height:9px;flex:none;margin-left:2px;
  border-right:2px solid var(--accent);border-bottom:2px solid var(--accent);
  transform:rotate(-45deg);
}
.panel-empty{padding:34px 22px;color:var(--text-faint);font-size:14px;text-align:center}
.panel-empty b{display:block;font-size:13px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
  color:var(--text-dim);margin-bottom:8px}
/* A 104px block to hold a 52px icon, with the tree name and the level pinned
   into two of the corners it left over. It is one row now: mark, tree, level,
   all on the icon's own line, and the 44px it was wasting goes to the perk. */
/* The hero band is the loudest statement of which tree you are reading, so it
   is the tree's colour over the tree's own near-black rather than over brown. */
.panel-hero{
  display:flex;align-items:center;gap:12px;padding:10px 14px;
  border-bottom:1px solid color-mix(in srgb,var(--accent) 30%,var(--line));
  background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 34%,transparent),rgba(0,0,0,0)),
             linear-gradient(150deg,color-mix(in srgb,var(--accent-deep) 80%,#241a12),#120d0a);
}
.panel-hero .hero-mark{width:40px;height:40px;flex:none;display:grid;place-items:center}
.panel-hero svg{width:40px;height:40px;stroke:var(--accent-2);fill:none;stroke-width:1.4}
.panel-hero .hero-tree{
  flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:9px;flex-wrap:wrap;
  font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--text-dim);
}
.panel-hero .hero-lv{flex:none;font-size:13px;font-weight:600;color:var(--accent-2);white-space:nowrap}
/* The exception badge: this perk does not keep its tree's hours. */
.hero-when{
  display:inline-flex;align-items:center;gap:5px;padding:2px 8px;
  border:1px solid color-mix(in srgb,var(--gold) 40%,transparent);border-radius:10px;
  font-size:10.5px;font-weight:700;letter-spacing:.1em;color:var(--gold);
}
.hero-when i{width:11px;height:11px;margin-right:0}
.panel-body{padding:14px 18px 0}
.panel h3{margin:0 0 8px;font-size:22px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--accent-2)}
.panel .desc{margin:0 0 14px;font-size:16px;color:var(--text)}
/* The game prints this under the effect in italics — a passive note, or that
   the ability does not take up a common slot. */
.panel .desc-note{margin:-8px 0 14px;font-size:14px;font-style:italic;color:var(--text-faint)}
.panel .req{margin:0 0 14px;font-size:14px;color:var(--text-faint)}
.panel .req b{color:var(--text-dim);font-weight:500}
/* The synergy tiers. Only the board dots and the one-line key under the board
   use these now; the side panel carries no synergy list at all. */
.tier-dot{display:inline-block;width:8px;height:8px;margin-right:5px;
  transform:rotate(45deg);vertical-align:middle}
.tier-dot.tier-green{background:var(--tier-green)}
.tier-dot.tier-yellow{background:var(--tier-yellow)}
.tier-dot.tier-red{background:var(--tier-red)}

.levels{list-style:none;margin:0;padding:0;border-top:1px solid var(--line)}
/* A level is a three-column row: marker, what it does, what it costs. The
   price used to be `flex:1 1 100%`, which forced it onto a line of its own and
   right-aligned it — so it sat hard against the divider under its row and read
   as the heading of the next level instead of the price of this one, with the
   empty half-line above it for good measure. A column cannot be misread that
   way, and it costs no extra height. */
.levels li{display:grid;grid-template-columns:13px minmax(0,1fr) max-content;
  column-gap:11px;align-items:start;padding:9px 0;
  border-bottom:1px solid rgba(58,48,42,.6);font-size:15px}
.levels .lp{width:13px;height:13px;flex:none;margin-top:4px;transform:rotate(45deg);
  background:#2b241e;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--accent) 34%,#4e4238)}
.levels li.on .lp{background:var(--accent);box-shadow:0 0 8px var(--accent-glow)}
.levels li.next .lp{box-shadow:inset 0 0 0 1px var(--accent-2)}
.levels li.blocked{opacity:.55}
.levels .lv-text{min-width:0;color:var(--text)}
.levels li.on .lv-text{color:var(--text-dim)}
.levels .lv-meta{display:flex;flex-direction:column;align-items:flex-end;gap:4px;
  padding-top:2px;font-size:13px;text-align:right}
/* The price stays on one line — a `2 PTS` that wraps away from its `1 SEG` reads
   as two prices. The gate, when there is one, takes the line below it. */
.lv-costs{display:flex;gap:9px;align-items:center;white-space:nowrap}

/* A gate is a condition, not a price, and it says which condition in words next
   to its own mark — the book, the shrine, the corruption sigil. It used to be
   spelled twice on the same row, once as "MANUAL REQUIRED" and again as a bare
   icon in among the costs, and the icon was the one nobody could place. */
.gate-tag{
  display:inline-flex;align-items:center;gap:5px;white-space:nowrap;
  font-size:11.5px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  color:var(--blood-2);
}
.gate-tag.ok{color:var(--text-faint)}
.gate-tag i{width:12px;height:12px;flex:none;background:currentColor;font-style:normal;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}
.gate-tag.g-manual i{-webkit-mask-image:var(--m-bk);mask-image:var(--m-bk)}
.gate-tag.g-shrine i{-webkit-mask-image:var(--m-shr);mask-image:var(--m-shr)}
.gate-tag.g-cor i{-webkit-mask-image:var(--m-cor);mask-image:var(--m-cor)}
.gate-tag.g-phial i{-webkit-mask-image:var(--m-hp);mask-image:var(--m-hp)}
.gate-tag.g-quest i{-webkit-mask-image:var(--m-lock);mask-image:var(--m-lock)}
/* Given by the story, not bought. Marked in green because the useful fact is
   that it costs nothing, and priced rows are read in red and gold. */
.free-tag{font-size:12px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--green)}
/* Muted: a damage figure converted to the registry's scale rather than read at
   it — right size, computed rather than transcribed. Red: a row still carrying
   the Game8 table rather than the game's own panel. */
.src-tag{
  padding:2px 6px;border:1px solid color-mix(in srgb,var(--text-faint) 45%,transparent);
  border-radius:9px;font-size:10px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--text-faint);white-space:nowrap;
}
.src-tag.bad{
  color:var(--tier-red);
  border-color:color-mix(in srgb,var(--tier-red) 45%,transparent);
}

/* The running total an over-time row adds up to, set beside the per-second
   figure it is derived from rather than replacing it. */
.lv-total{
  display:block;margin-top:3px;font-size:12px;font-weight:600;letter-spacing:.02em;
  color:var(--accent-2);
}

/* ---------------------------------------------------- theorycraft panel */

.tc-p{margin:0 0 14px;line-height:1.6;color:var(--text-dim);max-width:64ch}
.tc-p b{color:var(--text)}
.tc-p.soft{color:var(--text-faint);font-size:13.5px}
.tc-p code{
  padding:1px 5px;border-radius:3px;background:rgba(231,179,86,.1);
  color:var(--gold-2);font-size:12.5px;
}
.tc-chart{display:block;width:100%;height:auto;margin:6px 0 18px;overflow:visible}
.tc-band{fill:rgba(231,179,86,.07)}
.tc-band-lbl{fill:var(--text-faint);font-size:9px;letter-spacing:.14em}
.tc-grid{stroke:var(--line);stroke-width:1}
.tc-grid.on{stroke:var(--line-2)}
.tc-tick{fill:var(--text-faint);font-size:10.5px}
.tc-line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.tc-line.dash{stroke-dasharray:4 4;opacity:.55}
.tc-end{font-size:11.5px;font-weight:700}
/* One hue per tree, and the same three hues the board is painted in: the chart
   was red / gold / green, which agreed with nothing on the page. Each series is
   classed by its tree's key, so the line for Witchcraft is purple wherever the
   tree happens to sit in the data. */
.tc-wc{--c:#a878e0} .tc-sm{--c:#a9b7c4} .tc-vp{--c:#d94a45}
.tc-wc.tc-line,.tc-sm.tc-line,.tc-vp.tc-line{stroke:var(--c)}
.tc-wc.tc-key,.tc-sm.tc-key,.tc-vp.tc-key{color:var(--c)}
.tc-end.tc-wc,.tc-end.tc-sm,.tc-end.tc-vp{fill:var(--c);stroke:none}
.tc-dot{stroke:var(--panel-bg,#141210);stroke-width:1.5}
.tc-dot.tc-wc,.tc-dot.tc-sm,.tc-dot.tc-vp{fill:var(--c)}
.tc-key{
  display:inline-block;width:11px;height:3px;border-radius:2px;margin-right:8px;
  background:currentColor;vertical-align:middle;
}
.tc-tab{border-collapse:collapse;width:100%;margin:0 0 18px;font-size:13px}
.tc-tab th,.tc-tab td{
  padding:7px 10px;text-align:right;border-bottom:1px solid var(--line);
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
.tc-tab th{
  font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--text-faint);
  font-weight:600;
}
.tc-tab th:first-child,.tc-tab td:first-child{text-align:left;font-variant-numeric:normal}
.tc-tab tbody tr:last-child td{border-bottom:0}
.tc-tab .soft{color:var(--text-faint)}

.story-tag{
  margin-left:8px;padding:2px 7px;border:1px solid color-mix(in srgb,var(--green) 45%,transparent);
  border-radius:10px;font-size:10.5px;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--green);vertical-align:middle;
}
.panel h3 .story-tag{font-size:11px}
/* The numbers carry a word wherever there is room for one. The two marks are
   drawn, not the game's — the top bar is the only place they were ever spelled
   out, and a reader should not have to go back up there to price a level. */
.cost{display:inline-flex;align-items:center;gap:4px;white-space:nowrap;font-weight:600}
.cost u{font-style:normal;text-decoration:none;font-size:11px;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;opacity:.72}
.cost i{width:13px;height:13px;background:currentColor;font-style:normal;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}
.cost.sp{color:var(--blood-2)}
.cost.sp i{-webkit-mask-image:var(--m-sp);mask-image:var(--m-sp)}
.cost.ts{color:var(--gold)}
.cost.ts i{-webkit-mask-image:var(--m-ts);mask-image:var(--m-ts)}

/* The banner over a perk name, and the cooldown line on an ability. Both are
   read straight off the in-game panel, so both carry the game's own marks. */
.when-mark,.cd-mark{width:13px;height:13px;display:inline-block;vertical-align:-2px;
  margin-right:6px;background:currentColor;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain}
.when-mark.day{-webkit-mask-image:var(--m-sun);mask-image:var(--m-sun)}
.when-mark.night{-webkit-mask-image:var(--m-moon);mask-image:var(--m-moon)}
.when-mark.any{-webkit-mask-image:var(--m-any);mask-image:var(--m-any)}
.cd-mark{-webkit-mask-image:var(--m-cd);mask-image:var(--m-cd)}
.panel-cd{font-size:13px;font-weight:600;letter-spacing:.06em;color:var(--text-dim);margin:0 0 10px}

.panel-foot{
  position:sticky;bottom:0;z-index:2;padding:18px 18px 16px;display:flex;gap:10px;
  background:linear-gradient(180deg,rgba(20,15,11,0),rgba(17,13,9,.99) 60%,rgba(17,13,9,1));
}
.panel-foot .btn{flex:1;padding:12px 10px;text-align:center;font-size:13px}
/* The one button that belongs to the tree rather than to the app. */
.btn-learn{
  background:linear-gradient(180deg,var(--accent),var(--accent-dim));
  border-color:var(--accent-2);color:var(--accent-ink);font-weight:700;
}
.btn-learn:hover{background:linear-gradient(180deg,var(--accent-2),var(--accent))}
.panel-note{padding:0 18px 16px;margin:0;font-size:13px;color:var(--text-faint)}

/* ----------------------------------------------------- build overview */
.ovbody{padding:0 14px 14px;font-size:14px}
.ov-group{margin-top:12px}
.ov-group h4{margin:0 0 6px;font-size:12px;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--text-faint)}
.ov-row{display:flex;justify-content:space-between;gap:12px;padding:4px 0;
  border-bottom:1px solid rgba(58,48,42,.45)}
.ov-row span{color:var(--text-dim)}
.ov-row b{color:var(--accent-2);font-weight:600;white-space:nowrap}
.ov-list{margin:0;padding:0;list-style:none}
.ov-list li{padding:5px 0;border-bottom:1px solid rgba(58,48,42,.45);color:var(--text-dim);font-size:13px}
.ov-list li b{display:block;color:var(--text);font-weight:600;font-size:13px}
.ov-empty{color:var(--text-faint);font-size:13px;padding:8px 0}

/* The three long lists. They sit under the totals, which are the reason to open
   the overview at all, so they fold away behind their own count and each opens
   into a scroller of its own rather than laying forty rows out in the column. */
.ov-sub{margin-top:10px;border-top:1px solid var(--line)}
/* The overview body is itself a scroller, so an open section's rows would carry
   its own heading off the top and leave you reading a list with no idea which
   one it is. The heading sticks to the top of that scroller instead. */
.ov-sub>summary{
  position:sticky;top:0;z-index:1;
  cursor:pointer;list-style:none;display:flex;align-items:center;gap:8px;
  padding:8px 0;font-size:12px;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--text-faint);transition:.15s;
  background:linear-gradient(180deg,var(--panel) 72%,rgba(23,18,14,.92));
}
.ov-sub>summary::-webkit-details-marker{display:none}
.ov-sub>summary::before{
  content:"";width:0;height:0;flex:none;
  border:4px solid transparent;border-left-color:currentColor;
  transition:transform .15s;
}
.ov-sub[open]>summary::before{transform:rotate(90deg) translateX(1px)}
.ov-sub>summary:hover{color:var(--text-dim)}
.ov-sub[open]>summary{color:var(--accent)}
.ov-count{
  margin-left:auto;padding:1px 7px;border:1px solid var(--line-2);border-radius:9px;
  font-size:11px;font-weight:700;letter-spacing:.04em;color:var(--text-dim);
}
/* No bound of its own: this was the innermost of the three nested scrollers. A
   long list makes the column longer, which is what the column's scrollbar is
   for — and the section is a <details>, so anyone who does not want the
   twenty-two manuals in their way can fold them. */
.ov-sub>.ov-list{padding:2px 6px 6px 0}
.ov-sub>.ov-list li:last-child{border-bottom:0}

/* -------------------------------------------------------------- toast */
.toast{
  position:fixed;left:50%;bottom:24px;transform:translate(-50%,14px);
  padding:11px 18px;border:1px solid var(--line-2);border-radius:3px;
  background:rgba(22,16,12,.97);color:var(--text);font-size:14px;
  opacity:0;pointer-events:none;transition:.2s;z-index:9;box-shadow:var(--shadow);
}
.toast.show{opacity:1;transform:translate(-50%,0)}
.toast.warn{border-color:#8a3034;color:#f0bcbc}

/* --------------------------------------------------------- responsive */
@media (max-width:1360px){ :root{--side:clamp(320px,26vw,380px);--node:80px;--icon:40px;--colmin:84px;--colmax:132px} }

/* The tree's size by viewport height, and nothing else. This used to be two
   coarse steps with JavaScript squeezing the tree between them to fit whatever
   height its siblings left over — which measured the drawers empty on the first
   paint and full on every one after, so the board resized under the cursor on
   the first click, and bottomed out on a 52px node while the board scrolled
   anyway. A media query cannot be changed by a click, so the ladder is the whole
   mechanism now; it steps in sixes rather than tens so no single resize jumps.

   Every `--row` here stays at or above `--node` + 58px, the room a two-line
   perk name needs under its node, so labels never meet. */
@media (max-height:1120px){ :root{--node:86px;--icon:43px;--row:160px} }
@media (max-height:1000px){ :root{--node:80px;--icon:40px;--row:145px} }
@media (max-height:900px){  :root{--node:74px;--icon:37px;--row:134px} }
@media (max-height:820px){  :root{--node:68px;--icon:34px;--row:126px} }
@media (max-height:720px){
  :root{--node:62px;--icon:31px;--row:120px}
  /* The hint is dropped to buy board height, which is only worth buying where
     the board is sized to the window. Below 1000px the page scrolls instead, so
     there is no height to buy and the line — which is where it says what a tap
     does, and where the attribution lives — stays. */
  .hint{display:none}
  .drawer>summary{padding:7px 14px}
  .drawer{margin-top:7px}
}

@media (max-width:1000px){
  body{overflow:auto}
  .hint{display:block}
  /* The board stops being a scroller here, so its sticky tab strip sticks to
     the viewport instead — over the drawers, through a background built to be
     half-transparent over a tree. Bounded by the board either way, so it still
     leaves the panel alone; it just has to be opaque. */
  .board>.tabs{background:var(--ink);box-shadow:0 7px 11px -7px rgba(0,0,0,.95)}
  /* The page scrolls here, so nothing inside it may be a scroller of its own or
     claim a slice of viewport height — each box sizes to its content and the
     column simply gets longer. Without this the stage keeps the height it had
     as a two-column layout, and the tree spills straight over the panel. */
  :root{--gutter:14px}
  .stage{grid-template-columns:minmax(0,1fr);padding-bottom:20px;flex:none;min-height:auto}
  .board{overflow:visible;flex:none;min-height:auto}
  .boardscroll{overflow-x:auto;overflow-y:hidden}
  /* The page scrolls here, so neither pane is a scrollport and neither claims a
     share of the column — but the accordion still works, because folding the
     perk panel to its hero is just as useful on a phone. */
  .side,.side.ov-open{min-height:auto;grid-template-rows:auto auto}
  .panel,#ovdrawer{overflow:visible}
}

@media (max-width:640px){
  /* A wider column floor than the screen can hold, on purpose: the board scrolls
     sideways and the labels stay legible, rather than every name being squeezed
     into its neighbour to make the whole tree fit a phone. */
  :root{--node:70px;--icon:36px;--colmin:118px;--row:150px}
  :root{--gutter:10px}
  /* The totals keep the first row with the brand, still labelled — a phone has
     no room to spare, which is exactly where a bare number next to another bare
     number stops being readable. They wrap within that row if they must. */
  .meters{column-gap:13px;flex-wrap:wrap;row-gap:2px}
  .tabs{padding:0 0 6px}
  .tab{padding:6px 10px;font-size:12px;letter-spacing:.06em}
  .stage{padding-bottom:18px}
  .node .name{font-size:12px}
}

/* ------------------------------------------------------- legal notice
   The attribution has to be on the page — a visitor to the published site
   never reads the README — but the board is sized to the window, so anything
   in the layout below it comes straight out of the board's height. It rides
   the hint line instead and opens over the page, costing no height at all. */
/* Its own centred row under the hint: at most widths the hint line is already
   full, so it wraps anyway — better that it look deliberate than orphaned. */
.legal{display:block;position:relative;width:max-content;margin:5px auto 0}
.legal>summary{
  cursor:pointer;color:var(--text-faint);text-decoration:underline dotted;
  text-underline-offset:3px;list-style:none
}
.legal>summary::-webkit-details-marker{display:none}
.legal>summary:hover{color:var(--gold)}
.legal[open]>summary{color:var(--gold)}
.legal-body{
  position:absolute;bottom:calc(100% + 10px);left:50%;transform:translateX(-50%);
  width:min(460px,86vw);z-index:40;text-align:left;
  background:var(--panel-2);border:1px solid var(--line-2);border-radius:10px;
  padding:13px 15px;box-shadow:var(--shadow);
  font-size:12px;line-height:1.55;color:var(--text-faint)
}
.legal-body p{margin:0 0 7px}
.legal-body p:last-child{margin-bottom:0}
.legal-body strong{
  color:var(--text-dim);font-weight:600;letter-spacing:.09em;
  text-transform:uppercase;font-size:11px
}
.legal-body em{font-style:italic}

/* -------------------------------------------------- saved & shared builds */

.btn-sm{padding:5px 10px;font-size:11px;letter-spacing:.08em}
.btn-ghost.danger:hover{color:#f0bcbc;background:rgba(164,38,44,.18)}

/* The bar under the top bar, naming the community build on the board. It is
   hidden until there is one, so it costs no height on an ordinary visit. */
.loadedbar{
  flex:none;display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding-block:8px;border-bottom:1px solid var(--line);
  background:linear-gradient(180deg,rgba(40,30,18,.9),rgba(18,14,10,.9));
}
.loadedbar[hidden]{display:none}
.loaded-name{font-size:14px;font-weight:600;color:var(--gold-2)}
.loaded-by{font-size:13px;color:var(--text-dim)}
.loaded-tag{
  font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--text-faint);margin-right:auto
}
.loaded-x{
  border:0;background:none;cursor:pointer;color:var(--text-faint);
  font-size:20px;line-height:1;padding:0 4px
}
.loaded-x:hover{color:var(--text)}

/* The vote control. `off` is the same shape with the button taken away: an
   edited build still shows what the original scored, because hiding the count
   would read as the build having lost its votes. */
.votebtn{
  display:inline-flex;align-items:center;gap:7px;flex:none;
  padding:5px 11px;border:1px solid var(--line-2);border-radius:3px;
  background:linear-gradient(180deg,rgba(231,179,86,.16),rgba(231,179,86,.04));
  color:var(--gold-2);font:inherit;font-size:11px;font-weight:600;
  letter-spacing:.09em;text-transform:uppercase;cursor:pointer;transition:.15s
}
.votebtn:hover:not(:disabled):not(.off):not(.voted){border-color:var(--gold-dim);
  background:linear-gradient(180deg,rgba(231,179,86,.3),rgba(231,179,86,.1))}
.votebtn b{font-size:15px;font-weight:700;line-height:1}
.votebtn:disabled,.votebtn.off{cursor:default;opacity:.55}
/* Voted is a state you can leave, not a dead end — so it keeps a full-strength
   colour and a hover of its own, rather than the dimming that used to say
   "disabled". */
.votebtn.voted{color:var(--green);border-color:rgba(143,192,110,.4);
  background:linear-gradient(180deg,rgba(143,192,110,.14),rgba(143,192,110,.03))}
.votebtn.voted:hover{border-color:rgba(143,192,110,.75);
  background:linear-gradient(180deg,rgba(143,192,110,.26),rgba(143,192,110,.08))}
.vote-caret{
  width:0;height:0;flex:none;
  border-left:5px solid transparent;border-right:5px solid transparent;
  border-bottom:7px solid currentColor
}
/* In a list the vote sits in its own column to the left of the build. */
.votebtn.col{flex-direction:column;gap:2px;padding:7px 10px;min-width:52px;align-self:flex-start}

/* ------------------------------------------------------------ the dialog */

.modal{
  width:min(720px,calc(100vw - 32px));max-height:min(84vh,820px);
  padding:0;border:1px solid var(--line-2);border-radius:8px;
  background:var(--panel-2);color:var(--text);box-shadow:var(--shadow);
  overflow:hidden
}
.modal::backdrop{background:rgba(4,3,2,.72)}
.modal-head{
  display:flex;align-items:center;gap:12px;padding:14px 18px;
  border-bottom:1px solid var(--line);background:rgba(0,0,0,.22)
}
.modal-title{
  margin:0;flex:1;font-size:13px;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:var(--gold-2)
}
.modal-x{
  border:0;background:none;cursor:pointer;color:var(--text-faint);
  font-size:24px;line-height:1;padding:0 4px
}
.modal-x:hover{color:var(--text)}
.modal-body{padding:16px 18px 20px;overflow:auto;max-height:calc(84vh - 52px)}
.modal-rule{border:0;border-top:1px solid var(--line);margin:18px 0 14px}
.modal-acts{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px}
.modal-fine{margin:12px 0 0;font-size:12px;line-height:1.6;color:var(--text-faint)}
.modal-fine b{color:var(--text-dim);font-weight:600}

.fld{display:block;margin-top:14px}
.fld>span{
  display:block;margin-bottom:5px;font-size:11px;font-weight:700;
  letter-spacing:.11em;text-transform:uppercase;color:var(--text-dim)
}
.fld>span i{font-style:normal;font-weight:400;letter-spacing:.06em;
  text-transform:none;color:var(--text-faint);margin-left:6px}
.fld input,.fld textarea{
  width:100%;box-sizing:border-box;padding:9px 11px;
  border:1px solid var(--line);border-radius:3px;background:rgba(0,0,0,.3);
  color:var(--text);font:inherit;font-size:14px;line-height:1.5;resize:vertical
}
.fld textarea{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
.fld input:focus,.fld textarea:focus{outline:0;border-color:var(--gold-dim)}
.fld textarea[readonly]{color:var(--text-dim)}

.sv-stats{padding-bottom:4px}
.modal-sub{
  margin:0 0 2px;font-size:11px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--gold-2)
}
.sv-turnstile{margin-top:14px}
.sv-turnstile[hidden]{display:none}
.sv-busy{align-self:center;font-size:12px;color:var(--text-faint);letter-spacing:.06em}
.sv-busy[hidden]{display:none}
.mempty code{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;
  color:var(--text-dim);background:rgba(0,0,0,.35);padding:1px 5px;border-radius:2px
}

/* ------------------------------------------------------------- the lists */

.mtabs{display:flex;gap:8px;border-bottom:1px solid var(--line);margin-bottom:6px}
.mtab{
  border:0;border-bottom:2px solid transparent;background:none;cursor:pointer;
  padding:7px 4px;margin-bottom:-1px;color:var(--text-faint);font:inherit;
  font-size:12px;font-weight:700;letter-spacing:.11em;text-transform:uppercase
}
.mtab:hover{color:var(--text-dim)}
.mtab[aria-selected=true]{color:var(--gold-2);border-bottom-color:var(--accent)}
.mtab b{font-weight:700;color:var(--text-faint);margin-left:3px}
.mtab[aria-selected=true] b{color:var(--gold-dim)}

.msort{display:flex;gap:14px;padding:10px 2px 4px}
.msort-b{
  border:0;background:none;cursor:pointer;padding:0;font:inherit;font-size:12px;
  color:var(--text-faint);letter-spacing:.06em
}
.msort-b:hover{color:var(--text-dim)}
.msort-b[aria-selected=true]{color:var(--gold-2);font-weight:600}

.mempty{margin:18px 2px;font-size:13px;line-height:1.65;color:var(--text-faint)}
.mempty b{color:var(--text-dim);font-weight:600}

.brow{
  display:flex;align-items:flex-start;gap:13px;
  padding:13px 2px;border-bottom:1px solid rgba(58,48,42,.45)
}
.brow:last-of-type{border-bottom:0}
.brow.current{background:linear-gradient(90deg,rgba(231,179,86,.09),transparent 70%)}
.brow-main{flex:1;min-width:0}
.brow-main h3{
  margin:0;display:flex;align-items:center;gap:9px;flex-wrap:wrap;
  font-size:14px;font-weight:600;color:var(--text)
}
.brow-now{
  font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  color:var(--gold-2);border:1px solid var(--gold-dim);border-radius:2px;padding:1px 5px
}
.brow-meta{margin:3px 0 0;font-size:12px;color:var(--text-faint)}
.brow-meta .sep{margin:0 5px}
.brow-yours{font-style:normal;color:var(--gold-dim)}
.brow-notes{margin:6px 0 0;font-size:13px;line-height:1.55;color:var(--text-dim)}
.brow-acts{display:flex;flex-direction:column;gap:6px;flex:none;align-items:stretch}
.brow-acts .btn{text-align:center;text-decoration:none;display:block}

/* The one-line cost readout, shared by both lists and the save dialog. */
.bstat{
  display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:7px;
  font-size:12px;color:var(--text-dim)
}
.bstat .sep{color:var(--text-faint)}
.bstat .dot{
  width:8px;height:8px;border-radius:50%;background:var(--accent);
  display:inline-block;margin-right:2px
}

.mpaste{margin-top:16px;padding-top:6px;border-top:1px solid var(--line)}
.mpaste-row{display:flex;gap:8px;align-items:center}
.mpaste-row input{flex:1}

@media (max-width:640px){
  .modal{width:calc(100vw - 16px);max-height:92vh}
  .modal-body{max-height:calc(92vh - 52px);padding:14px}
  .brow{flex-wrap:wrap}
  .brow-acts{flex-direction:row;width:100%}
  .brow-acts .btn{flex:1}
  .loadedbar{padding:8px 14px;gap:9px}
  .loaded-tag{width:100%;margin-right:0;order:9}
}

/* ------------------------------------------------------------ touch
   A finger is not a mouse pointer: it is about 9mm wide, it cannot hover, and
   the browser will happily turn a slow tap into a text selection or a 300ms
   wait. Everything here is scoped to coarse pointers, so a desktop window
   dragged narrow keeps the desktop sizing it has always had. */
@media (pointer:coarse){
  /* No grey flash box on tap, no accidental word selection when a tap on a node
     label is held a fraction too long, no synthetic-click delay. */
  .node,.abil,.ult,.tab,.btn,.drawer>summary{
    -webkit-tap-highlight-color:transparent;touch-action:manipulation;
  }
  .node,.abil,.ult{-webkit-user-select:none;user-select:none}

  /* A 6px track with a 14px thumb is a mouse-sized control. The track keeps its
     look and the input grows a 28px tall hit area around it. */
  .ctl input[type=range]{height:28px}
  .ctl input[type=range]::-webkit-slider-thumb{width:20px;height:20px;margin-top:-8px}
  .ctl input[type=range]::-moz-range-thumb{width:19px;height:19px}
  .ctl-check input{width:19px;height:19px}
  .ctl-check{padding:2px 0}

  /* The two buttons that are the whole interaction on a phone. */
  .panel-foot{padding:14px 14px calc(14px + env(safe-area-inset-bottom,0px))}
  .panel-foot .btn{padding:15px 10px;font-size:14px}

  .tab{padding:9px 12px}
  .drawer>summary{padding:12px 14px}
}

@media (prefers-reduced-motion:reduce){ *{transition:none!important} }

/* --------------------------------------------- the top bar on a phone
   194px of header, then 54px of tabs, and the board started a third of the way
   down an 844px screen. The totals earn their place — they are the only figures
   on the page that change on every click — and so does the tab strip. The two
   sliders, the two switches and the five buttons do not: nothing in either
   group changes while you are reading a tree, and between them they were 148px
   of that header. They fold behind one button, which is the whole saving.

   Collapsed the bar is one row of totals, about 46px. The board starts where
   the tabs used to and gets the difference. */
.bar-more{display:none}
.bar-more-ico{width:17px;height:17px;flex:none}
.bar-more[aria-expanded=true]{
  color:var(--gold-2);border-color:var(--gold-dim);
  background:linear-gradient(180deg,rgba(231,179,86,.24),rgba(231,179,86,.08));
}

@media (max-width:760px){
  .bar-more{display:inline-flex;align-items:center;gap:7px;order:3;margin-left:auto;padding:6px 10px}
  /* The hexagon goes. The wordmark went at 1300, so what is left of the brand
     here is a decorative shape, and the row it is sitting in is the one row on
     a phone that has to hold the totals and this button as well. */
  .brand{display:none}
  /* The three groups keep their own `display:grid` when they come back; only
     the folding is written here, so nothing about how they lay out internally
     is duplicated or has to be kept in step with the desktop rules. */
  .topbar>.bargroup{display:none}
  body.bar-open .topbar>.bargroup{display:grid}
  /* Open, the bar is three rows: the totals it always had, the two settings
     groups sharing a row, and the buttons across the bottom on a grid of equal
     columns rather than one sized to the longest word. */
  body.bar-open .sliders{order:4;flex:1 1 210px}
  body.bar-open .checks{order:5;flex:0 1 auto;margin-left:auto}
  body.bar-open .acts{order:6;flex:1 0 100%;grid-template-columns:repeat(3,1fr);margin-left:0}
  body.bar-open .acts .btn{text-align:center}
  /* Five buttons in three columns leaves the second row a cell short. Copy link
     takes it rather than Reset: a destructive button twice the width of the one
     beside it is a button you press by accident. */
  body.bar-open .acts #share{grid-column:span 2}
}
/* The word beside the glyph is what pushes the row over at 430, and the glyph
   is the half that survives: it says "settings" on its own in a way the word
   cannot say in 40px. */
@media (max-width:430px){
  .bar-more-lbl{display:none}
  .bar-more{padding:6px 9px}
  .meters{column-gap:10px}
  .meter{gap:6px}
}
/* Last stop. The three icons are distinct and each total carries a `title`,
   so at this width the words go rather than the row wrapping. */
@media (max-width:365px){
  .meter-lbl{display:none}
  /* Too narrow for the sliders and the switches to share a row, so they stack —
     and stack left, rather than being pushed to the right edge by the margin
     that packs them against the buttons when they do fit beside each other. */
  body.bar-open .checks{margin-left:0}
}

/* ==================================================== the narrow layout
   Below 1000px the planner is one column: the tree, then everything under it.
   Two things about that column are not just a desktop squeezed — the tab strip
   and the perk panel — and both live here, after every earlier rule they have
   to win against. */

/* ----------------------------------------------------- tabs on a phone
   Three tabs, one screen, always. At 667px the strip was already sideways-
   scrolling with Vampirism half off the edge, which makes the third tree
   something you have to discover rather than something you can see.

   The hexagon goes: it is the one part of a tab that says nothing the word
   beside it does not. The hours drop under the name rather than out of the
   tab — a tree that only works at night is a fact worth a line — and the
   points stay beside them, because what a tree has cost is the whole reason
   to glance at a tab you are not looking at. Two short lines are narrower
   than one long one, and the strip fits in about half the width it took. */
/* 1180, not a phone width, because the strip does not run out of room at a
   phone width — it runs out of room wherever the board column is under about
   790px, and the two-column layout hands the board less than that anywhere
   below 1180. Left at 760 the strip still clipped Vampirism across the whole
   1001-1180 band and again between 761 and 800, which is the same bug wearing
   a desktop's clothes. */
@media (max-width:1180px){
  .tabs{gap:5px}
  /* A two-column grid rather than a wrapping flex: `flex-basis:100%` inside a
     shrink-to-fit flex item asks the tab how wide it is in order to answer how
     wide it is, and the browser settles that by making the tab as wide as the
     strip. A grid row that spans both columns says the same thing without the
     circularity, and the tab stays as wide as its widest line. */
  .tab{
    display:grid;grid-template-columns:auto auto;justify-content:center;
    justify-items:center;align-content:center;gap:1px 7px;
    padding:6px 10px;font-size:12px;letter-spacing:.04em;
  }
  .tab .tab-mark{display:none}
  .tab .tab-name{grid-column:1 / -1;text-align:center}
  .tab .tab-when{font-size:10px;letter-spacing:.06em;gap:4px}
  .tab .tab-stat{font-size:11px;gap:4px}
  /* Only one child on the second row — a tree with no hours of its own, or one
     whose build has cost nothing yet — and it centres under the name rather
     than sitting in a column of a grid the other tab is using. */
  .tab .tab-name:only-child{grid-column:1 / -1}
}
/* The last two steps, for phones. Tracking on an uppercase word is a luxury,
   and SWORDMASTERY is twelve letters paying for it eleven times; below 360 —
   an SE, a fold's cover screen — the gutter goes too, because at that width
   the three tree names are worth more than the margin beside them. */
@media (max-width:430px){
  .tab{padding:5px 7px;font-size:11px;letter-spacing:0}
  .tabs{gap:4px}
}
@media (max-width:360px){
  :root{--gutter:7px}
  .tab{padding:4px 5px;font-size:10px;gap:1px 5px}
  .tab .tab-when{font-size:9.5px;letter-spacing:.04em}
  .tab .tab-stat{font-size:10px}
  .tabs{gap:3px}
}

/* ------------------------------------------------------ the perk sheet
   Selecting a perk used to scroll the panel into view, and on a phone the
   panel is below the whole board: you got the description with the tree
   scrolled off the top, having lost the map you were reading it against, and
   getting back cost a scroll in the other direction. The panel is the same
   element and the same markup — it is simply pinned to the bottom of the
   screen over the board instead, which is where a thumb already is.

   There is no scrim. Dimming the board would defeat the point of putting the
   panel over it, and a scrim would make reading a second perk cost two taps
   instead of one. A tap anywhere that is not the sheet and not a node closes
   it, and so do Escape, the cross in the hero, and a second tap on the perk
   already open. */
@media (max-width:1000px){
  body.sheet-open .side>.panel{
    position:fixed;left:0;right:0;bottom:0;z-index:60;
    max-height:min(62vh,520px);
    display:flex;flex-direction:column;overflow:hidden;
    border:1px solid var(--line-2);border-bottom:0;
    border-top-color:color-mix(in srgb,var(--accent) 45%,var(--line-2));
    border-radius:14px 14px 0 0;
    box-shadow:0 -18px 44px rgba(0,0,0,.72);
    /* Opaque, unlike the column version. The panel's 4% of translucency is a
       texture against the page's own background and a legibility bug against a
       tree of glowing nodes scrolling underneath it. */
    background:linear-gradient(180deg,#1e1711,#0e0b08);
    animation:sheet-rise .18s ease-out;
  }
  /* The grabber. It says "this came up and can go down again" in 38 pixels,
     which is less than any sentence could. */
  body.sheet-open .side>.panel::before{
    content:'';position:absolute;top:5px;left:50%;transform:translateX(-50%);
    width:38px;height:4px;border-radius:2px;z-index:3;pointer-events:none;
    background:rgba(255,255,255,.24);
  }
  /* The hero and the buttons are the sheet's two fixed ends; only the middle
     scrolls, so the Learn button is at the bottom of the screen whatever the
     perk's level list does. Sticky is what the panel used as a column — inside
     a flex column of fixed height it has nothing to stick to. */
  /* The hero was 75px of a 520px sheet — a fifth of it spent on the tree's name
     and the level count, above the levels that are the reason the sheet is
     open. It is a caption, not a banner: the 40px tree mark comes down to 26,
     the padding with it, and the grabber sits in the top of that padding rather
     than being paid for in a row of its own. About 46px, and the 29 it gives
     back go to the level list. */
  body.sheet-open .side>.panel>.panel-hero{
    position:static;flex:none;border-radius:13px 13px 0 0;
    padding:12px 12px 9px;gap:10px;
  }
  body.sheet-open .panel-hero .hero-mark,
  body.sheet-open .panel-hero svg,
  body.sheet-open .panel-hero .mark{width:26px;height:26px}
  body.sheet-open .panel-hero .hero-tree{font-size:11px;letter-spacing:.1em;gap:7px}
  body.sheet-open .panel-hero .hero-lv{font-size:12px}
  body.sheet-open .panel-hero .hero-when{padding:1px 6px;font-size:10px}
  body.sheet-open .side>.panel>.panel-body{
    display:block;flex:1 1 auto;min-height:0;
    overflow-y:auto;overscroll-behavior:contain;
  }
  body.sheet-open .side>.panel>.panel-foot{
    display:flex;position:static;flex:none;padding:12px 14px;
    /* Learn on the right, where a thumb falls. Refund is the destructive one
       of the pair and it keeps the corner that is harder to hit by accident. */
    flex-direction:row-reverse;
  }
  body.sheet-open .side>.panel>.panel-note{display:block;flex:none;padding:0 14px 12px}
  body.sheet-open .side>.panel>.panel-empty{display:block;flex:none}
  /* Whichever of the two ends up last carries the phone's home indicator. */
  body.sheet-open .side>.panel>:last-child{
    padding-bottom:calc(12px + env(safe-area-inset-bottom,0px));
  }
  /* The overview drawer folds the panel to its hero in the column layout. As a
     sheet the panel is not sharing a column with anything, so it unfolds. */
  body.sheet-open .side.ov-open>.panel{overflow:hidden}

  /* Quick picks is a mouse's mode — hover to read, click to buy, right-click to
     refund — and none of those three exist here. The switch is hidden by the
     stylesheet as well as by the script, so a desktop window dragged narrow
     loses it at the same width the layout changes. */
  #pickmode{display:none!important}
}
@keyframes sheet-rise{from{transform:translateY(101%)}to{transform:translateY(0)}}
@media (prefers-reduced-motion:reduce){ .side>.panel{animation:none!important} }

/* The cross in the hero. Nothing but a sheet has anything to close, so it is
   not rendered as visible anywhere else. */
.hero-x{display:none}
body.sheet-open .hero-x{
  display:grid;place-items:center;flex:none;
  width:32px;height:32px;margin:-7px -7px -7px 0;
  border:0;border-radius:9px;background:none;cursor:pointer;
  color:var(--text-dim);font-size:22px;line-height:1;transition:.15s;
}
body.sheet-open .hero-x:hover{color:var(--accent-2);background:rgba(255,255,255,.07)}
