/* ==========================================================================================
   MERGE MC — THE SHARED UI TOOLKIT.  ** DRAFT / DEMO CUT, rev 3, 2026-09-01 **

   This is the file docs/WEB_UI_TOOLKIT.md proposes as web/ui/ui.css. It lives under
   web/ui-demo/ so that NOTHING ON THE LIVE SITE LINKS IT. The wiki, /worth, the store and the
   home page are untouched; this is a thing to look at, not a thing that ships.

   REV 3, at Kelp's direction: THE WHOLE THING IS BIGGER, and it is bigger from one place.
   Rev 2 had twenty-three hardcoded font sizes and the smallest was .36rem - under six pixels.
   That is not a toolkit, it is twenty-three separate decisions, and it is why the text was not
   legible. There is now a TYPE SCALE, every size is drawn from it, and the whole UI scales from
   two numbers at the top: --ui-root (text) and --ui-scale (the chest furniture).

   Rev 2 added the chest slot and the animated glint. Rev 1 was the extraction from wiki.css.
   ========================================================================================== */

/* ---------------------------------------------------------------------------- THE SCALE
   Press Start 2P is a 5x7 pixel face. It has NO hinting and no small-size design: below about
   11px the strokes land between device pixels and it turns to mush, and no amount of
   antialiasing gets it back. So 11.2px (--fs-2xs) is the floor for pixel text, and it is used
   only for stack counts and tier numerals - things you glance at, never read.

   Prose is a different face (Rubik) with a proper hinted design, so its floor is lower.

   TO SCALE THE WHOLE UI: change --ui-root. Everything below is relative to it. */
:root{
  font-size:112.5%;              /* 18px. The browser default 16px made the pixel face too
                                    small at every step of the scale below. */
  --ui-scale:1;                  /* multiplies the chest furniture - slots, panels, gaps */
}

:root{
  --pixel:"Press Start 2P", ui-monospace, monospace;
  --sans:"Rubik", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* PIXEL TYPE SCALE. Nothing may use a size that is not on this ladder. */
  --fs-2xs:.62rem;    /* 11.2px - the floor. Stack counts, tier numerals. Glanced at. */
  --fs-xs:.72rem;     /* 13.0px - chips, table headers, tooltip material counts */
  --fs-sm:.86rem;     /* 15.5px - tooltip item names, chest titles */
  --fs-md:1.05rem;    /* 18.9px - section headings, small */
  --fs-lg:1.3rem;     /* 23.4px - section headings */
  --fs-xl:2rem;       /* 36.0px - page title */

  /* PROSE SCALE, in the sans face. */
  --fs-body:1rem;     /* 18px */
  --fs-lede:1.1rem;   /* 19.8px */
  --fs-note:.9rem;    /* 16.2px - captions, chest notes */

  --measure:1040px;       /* page column. Wider than rev 2's 900 because everything in it grew.
                             /worth overrides to 1240px; a data table needing the width is the
                             only legitimate reason to touch this. */
  --measure-text:62ch;

  --slot-cell-n:calc(64 * var(--ui-scale));    /* unitless px count; see .slot */
  --slot-cell:calc(var(--slot-cell-n) * 1px);    /* was 48. A 300px render inside 48px was the
                                                  other half of the legibility problem. */
  --slot-inline-n:calc(42 * var(--ui-scale));
  --slot-inline:calc(var(--slot-inline-n) * 1px);
  --radius:9px;
  --gap:18px;
}

/* ---------------------------------------------------------------------------- THEME TOKENS */
:root{
  --bg:#0a0d0a; --ink:#eaf2e6; --muted:#9db197;
  --grass:#4ade5f; --grass-dim:#2fa347; --green:#22b34d; --green-dim:#188038;
  --line:rgba(255,255,255,.18);
  --card:rgba(10,13,10,.55);
  --accent:var(--grass);

  /* THE CHEST GUI. Vanilla's real values, not approximations: a #C6C6C6 panel, #8B8B8B slots,
     and a two-tone bevel that is #373737 top-left and pure white bottom-right. A slot that is
     merely "light grey" reads as a web control; a slot that is these five values reads as the
     game. */
  --slot:#8B8B8B;
  --slot-inset:#373737;
  --slot-edge:#FFFFFF;
  --panel:#C6C6C6;
  --panel-lit:#FFFFFF;
  --panel-dark:#555555;
  --panel-ink:#3A3A3A;

  /* TIER RAMP. aqua -> purple -> gold, the order players already read as "better", and the same
     ramp the plugin uses (MachineTier.colour is AQUA / LIGHT_PURPLE / GOLD). Colour is never
     the only signal: every tier also carries a numeral and a word. */
  --t1:#9db197; --t2:#00b7d6; --t3:#8b2fd6; --t4:#d98800;
  --soon:#e0a13f;
  --up:#4ade5f; --down:#ffa33d;
}

*{box-sizing:border-box}
html{scroll-behavior:smooth}
/* The green gradient is gone: the ground is the end portal, painted by .endportal behind
   everything. body stays transparent so that shows through. */
body{
  margin:0; background:#05000c; color:var(--ink);
  font-family:var(--sans); font-size:var(--fs-body); line-height:1.65;
  -webkit-font-smoothing:antialiased;
}
.wrap{max-width:var(--measure); margin:0 auto; padding:26px 22px 80px}
.wrap.wide{--measure:1240px}
code{font-size:.94em; background:rgba(255,255,255,.07); padding:1px 5px; border-radius:4px}

/* The old inline .bar/.nav/.lnk header was removed in rev 6 - .topbar replaced it, and a
   toolkit with two header components is a toolkit with one too many. */

/* ---------------------------------------------------------------------------- §4.2 UNIFIED */
header.hero{margin-bottom:30px}
h1{font-family:var(--pixel); font-size:var(--fs-xl); line-height:1.5; margin:0 0 20px}
h1 .mc, .sectionhead h2 .mc{color:var(--accent)}
.lede{color:var(--muted); font-size:var(--fs-lede); max-width:var(--measure-text); margin:0}

.sectionhead{margin:58px 0 16px}
.sectionhead h2{font-family:var(--pixel); font-size:var(--fs-lg); line-height:1.5; margin:0 0 12px}
.sectionhead.sm h2{font-size:var(--fs-md)}
.sectionhead p{color:var(--muted); margin:0; max-width:var(--measure-text)}

/* ==========================================================================================
   THE ATOM — a vanilla chest slot.

   Built out of box-shadow rather than border so the bevel costs no layout: two hard inset
   shadows, dark top-left and white bottom-right, exactly as the container texture draws them.
   No border-radius anywhere in here on purpose - the GUI has none, and a 5px radius is the one
   change that makes the whole thing stop reading as Minecraft.
   ========================================================================================== */
.slot{
  --size-n:var(--slot-cell-n);          /* unitless: the slot's size in px */
  --size:calc(var(--size-n) * 1px);
  width:var(--size); height:var(--size); flex:0 0 var(--size);
  background:var(--slot);
  box-shadow:
    inset 3px 3px 0 0 var(--slot-inset),
    inset -3px -3px 0 0 var(--slot-edge);
  border-radius:0;
  display:inline-flex; align-items:center; justify-content:center;
  position:relative; vertical-align:middle;
  isolation:isolate;   /* so the glint's blend mode cannot reach the page behind the slot */
}
/* HOW BIG THE ART SITS IN THE SLOT - one number, both tiers.
   The art tier is a percentage of the slot and the tex tier used to be a fixed 16px times a
   hand-tuned zoom (3.0 here, 2.4 in the /worth grid, 1.9 inline, 1*--gs in the crafting table).
   Two scaling models for one atom: they agreed only where somebody had tuned them to agree, and
   the moment a tex item sat next to an art item - the stews - they visibly did not.
   Now the tex zoom is DERIVED from the slot size and the same fill fraction, so every context
   that sets --size gets both tiers at matching size for free and there is nothing left to tune. */
.slot{
  --art-fill:.74;
  /* Unitless throughout, or transform:scale() silently rejects it. */
}
.slot.inline{--size-n:var(--slot-inline-n)}

/* ==========================================================================================
   THE ITEM SLOT — one rule for every picture of an item on the site.

   WHY THIS REPLACED ELEVEN RULES. Eleven places sized an item image and each did it its own way.
   Seven of them used a fixed 16px box plus transform:scale(), which silently assumes every
   source is a 16px sprite. That held while it was true. Then the wiki's 300px renders and
   render-blocks.py's 48px cubes arrived, and those seven broke while the four using percentages
   did not - which is why the block suppressor and the coop scaled wrong and the tier matrix
   beside them did not. One of the seven was worse: .rscell scaled by calc(var(--rs) * ...),
   and --rs is a LENGTH, so scale() rejected it and the image sat at natural size.

   THE SPLIT THAT FIXES IT. A slot decides HOW BIG ITS BOX IS. This rule decides HOW THE IMAGE
   FILLS THAT BOX, and does it in a way no source resolution can break:

     percentage sizing   74% of whatever the box is - identical for a 16px sprite, a 48px cube
                         and a 300px render, because a percentage has no opinion about pixels.
     object-fit: cover   scales to fill rather than cropping at natural size. For a square source
                         that is simply "fill the box".
     object-position:top which is what makes an ANIMATION STRIP work. Lava and sea lantern ship
                         as one tall column of frames; cover scales the width to fit and top
                         anchors it, so you see frame one at full size. The old code needed a
                         separate crop rule and a hand-tuned zoom to do the same job worse.
     transform: none     nothing here scales by transform, so nothing here can be broken by a
                         unit slipping into a scale().

   ADD A NEW KIND OF SLOT by giving it a box and adding it to this list. Do not size its image.
   ========================================================================================== */
:root{ --art-fill:.74 }

.slot > img,
.rscell > img,
.tblock > img,
.fcell > img,
.pickbtn > img,
.tab > img,
.autotab > img,
.tip .mini i > img,
.tip .ci > img{
  width:calc(var(--art-fill) * 100%);
  height:calc(var(--art-fill) * 100%);
  object-fit:cover;
  object-position:top center;
  image-rendering:pixelated;
  transform:none;
  position:relative;
  z-index:1;
}

/* The three boxes that want the image edge to edge rather than inset. */
.crafttable .slot > img,
.tip .mini i > img{ --art-fill:1 }

/* Tab and roll-up icons are inline chrome, not slots, so they carry their own small box - but
   they fill it by the same rule as everything else. */
.tab > img, .autotab > img{ width:20px; height:20px; flex:0 0 20px }
.tip .ci > img{ width:18px; height:18px }
.tblock > img{ --art-fill:.82 }

/* HOVER, the way the game does it: the slot lightens. It does not lift, and it does not glow. */
.slot.pick:hover, .slot.pick:focus-visible{background-color:#C5C5C5}

/* ART TIER — follows the ICON SET, never the size. See §5 of the plan. */
.slot .letters{font-family:var(--pixel); font-size:var(--fs-2xs); color:var(--panel-ink)}

/* ------------------------------------------------------------------ THE ENCHANTMENT GLINT
   WAVY, NOT A SEARCHLIGHT. The first cut swept one hard bright band across the item, which read
   as a highlight sliding over glass rather than as an enchantment. Vanilla's is a soft
   REPEATING field of blotchy diagonal bands, low contrast, that seems to ripple because two
   copies of it move across each other at different angles and speeds.

   So: repeating gradients rather than one band, many soft stops rather than a hard edge, ~.3
   alpha rather than ~.9, and the two layers set 34 degrees apart. That angle difference does
   the waving - where the two sets of bands cross they reinforce and where they oppose they
   cancel, and the moire between them drifts as they scroll. Neither layer alone looks wavy.

   MASKED TO THE ITEM by its own PNG, handed in as --art. That is why the sheen runs over the
   hopper's metal and not over the empty corners of the square, and it is what a full-square
   gradient gets wrong. mask-size tracks .art img above; if that 74% moves, move it here too. */
/* THE ART ITSELF, for a slot that carries no <img>.

   WHY THIS RULE HAD TO EXIST. A slot paints its picture one of two ways: an <img> child, sized by
   the primitive above, or - where the markup wants an empty box it can also glint - the --art
   custom property. Only the second half was ever written. `--art` appeared exactly once in this
   file, as the glint's mask-image, so a `.slot.art` with no child rendered its grey background and
   a shimmer in the SHAPE of the item, and no item. Thirty slots across the site looked like
   missing images because, literally, nothing drew them.

   The geometry is deliberately identical to the mask's - same calc, same centring, same no-repeat -
   so the glint lines up with the art it is supposed to be glinting on. */
.slot.art{
  background-image:var(--art);
  background-size:calc(var(--art-fill) * 100%) calc(var(--art-fill) * 100%);
  background-position:center;
  background-repeat:no-repeat;
  image-rendering:pixelated;
}

.slot.glint::before,
.slot.glint::after{
  content:""; position:absolute; inset:0; z-index:2; pointer-events:none;
  -webkit-mask-image:var(--art); mask-image:var(--art);
  -webkit-mask-size:calc(var(--art-fill) * 100%) calc(var(--art-fill) * 100%);
  mask-size:calc(var(--art-fill) * 100%) calc(var(--art-fill) * 100%);
  -webkit-mask-position:center;  mask-position:center;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  mix-blend-mode:screen;
}
.slot.glint::before{
  background:repeating-linear-gradient(112deg,
    rgba(150,60,225,0)    0px,
    rgba(150,60,225,.05)  9px,
    rgba(178,108,248,.22) 17px,
    rgba(214,170,255,.30) 22px,
    rgba(178,108,248,.22) 27px,
    rgba(150,60,225,.05)  35px,
    rgba(150,60,225,0)    49px);
  background-size:180% 180%;
  animation:glint-a 7.5s linear infinite;
}
.slot.glint::after{
  background:repeating-linear-gradient(78deg,
    rgba(126,40,200,0)   0px,
    rgba(126,40,200,.06) 12px,
    rgba(163,86,236,.20) 21px,
    rgba(126,40,200,.06) 31px,
    rgba(126,40,200,0)   44px);
  background-size:220% 220%;
  animation:glint-b 11s linear infinite;
  opacity:.8;
}
@keyframes glint-a{ from{background-position:0% 0%}     to{background-position:-180% 180%} }
@keyframes glint-b{ from{background-position:100% 100%} to{background-position:320% -80%} }

/* Motion is decoration here, never information: the item is still readable and its name is
   still in the tooltip and the aria-label with the sheen held still. */
@media (prefers-reduced-motion: reduce){
  .slot.glint::before, .slot.glint::after{animation:none}
  .slot.glint::before{background-position:50% 50%}
  .slot.glint::after{opacity:.5; background-position:50% 50%}
}

/* TIER RING + NUMERAL. The ring sits inside the slot's own edge so it never eats the bevel. */
.slot.t2{outline:4px solid var(--t2); outline-offset:-4px}
.slot.t3{outline:4px solid var(--t3); outline-offset:-4px}
.slot.t4{outline:4px solid var(--t4); outline-offset:-4px}
.slot .rank{
  position:absolute; right:-3px; top:-4px; z-index:3;
  font-family:var(--pixel); font-size:var(--fs-2xs); line-height:1;
  padding:3px 4px; color:#fff;
  text-shadow:1px 1px 0 rgba(0,0,0,.65);
}
.slot.t2 .rank{background:var(--t2)}
.slot.t3 .rank{background:var(--t3)}
.slot.t4 .rank{background:var(--t4)}

/* COUNT, bottom-right, drawn the way the game draws a stack size. */
.slot .qty{
  position:absolute; right:2px; bottom:0; z-index:3;
  font-family:var(--pixel); font-size:var(--fs-2xs); color:#fff;
  text-shadow:2px 2px 0 rgba(0,0,0,.75);
}

/* NOT-LIVE DOT. Mark only the exception, and say what unmarked means in the legend. */
.slot.notlive::after{
  content:""; position:absolute; left:-5px; bottom:-5px; z-index:4;
  width:13px; height:13px;
  background:var(--soon); box-shadow:0 0 0 3px var(--panel);
  -webkit-mask-image:none; mask-image:none; mix-blend-mode:normal; animation:none;
}

/* ------------------------------------------------------------------ THE TOOLTIP
   Vanilla's item tooltip: a near-black box with a two-tone purple border, the name in white and
   the extra lines under it in grey. */
.slot[data-tip]{cursor:pointer}
.slot[data-tip] .tip{
  position:absolute; bottom:calc(100% + 11px); left:50%; transform:translateX(-50%);
  font-family:var(--pixel); line-height:1.7; letter-spacing:.5px;
  padding:11px 13px; z-index:20;
  background:#100010f2; color:#fff;
  text-shadow:2px 2px 0 rgba(0,0,0,.8);
  border:3px solid #2d0a63;
  box-shadow:0 0 0 3px #100010f2, inset 0 0 0 2px #5a189a55;
  opacity:0; visibility:hidden; transition:opacity .1s ease;
  text-align:left; white-space:nowrap;
}
.slot[data-tip]:hover .tip,
.slot[data-tip]:focus-visible .tip,
.slot[data-tip].tipped .tip{opacity:1; visibility:visible}
.slot[data-tip]:hover, .slot[data-tip]:focus-visible, .slot[data-tip].tipped{z-index:19}
.slot[data-tip].tipL .tip{left:0; transform:none}
.slot[data-tip].tipR .tip{left:auto; right:0; transform:none}
/* Flipped under the item, for a slot too near the top of the window to draw above it. */
.slot[data-tip].tipB .tip{bottom:auto; top:calc(100% + 11px)}

/* THE COST ROLL-UP, under the name. Vanilla puts an item's extra lines under its name in grey,
   so this does too. Each row is an icon and a count, because "625" beside an iron ingot is read
   faster than the words "625 Iron Ingot" and survives not knowing English. */
.tip .tipname{display:block; color:#fff; font-size:var(--fs-sm); margin-bottom:6px}
.tip .tipsub{display:block; color:#8a8a8a; font-size:var(--fs-2xs); margin-bottom:9px}
.tip .cost{display:flex; flex-wrap:wrap; gap:8px 14px; max-width:340px; white-space:normal}
.tip .ci{display:inline-flex; align-items:center; gap:6px; color:#c8c8c8; font-size:var(--fs-xs)}
.tip .ci i{font-style:normal; color:#fff}

/* ==========================================================================================
   THE CHEST PANEL — the container the wiki index becomes.
   ========================================================================================== */
.chest{
  background:var(--panel);
  box-shadow:
    inset 4px 4px 0 0 var(--panel-lit),
    inset -4px -4px 0 0 var(--panel-dark);
  padding:20px;
  margin:0 0 8px;
}
.chest .chesttitle{
  font-family:var(--pixel); font-size:var(--fs-sm); color:var(--panel-ink);
  margin:4px 0 16px; letter-spacing:.5px;
}
.chest .chestnote{
  font-family:var(--sans); font-size:var(--fs-note); color:#4a4a4a;
  margin:16px 0 0; max-width:70ch;
}
.iconwall{display:flex; flex-wrap:wrap; gap:5px; margin:0; padding:0; list-style:none}
.iconwall.grouped{gap:20px}
.iconwall .group{display:flex; gap:5px; flex-wrap:wrap}
.iconwall a{text-decoration:none; display:inline-flex; line-height:0}
.walllegend{
  display:flex; gap:22px; flex-wrap:wrap; align-items:center;
  margin:16px 0 0; color:var(--muted); font-size:var(--fs-note);
}
.walllegend b{color:var(--ink); font-weight:700}
.walllegend .dot{
  display:inline-block; width:13px; height:13px;
  background:var(--soon); margin-right:8px; vertical-align:middle;
}

/* ---------------------------------------------------------------------------- CHIPS */
.tag{
  display:inline-block; font-family:var(--pixel); font-size:var(--fs-xs);
  letter-spacing:1px; text-transform:uppercase; padding:7px 11px; border-radius:6px;
  color:#08130a; background:var(--accent);
}
.tag.t2{background:var(--t2); color:#fff} .tag.t3{background:var(--t3); color:#fff} .tag.t4{background:var(--t4); color:#fff}
.marker{
  display:inline-block; font-family:var(--pixel); font-size:var(--fs-2xs);
  letter-spacing:1px; text-transform:uppercase; padding:5px 8px; border-radius:5px;
  vertical-align:middle; margin-left:8px; white-space:nowrap;
  background:transparent; color:var(--muted); border:2px solid var(--line);
}
.marker.soon{color:var(--soon); border-color:var(--soon); border-style:dashed}
.marker.down{color:var(--down); border-color:var(--down)}
.marker.up{color:var(--up); border-color:var(--up)}

/* ---------------------------------------------------------------------------- CARD + TABLE */
.card{
  background:var(--card); border:2px solid var(--line); border-radius:14px;
  padding:22px; display:flex; flex-direction:column; gap:14px;
}
.cardgrid{display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:var(--gap)}
.scrollx{overflow-x:auto}
table.data{width:100%; border-collapse:collapse; font-size:var(--fs-note)}
table.data th, table.data td{padding:13px 14px; text-align:left; border-bottom:1px solid rgba(255,255,255,.08)}
table.data thead th{font-family:var(--pixel); font-size:var(--fs-2xs); letter-spacing:1px; text-transform:uppercase; color:var(--muted)}
.namecell{display:flex; align-items:center; gap:12px}

/* ---------------------------------------------------------------------------- .facts */
.facts{list-style:none; margin:0; padding:0; display:grid; gap:10px}
.facts li{display:flex; gap:12px; font-size:var(--fs-note)}
.facts b{flex:0 0 120px; color:var(--accent)}
.factgrid{
  list-style:none; margin:0; padding:0;
  display:grid; grid-template-columns:1fr 1fr; gap:1px;
  background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.08);
  border-radius:12px; overflow:hidden;
}
.factgrid li{background:#0c110c; padding:16px 18px; font-size:var(--fs-note)}

/* ==========================================================================================
   THE STORE THEME — decision 1, 2026-08-31.
   Same markup, same primitives, DIFFERENT TOKEN VALUES. Nothing here redefines a component,
   and the chest slot needs no override at all: it was already the game's.
   ========================================================================================== */
[data-theme="gui"]{
  --bg:#16161A; --ink:#FFFFFF; --muted:#AAAAAA;
  --line:#4A4A52;
  --card:#2A2A31;
  --accent:#FFAA00;                 /* the REAL gold */
  --green:#55FF55; --green-dim:#2f8f2f; --grass:#55FF55; --grass-dim:#2f8f2f;
  --radius:0px;
  --sans:"Press Start 2P", ui-monospace, monospace;
  background:var(--bg);
  background-image:
    repeating-linear-gradient(0deg,  rgba(255,255,255,.016) 0 16px, transparent 16px 32px),
    repeating-linear-gradient(90deg, rgba(255,255,255,.016) 0 16px, transparent 16px 32px);
}
/* The GUI face is far wider than Rubik at the same size, so prose in this theme steps DOWN the
   scale rather than keeping the site's body size. */
[data-theme="gui"] .lede,
[data-theme="gui"] .sectionhead p,
[data-theme="gui"] .card p{font-size:var(--fs-xs); line-height:2}
[data-theme="gui"] .card{border-width:4px; border-color:#43434D; border-radius:0}
[data-theme="gui"] .lnk{border-radius:0; font-size:var(--fs-2xs)}

/* ---------------------------------------------------------------------------- SMALL SCREENS
   The scale steps down as a whole rather than per-element, which is the point of having one. */
@media (max-width:820px){
  :root{--ui-scale:.85}
  .factgrid{grid-template-columns:1fr}
  .iconwall.grouped{gap:12px}
}
@media (max-width:520px){
  :root{font-size:106.25%; --ui-scale:.75}
  .tip .cost{max-width:210px}
}

/* ---------------------------------------------------------------------------- NAMED GROUPS
   A run of squares with a gap after it is a layout; a run of squares with a NAME over it is an
   organisation. The label is the panel's own ink at the smallest pixel size, so it reads as a
   part of the chest rather than as page furniture sitting on top of it. */
.iconwall .group{display:flex; flex-direction:column; gap:7px}
.iconwall .groupslots{display:flex; gap:5px; flex-wrap:wrap}
.grouplabel{
  font-family:var(--pixel); font-size:var(--fs-2xs); letter-spacing:.5px;
  color:#5c5c5c; text-transform:uppercase; line-height:1;
}

/* ==========================================================================================
   THE SITE NAV — rev 6. Its own bar, full bleed, owning the top of the window.

   Rev 5 put the tabs inline at the top of the page column, where they sat among the old header
   text and read as page content. They are not page content: they are the frame. So the strip is
   now a band of its own that spans the viewport, sticks to the top, and the page hangs below it.

   The tabs are the creative inventory's. The ACTIVE tab is lifted and joined to the band, which
   is what says "you are inside this one" rather than "this one is highlighted".
   ========================================================================================== */
.topbar{
  position:sticky; top:0; z-index:60;
  background:#1b1b20;
  border-bottom:4px solid #000;
  box-shadow:0 4px 0 0 rgba(0,0,0,.45);
}
/* Brand pinned hard left, tabs centred in the bar. Three columns rather than a flex row: with
   `justify-content:center` the tabs would centre in the space LEFT OVER after the brand, which
   is not the middle of the bar. A grid with two equal outer columns puts them in the actual
   centre, and the empty third column is what keeps them there. */
/* FLEX, NOT A THREE-COLUMN GRID, and the difference is a bug.

   `grid-template-columns:1fr auto 1fr` put the brand in column one and centred the tabs in column
   two. A grid column does not clip its contents: the brand carries white-space:nowrap, so as soon
   as it is wider than its 1fr share it OVERFLOWS into the tab strip and lands on top of whichever
   tab is first. That tab is Home, which is why Home was the one that went missing rather than any
   other - nothing was absent from the markup, it was covered.

   Flex with `margin-left:auto` on the strip cannot do that: the brand takes its natural width
   first and the tabs get what is left. The strip is very slightly off true centre as a result,
   which is a fair trade for a tab that is always visible, and it wraps rather than clipping when
   the bar runs out of room. */
.topbar-in{
  max-width:var(--measure); margin:0 auto;
  padding:0 22px;
  display:flex; align-items:flex-end; gap:22px; flex-wrap:wrap;
  min-height:78px;
}
.topbar-in .brand{flex:0 0 auto}
.topbar-in .tabs{flex:1 1 auto; justify-content:center; min-width:0}
.topbar .brand{
  font-family:var(--pixel); font-size:var(--fs-md); letter-spacing:.5px;
  text-decoration:none; color:#fff; padding-bottom:20px; white-space:nowrap;
}
.topbar .brand .mc{color:var(--accent)}
.tabs{display:flex; align-items:flex-end; gap:6px; flex-wrap:wrap}

.tab{
  display:flex; align-items:center; gap:12px;
  padding:14px 22px 16px; text-decoration:none;
  font-family:var(--pixel); font-size:var(--fs-xs); letter-spacing:.5px;
  color:#2b2b2b; background:#9c9c9c;
  box-shadow: inset 3px 3px 0 0 #cfcfcf, inset -3px -3px 0 0 #666;
  position:relative; top:6px;
  transition:top .08s ease, background .12s ease;
}
.tab:hover{background:#b6b6b6; top:3px}
.tab[aria-current="page"]{
  background:var(--panel); color:#141414; top:0;
  padding-bottom:22px; margin-bottom:-4px;
  box-shadow: inset 3px 3px 0 0 var(--panel-lit), inset -3px 0 0 0 var(--panel-dark);
  z-index:2;
}

@media (max-width:760px){
  .topbar-in{min-height:64px; gap:12px; padding:0 14px}
  .tab{padding:12px 14px 14px}
  .tab .tablabel{display:none}   /* the icon alone still names the section */
  .topbar .brand{font-size:var(--fs-sm); padding-bottom:16px}
}

/* ==========================================================================================
   THE END PORTAL GROUND — rev 13. One canvas; the drawing lives in portal.js.

   WHAT THIS USED TO BE, AND WHY IT IS NOT ANY MORE. Rev 12 stacked fourteen elements, each 170%
   of the viewport, each with mix-blend-mode and will-change:transform. That is fourteen
   compositor layers permanently resident on the GPU behind every page; when their texture memory
   does not fit, the compositor evicts and re-uploads, and THAT was the flickering on the wiki.
   It was never a tuning problem.

   It also could not rotate. A CSS tiled background scales and translates and stops there, so
   every pass was an axis-aligned copy of the same star sheet and the field read as flat repeats
   rather than depth. The real thing gives each pass its own rotation, which is precisely what
   stops the passes looking like one picture drawn thirteen times.

   A canvas is ONE layer and a canvas transform rotates, so both problems have the same answer.
   Everything about how the passes are built - scale, rotation, offset, per-pass colour multiply,
   additive blend, depth attenuation - is documented at the top of portal.js.

   The CSS that remains is a dark ground, so that a page with JavaScript off or a texture that
   fails to load is still the void rather than a white sheet.
   ========================================================================================== */
.endportal{
  position:fixed; inset:0; z-index:-1; overflow:hidden;
  background:#02060f;
  contain:strict;          /* nothing in here affects layout or paint anywhere else */
}
.endportal canvas{
  display:block; width:100%; height:100%;
  image-rendering:pixelated;
}

/* ---------------------------------------------------------------------------- RECIPE ROW
   The crafting grid and its result. Three columns of slots, an arrow, and the output - the
   game's own arrangement, built from the same .slot as everything else. */
.craftrow{display:flex; align-items:center; gap:18px; flex-wrap:wrap}
.craftgrid{display:grid; grid-template-columns:repeat(3, auto); gap:5px}
.craftarrow{font-family:var(--pixel); font-size:var(--fs-md); color:var(--panel-ink)}

/* ==========================================================================================
   THE TIER TABLE — rev 8.

   Six families x three tiers was drawn as six loose runs of three, which is a LIST of ladders
   and makes the reader climb each one separately. It is really a matrix: family down the side,
   tier across the top. Laid out that way a whole column is one tier, so "what does Mega cost
   me across the board" is a glance rather than six glances, and the ladders line up.
   ========================================================================================== */
.tiertable{
  display:grid;
  grid-template-columns:auto repeat(3, minmax(0, 1fr));
  gap:6px 14px;
  align-items:center;
}
.tiertable .th{
  font-family:var(--pixel); font-size:var(--fs-2xs); letter-spacing:.5px;
  text-transform:uppercase; color:#fff; padding:6px 9px; text-align:center;
}
.tiertable .th.t2{background:var(--t2)}
.tiertable .th.t3{background:var(--t3)}
.tiertable .th.t4{background:var(--t4)}
.tiertable .th.corner{background:none}
.tiertable .fam{
  font-family:var(--pixel); font-size:var(--fs-2xs); color:#3A3A3A;
  text-align:right; padding-right:6px; white-space:nowrap;
}
.tiertable .cell{display:flex; justify-content:center}
/* A hairline under each family row, so the eye tracks across a long row without drifting. */
.tiertable .rule{grid-column:1 / -1; height:2px; background:rgba(0,0,0,.10)}

@media (max-width:640px){
  .tiertable{grid-template-columns:auto repeat(3, auto); gap:5px 8px}
  .tiertable .fam{font-size:var(--fs-2xs); white-space:normal; max-width:9ch; line-height:1.4}
}

/* ==========================================================================================
   THE CRAFTING TABLE — the game's own GUI texture, with real slots laid on it.

   textures/crafting_table.png is the container GUI from the client jar, pulled the same way the
   item sprites and the portal textures were. The GUI occupies the top-left 176x166 of a 256x256
   sheet; it draws all 166 rows of it - the crafting grid, the arrow, the result and the player inventory
   underneath. Rev 17 cropped to the top 82 rows, which is the crafting area alone and is a squat
   176x82 box; the full GUI is 176x166, near enough square, which is what "grow its height to
   match the width" asks for. The inventory slots draw empty, which is what a crafting table
   looks like when you are reading a recipe rather than standing at one.

   THE COORDINATES ARE MEASURED, not remembered. Scanning the sheet for slot-grey (#8b8b8b) puts
   the grid's three columns at x 29/47/65 and its three rows at y 16/34/52, each slot 18px pitch
   with a 16px interior one pixel in - so item cells sit at x 30/48/66 and y 17/35/53. The result
   box is 26x26 at x 119, y 30, so its 16px item sits at x 124, y 35.

   EVERYTHING IS SCALED BY ONE INTEGER, --gs. Integer only: at 3.5x a 16px sprite lands on half
   pixels and the whole thing goes soft, which is the one way to make pixel art look wrong.

   The cells are ordinary .slot elements with their chrome switched off - the texture is drawing
   the slot now - so the tooltips, the tier rings, the glint and the art tiers all still work.
   ========================================================================================== */
.crafttable{
  --gs:5;
  position:relative;
  width:calc(176px * var(--gs));
  height:calc(166px * var(--gs));
  max-width:100%;
  background-image:url(/ui/textures/crafting_table.png);
  background-size:calc(256px * var(--gs)) calc(256px * var(--gs));
  background-repeat:no-repeat;
  image-rendering:pixelated;
}
.crafttable .ctcell{position:absolute}
/* Inside the table the texture IS the slot, so the atom drops its own fill and bevel. */
.crafttable .slot{
  position:relative;
  --size-n:calc(16 * var(--gs));
  background:transparent;
  box-shadow:none;
}
.crafttable .slot.pick:hover{background-color:rgba(255,255,255,.28)}
.crafttable .slot .rank{right:-4px; top:-6px}
.crafttable .ctlabel{
  position:absolute; left:calc(8px * var(--gs)); top:calc(5px * var(--gs));
  font-family:var(--pixel); font-size:var(--fs-2xs); color:#3f3f3f;
}
@media (max-width:1000px){ .crafttable{--gs:4} }
@media (max-width:760px){  .crafttable{--gs:3} }
@media (max-width:520px){  .crafttable{--gs:2} }

/* ==========================================================================================
   PANEL FADE-IN.

   The panels are heavy - a chest full of 300px renders, or fourteen hundred sprites - so they
   land in visible steps as the images decode. A short rise-and-fade covers that: the panel is
   already in place before its contents finish arriving, so what you see is one thing appearing
   rather than several arriving.

   `both` matters: without it the panel is fully visible for one frame before the animation runs,
   which is a flash rather than a fade. The stagger is small on purpose - it should read as one
   page settling, not as a sequence being performed.
   ========================================================================================== */
@keyframes panel-in{
  from{ opacity:0; transform:translateY(14px) }
  to  { opacity:1; transform:none }
}
@keyframes bar-in{
  from{ opacity:0; transform:translateY(-10px) }
  to  { opacity:1; transform:none }
}
/* FILL-MODE MATTERS MORE THAN IT LOOKS. `both` leaves the animation permanently in effect, and
   an animation of opacity/transform that is in effect CREATES A STACKING CONTEXT. Every panel
   therefore became its own context, so a tooltip's z-index only stacked inside its own panel and
   the next panel - a later sibling, painted wholesale afterwards - covered anything that hung
   below the first one's edge. With 300px crafting tooltips that is most of them, and the result
   is a tooltip sliced in half by the panel underneath.

   `backwards` gives the same fade: the from-state applies before the animation starts, so there
   is still no flash of a fully-visible panel, and once it finishes the element reverts to its
   ordinary styles - no animation in effect, no stacking context, tooltips free to paint over
   whatever follows them. */
.topbar{ animation:bar-in .34s ease backwards }
.wrap > .chest{ animation:panel-in .42s cubic-bezier(.2,.7,.3,1) backwards }

/* Belt and braces for the same problem: a hovered panel lifts above its later siblings, so even
   if something else in the page grows a stacking context, a tooltip still paints over the panel
   below it. */
.wrap > .chest{ position:relative; z-index:1 }
.wrap > .chest:hover, .wrap > .chest:focus-within{ z-index:40 }
.wrap > .chest:nth-of-type(1){ animation-delay:.04s }
.wrap > .chest:nth-of-type(2){ animation-delay:.10s }
.wrap > .chest:nth-of-type(3){ animation-delay:.16s }
.wrap > .chest:nth-of-type(4){ animation-delay:.22s }

/* Content that arrives from a fetch cannot be staggered in CSS - it is not there at parse time -
   so worth.js adds .ready once the grid is populated. */
.itemgrid{ opacity:0; transition:opacity .3s ease }
.itemgrid.ready{ opacity:1 }

@media (prefers-reduced-motion: reduce){
  .topbar, .wrap > .chest{ animation:none }
  .itemgrid{ opacity:1; transition:none }
}

/* ---------------------------------------------------------------------------- RATES
   Throughput, printed under the item it belongs to. Every number is derived from the plugin's
   own constants - see RATES in build.py for the arithmetic and where each constant lives. */
.rate{
  display:block; margin-top:7px; text-align:center;
  font-family:var(--pixel); font-size:var(--fs-2xs); line-height:1.5; color:#3A3A3A;
}
.rate b{color:#14506b; font-weight:400}
.tiertable .cell{flex-direction:column; align-items:center}

/* ---------------------------------------------------------------------------- MINI RECIPE
   The recipe inside a tooltip, drawn on the GAME'S OWN crafting GUI rather than on boxes I drew.
   It is the same crafting_table.png the big display uses, cropped to the tightest region that
   holds the 3x3, the arrow and the result box and NOTHING else - no panel border, no title bar,
   no player inventory, because a tooltip has no room for chrome.

   THE CROP IS MEASURED. Scanning the sheet for slot-grey puts every slot between x 29-144 and
   y 16-69; one pixel out on each side keeps each slot's own dark bevel, which gives x 28, y 15,
   118 x 56. Cells then sit at x 2/20/38, y 2/20/38 and the result at x 96, y 20, all relative to
   the crop. Everything scales by --ms, integer only for the same reason --gs is. */
.tip .mini{
  --ms:2;
  /* display:block is LOAD-BEARING. .mini is a <span>, so it is display:inline by default, and
     width/height do not apply to an inline box - it collapses to nothing and every absolutely
     positioned cell inside it stacks on the same origin, which is the recipe drawn on top of
     itself. The grid version that preceded this carried display:grid and so never showed the
     problem; moving to the GUI texture dropped it. */
  display:block;
  position:relative;
  width:calc(118px * var(--ms));
  height:calc(56px * var(--ms));
  margin:9px 0 4px;
  background-image:url(/ui/textures/crafting_table.png);
  background-size:calc(256px * var(--ms)) calc(256px * var(--ms));
  background-position:calc(-28px * var(--ms)) calc(-15px * var(--ms));
  background-repeat:no-repeat;
  image-rendering:pixelated;
}
.tip .mini i{
  position:absolute;
  width:calc(16px * var(--ms));
  height:calc(16px * var(--ms));
  display:block;
}
/* THE TOOLTIP IS INSIDE THE SLOT, so `.slot.tex img` matches these images too - and its
   object-fit:none plus transform:scale() are meant for a 16px sprite in a 16px box, not for a
   recipe cell. Unreset, a 160px shulker shell was cropped to its empty top-left corner (it looked
   MISSING) and every cell was blown up by the slot's tex zoom (it looked wrongly scaled). Both
   symptoms, one leak. Reset all three properties here rather than narrowing the tex rule, because
   a recipe cell wants the whole image scaled to fit whatever tier it came from. */
.tip .minihead{display:block; color:#8a8a8a; font-size:var(--fs-2xs); margin-top:8px}
/* The two counts a player actually wants off a recipe: what it eats, and what it gives back. */
.tip .mtotal{
  display:block; color:#c8c8c8; font-size:var(--fs-2xs); margin:2px 0 4px;
}
.tip .mtotal b{color:#fff}

/* ---------------------------------------------------------------------------- COMING SOON
   A chest whose contents do not exist yet. Marked on the panel, not only on the items, because
   a whole section being unbuilt is a different claim from one item in it being unbuilt. */
.chest.soon{background:#b9b2a4; box-shadow: inset 4px 4px 0 0 #efe8d8, inset -4px -4px 0 0 #6f6a5e}
.soonflag{
  font-family:var(--sans); font-size:var(--fs-note); font-weight:700;
  color:#7a4d00; background:#f5c265; padding:3px 9px; margin-left:10px; vertical-align:middle;
}
.chest.soon .slot{filter:saturate(.35)}

/* ---------------------------------------------------------------------------- LONG FORM
   The one place on the site with real prose. Everything else is items in slots; the placer and
   the breaker have a mechanic that a slot cannot carry, so this is a deliberate exception rather
   than the descriptions the rest of the page had removed. */
.longform{margin-top:20px; max-width:74ch}
.longform p{margin:0 0 14px; color:#2f2f2f; font-size:var(--fs-note); line-height:1.75}
.longform p:last-child{margin-bottom:0}
.longform b{color:#14506b}
.longform em{font-style:italic}

/* ==========================================================================================
   THE BREAKER RANGE DIAGRAM.

   Replaced the prose that used to sit here. The rule - distance = 16 - signal - runs backwards
   from what anyone expects, and a paragraph saying so is much less convincing than a lever you
   can flip. Everything drawn is derived from one integer, how many dust the signal crossed.
   ========================================================================================== */
.rsdemo{--rs:34px; margin-top:18px}
.rsrow{
  display:flex; gap:2px; align-items:center; flex-wrap:nowrap;
  overflow-x:auto; padding:10px 4px 14px;
  background:#A8A8A8;
  box-shadow:inset 3px 3px 0 0 #7a7a7a, inset -3px -3px 0 0 #d8d8d8;
}
.rscell{
  position:relative; flex:0 0 var(--rs); width:var(--rs); height:var(--rs);
  background:#8B8B8B;
  box-shadow: inset 2px 2px 0 0 #373737, inset -2px -2px 0 0 #FFFFFF;
  display:flex; align-items:center; justify-content:center;
  border:0; padding:0; margin:0;
}
.rscell b{
  position:absolute; right:1px; bottom:-1px; z-index:2;
  font-family:var(--pixel); font-size:.5rem; color:#fff; font-weight:400;
  text-shadow:2px 2px 0 rgba(0,0,0,.8);
}
button.rscell{cursor:pointer}
button.rscell:hover{background:#C5C5C5}

.rslever.on{outline:3px solid #ff5555; outline-offset:-3px}
.rsdust.live{background:#a06060}
.rsdust.live b{color:#ffd0d0}
.rsbreaker{outline:3px solid #3A3A3A; outline-offset:-3px}
.rsbreaker.live{outline-color:#ff5555}
.rsblock{background:#7d7d7d}
.rsblock.target{outline:3px solid #ffb43d; outline-offset:-3px; background:#9a8a6a}
.rsblock.target b{color:#ffe6b0}
@keyframes rs-swing{
  0%{transform:none} 40%{transform:translateX(-3px) scale(.9)} 100%{transform:none}
}
.rsblock.swing{animation:rs-swing .34s ease}

.rscontrols{display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:12px}
.rsbtn{
  font-family:var(--pixel); font-size:var(--fs-2xs); color:#2b2b2b; background:#AFAFAF;
  border:0; cursor:pointer; padding:10px 14px;
  box-shadow: inset 2px 2px 0 0 #FFFFFF, inset -2px -2px 0 0 #6a6a6a;
}
.rsbtn:hover{background:#BEBEBE}
.rsbtn:active{box-shadow: inset 2px 2px 0 0 #5c5c5c, inset -2px -2px 0 0 #cfcfcf}
.rsread{
  font-family:var(--sans); font-size:var(--fs-note); color:#2f2f2f; margin:12px 0 0;
}
.rsread b{color:#14506b}
.rsrule{
  display:inline-block; margin-left:10px; padding:3px 8px;
  font-family:var(--pixel); font-size:var(--fs-2xs); color:#fff; background:#14506b;
}
.rshint{font-family:var(--sans); font-size:var(--fs-note); color:#4a4a4a; margin:10px 0 0}

/* ---------------------------------------------------------------------------- DROPDOWNS
   <details> panels, drawn as vanilla buttons that open. Used for the breaker's long tail: the
   tools it takes, what repairs it, and what each tool can actually mine. */
.drop{margin-top:10px; background:#B4B4B4; box-shadow: inset 2px 2px 0 0 #d8d8d8, inset -2px -2px 0 0 #7a7a7a}
.drop > summary{
  cursor:pointer; list-style:none; padding:13px 16px;
  font-family:var(--pixel); font-size:var(--fs-2xs); color:#2b2b2b;
}
.drop > summary::-webkit-details-marker{display:none}
.drop > summary::before{content:"\25B8  "; color:#5c5c5c}
.drop[open] > summary::before{content:"\25BE  "}
.drop > summary:hover{background:#C3C3C3}
.dropbody{padding:4px 16px 16px}
.dropbody p{margin:0 0 12px; font-size:var(--fs-note); color:#2f2f2f; line-height:1.7; max-width:70ch}
.dropbody p:last-child{margin-bottom:0}
.dropbody b{color:#14506b}

.toolpick{display:flex; gap:14px; flex-wrap:wrap; align-items:flex-end; margin-bottom:14px}
.toolpick label{display:flex; flex-direction:column; gap:6px}
.toolpick span{font-family:var(--pixel); font-size:var(--fs-2xs); color:#3A3A3A}
.toolpick select{
  font-family:var(--sans); font-size:var(--fs-note); color:#1c1c1c;
  background:#AFAFAF; border:0; padding:9px 12px; cursor:pointer;
  box-shadow: inset 2px 2px 0 0 #6a6a6a, inset -2px -2px 0 0 #FFFFFF;
}
.toolout{font-size:var(--fs-note); color:#2f2f2f; line-height:1.7; margin:0 0 12px; max-width:70ch}
.toolout b{color:#14506b}
.toolgrid{
  display:flex; flex-wrap:wrap; gap:3px; max-height:230px; overflow-y:auto;
  padding:6px; background:#A8A8A8; box-shadow:inset 2px 2px 0 0 #7a7a7a, inset -2px -2px 0 0 #d8d8d8;
}
.tblock{
  width:26px; height:26px; flex:0 0 26px; background:#8B8B8B;
  box-shadow: inset 1px 1px 0 0 #373737, inset -1px -1px 0 0 #FFFFFF;
  display:flex; align-items:center; justify-content:center;
}
.toolnote{font-size:var(--fs-note); color:#4a4a4a; margin:12px 0 0; max-width:70ch}
.toolnote b{color:#14506b}

/* ==========================================================================================
   THE AUTOMATION TAB STRIP.

   One tab per block, each holding its own demo. Replaces a column of <details> dropdowns: with
   five blocks the dropdowns became a list of closed doors, and a tab strip shows what is on
   offer without opening anything. The strip is the creative inventory's, one size down.
   ========================================================================================== */
.autotabs{display:flex; gap:4px; flex-wrap:wrap; margin:4px 0 0}
.autotab{
  display:flex; align-items:center; gap:9px; cursor:pointer;
  font-family:var(--pixel); font-size:var(--fs-2xs); letter-spacing:.5px;
  color:#2b2b2b; background:#9c9c9c; border:0; padding:11px 15px;
  box-shadow: inset 3px 3px 0 0 #cfcfcf, inset -3px -3px 0 0 #666;
}
.autotab:hover{background:#b6b6b6}
.autotab[aria-selected="true"]{
  background:#C6C6C6; color:#141414;
  box-shadow: inset 3px 3px 0 0 #FFFFFF, inset -3px -3px 0 0 #555;
}
.autopanel{padding:18px 2px 2px}
.autopanel[hidden]{display:none}

/* ---------------------------------------------------------------------------- demo scenes */
.rsrow.vert{flex-direction:column; align-items:center; overflow:visible; width:max-content; margin:0 auto}
.rsrow.dark{background:#4a4a52}
.rslamp{outline:3px solid #6a6a6a; outline-offset:-3px}
.rslamp.live{outline-color:#ffd166; background:#b8a05a}
.rslit{background:#a89a6a}
.rspopped{opacity:.45; outline:3px dashed #d06a6a; outline-offset:-3px}
.rsheld{outline:3px solid #3aa7ff; outline-offset:-3px}
.rsfall{outline:3px solid #9c9c9c; outline-offset:-3px}
.rsair{background:#6f6f6f; box-shadow:none}
.rsfired{outline:3px solid #ff5555; outline-offset:-3px; background:#9a6a6a}
.rscoop{outline:3px solid #7a5a2a; outline-offset:-3px}
.rscoop.live{outline-color:#4ade5f; background:#6a8a5a}
.rsmove{outline:3px solid #ffd166; outline-offset:-3px}

/* ---------------------------------------------------------------------------- centring
   Kelp: centre the special-item panels and their titles. The chests are galleries of items, and
   a gallery centres; the long-form prose under the breaker stays left-aligned, because centred
   paragraphs are hard to read. */
.chest .chesttitle{text-align:center}
.chest .iconwall{justify-content:center}
.chest .rsread, .chest .rshint, .chest .toolout, .chest .toolnote{margin-left:auto; margin-right:auto}
.chest .rscontrols{justify-content:center}
.autotabs{justify-content:center}
.tiertable{justify-content:center; margin:0 auto; width:max-content; max-width:100%}

/* ---------------------------------------------------------------------------- FURNACE GUI
   The breaker is a furnace, so its repair panel is drawn on the furnace's own GUI. Same method as
   the crafting table: crop the sheet to the machine, then place cells at measured coordinates.

   MEASURED, not remembered. Scanning furnace_gui.png for slot-grey puts the slot column at x
   55-72 and the result box at x 111-136, with slot rows spanning y 16-69 - the vanilla layout:
   input at 56,17, fuel at 56,53, result at 112,31. Cropping at x 54 / y 15 (one pixel out, to keep
   each slot's bevel) gives an 84 x 56 box with cells at 2,2 / 2,38 / 58,16. */
.furngui{
  --fs2:4;
  position:relative;
  width:calc(84px * var(--fs2));
  height:calc(56px * var(--fs2));
  margin:0 auto;
  background-image:url(/ui/textures/furnace_gui.png);
  background-size:calc(256px * var(--fs2)) calc(256px * var(--fs2));
  background-position:calc(-54px * var(--fs2)) calc(-15px * var(--fs2));
  background-repeat:no-repeat;
  image-rendering:pixelated;
}
.fcell{
  position:absolute;
  width:calc(16px * var(--fs2)); height:calc(16px * var(--fs2));
  display:flex; align-items:center; justify-content:center;
}
/* The fuel slot is the one that repairs, so it is the one marked. */
.fcell.fuel{outline:3px solid #4ade5f; outline-offset:-3px; background:rgba(74,222,95,.18)}
/* Vanilla's durability bar: a dark trough with a green fill that shortens as the tool wears. */
.fcell .dura{
  position:absolute; left:12%; right:12%; bottom:10%; height:9%;
  background:#000; display:block;
}
.fcell .dura b{display:block; height:100%; background:#4ade5f; transition:width .28s ease}
.rsdim{opacity:.5; outline:3px solid #4a4a52; outline-offset:-3px}
.rsdim b{color:#9aa}
@media (max-width:760px){ .furngui{--fs2:3} }

/* A tiered INGREDIENT inside a tooltip recipe. Three Mega Hoppers and three plain hoppers are the
   same texture; the ring and the numeral are the entire difference, so a recipe that drops them
   is not a smaller recipe, it is a wrong one. */
.tip .mini i.mcell.t2{outline:2px solid var(--t2); outline-offset:-2px}
.tip .mini i.mcell.t3{outline:2px solid var(--t3); outline-offset:-2px}
.tip .mini i.mcell.t4{outline:2px solid var(--t4); outline-offset:-2px}
.tip .mini i.mcell u{
  position:absolute; right:-2px; top:-3px; z-index:2;
  font-family:var(--pixel); font-size:.42rem; line-height:1; padding:1px 2px;
  color:#fff; text-decoration:none; text-shadow:1px 1px 0 rgba(0,0,0,.7);
}
.tip .mini i.mcell.t2 u{background:var(--t2)}
.tip .mini i.mcell.t3 u{background:var(--t3)}
.tip .mini i.mcell.t4 u{background:var(--t4)}

/* ---------------------------------------------------------------------------- ICON PICKERS
   A tool and a material are both things with a picture, so the picker shows pictures. The name
   stays on each button as title and aria-label, so it is still reachable by keyboard or reader. */
.picklab{font-family:var(--pixel); font-size:var(--fs-2xs); color:#3A3A3A; align-self:center}
.pickrow{display:flex; gap:4px; flex-wrap:wrap}
.pickbtn{
  width:44px; height:44px; padding:0; border:0; cursor:pointer;
  background:#8B8B8B;
  box-shadow: inset 2px 2px 0 0 #373737, inset -2px -2px 0 0 #FFFFFF;
  display:flex; align-items:center; justify-content:center;
}
.pickbtn:hover{background:#C5C5C5}
.pickbtn[aria-checked="true"]{background:#C6C6C6; outline:3px solid #4ade5f; outline-offset:-3px}

/* An enchantment has no item of its own to show, so this one picker stays words - the same
   button, widened, rather than a second control that behaves differently. */
.pickbtn.text{
  width:auto; height:44px; padding:0 14px;
  font-family:var(--pixel); font-size:var(--fs-2xs); color:#2b2b2b;
}

/* ---------------------------------------------------------------------------- ICON DROPDOWN
   Eighteen selection buttons on screen at once was the clutter. A dropdown shows the current
   choice and opens the rest, and every option stays a picture rather than reverting to a word. */
.icodrop{position:relative; display:flex; flex-direction:column; gap:6px}
.icodrop-btn{
  display:flex; align-items:center; gap:10px; cursor:pointer;
  font-family:var(--sans); font-size:var(--fs-note); color:#1c1c1c;
  background:#AFAFAF; border:0; padding:9px 12px; min-width:172px;
  box-shadow: inset 2px 2px 0 0 #FFFFFF, inset -2px -2px 0 0 #6a6a6a;
}
.icodrop-btn > img{width:26px; height:26px; flex:0 0 26px}
.icodrop-btn > span{flex:1 1 auto; text-align:left}
.icodrop-btn > i{font-style:normal; color:#4a4a4a}
.icodrop-btn:hover{background:#BEBEBE}
.icodrop-btn[aria-expanded="true"]{background:#8f8f8f; color:#fff;
  box-shadow: inset 2px 2px 0 0 #5c5c5c, inset -2px -2px 0 0 #cfcfcf}

.icodrop-menu{
  position:absolute; top:100%; left:0; z-index:30; min-width:100%;
  display:flex; flex-direction:column; gap:2px; padding:4px;
  background:#A8A8A8;
  box-shadow: inset 2px 2px 0 0 #d8d8d8, inset -2px -2px 0 0 #7a7a7a, 0 6px 0 0 rgba(0,0,0,.25);
}
.icodrop-menu[hidden]{display:none}
.icodrop-opt{
  display:flex; align-items:center; gap:10px; cursor:pointer; white-space:nowrap;
  font-family:var(--sans); font-size:var(--fs-note); color:#1c1c1c;
  background:#8B8B8B; border:0; padding:8px 12px;
  box-shadow: inset 2px 2px 0 0 #373737, inset -2px -2px 0 0 #FFFFFF;
}
.icodrop-opt > img{width:24px; height:24px; flex:0 0 24px}
.icodrop-opt:hover{background:#C5C5C5}
.icodrop-opt[aria-selected="true"]{background:#C6C6C6; outline:3px solid #4ade5f; outline-offset:-3px}

/* ---------------------------------------------------------------------------- HOME
   The landing page was the last thing on the old look. It is three panels now: what the server
   is, where to go, and where else we are. */
.homelede{font-size:var(--fs-lede); color:#2f2f2f; margin:0 auto; max-width:60ch; text-align:center}
.homelede b{color:#14506b}
.homeip{
  font-family:var(--pixel); font-size:var(--fs-md); color:#14506b;
  text-align:center; margin:18px 0 4px;
}
.hometiles{display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); gap:12px}
.hometile{
  display:flex; align-items:center; gap:16px; text-decoration:none; padding:14px;
  background:#8B8B8B;
  box-shadow: inset 3px 3px 0 0 #373737, inset -3px -3px 0 0 #FFFFFF;
}
.hometile:hover{background:#9d9d9d}
.hometext{display:flex; flex-direction:column; gap:5px}
.hometext b{font-family:var(--pixel); font-size:var(--fs-xs); color:#fff; text-shadow:2px 2px 0 rgba(0,0,0,.5)}
.hometext span{font-family:var(--sans); font-size:var(--fs-note); color:#eee}
/* THE STEW ROWS. The same box as a home tile - it is the right shape for an icon beside two lines
   of text - but not an <a>, so no hover lift: these rows are not links and must not offer to be
   clicked. The effect line carries the numbers, which is the whole reason the panel is a list of
   rows rather than a bare wall of five identical bowls. */
.stewtiles{display:grid; grid-template-columns:repeat(auto-fit,minmax(320px,1fr)); gap:12px}
.stewtile{
  display:flex; align-items:center; gap:16px; padding:14px;
  background:#8B8B8B;
  box-shadow: inset 3px 3px 0 0 #373737, inset -3px -3px 0 0 #FFFFFF;
}
.stewtext{display:flex; flex-direction:column; gap:5px}
.stewtext b{font-family:var(--pixel); font-size:var(--fs-xs); color:#fff; text-shadow:2px 2px 0 rgba(0,0,0,.5)}
.stewtext span{font-family:var(--sans); font-size:var(--fs-note); color:#eee}
.stewtext .cap{color:#d7d7d7}
.stewnote{font-family:var(--sans); font-size:var(--fs-note); color:var(--panel-ink); margin:16px 0 0; line-height:1.6}

.homelinks{display:flex; gap:12px; flex-wrap:wrap; justify-content:center}
.lnkbtn{
  font-family:var(--pixel); font-size:var(--fs-2xs); text-decoration:none;
  color:#2b2b2b; background:#AFAFAF; padding:14px 22px;
  box-shadow: inset 2px 2px 0 0 #FFFFFF, inset -2px -2px 0 0 #6a6a6a;
}
.lnkbtn:hover{background:#BEBEBE}

/* ---------------------------------------------------------------------------- panel spacing
   Panels are separate ideas. At 14px they read as one long column; at 44px each one is its own
   thing. Set here rather than per page, and never as an inline style - it was repeated on every
   panel and had to be changed in a dozen places. */
.wrap > .chest + .chest,
.wrap > .tiergrid,
.tiergrid + .chest{margin-top:44px}

/* A square that content does not fit into stops being a square rather than hiding what is in it.
   aspect-ratio sets the preferred shape; min-height lets it grow if the perks need the room. */
.chest.tier{min-height:max-content; overflow:visible}
