/* tool-theme.css — normalise ported widgets onto the site's tokens.
 *
 * Hash re-cut 2026-07-31. The seed-card contrast fix below deployed correctly and reached
 * nobody: mctoolkit.net answered /css/tool-theme.css?v=3742fk with the PREVIOUS file —
 * 13,200 bytes, cf-cache-status HIT, Age climbing — while the same URL with one extra query
 * param, and the same URL on the *.pages.dev host, both returned the right 14,577. A request
 * landed in the window between the HTML flip and the asset going live, and
 * `max-age=31536000, immutable` then pinned that mismatch under a hash that had never served
 * anything before. Only changing the file again clears it, which is what this note does.
 * Second time this has happened here; see the same paragraph in CLAUDE.md.
 *
 * The 75 tools came from mc-mod's WordPress theme, which is LIGHT. Their stylesheets
 * assume white panels, dark text, grey borders and browser-default form controls. Dropped
 * onto a near-black page that produces exactly what it sounds like: white inputs, dark
 * labels on dark ground, glaring tables.
 *
 * Editing 75 stylesheets by hand would be slow and would break on the next port. This
 * file is loaded AFTER each widget's own CSS and re-skins whatever it finds, scoped to
 * #tool-root so it can never touch the site chrome. Specificity comes from the id, so
 * plain class rules inside a widget lose without needing !important on everything.
 */

/* ---------- surfaces ---------- */
#tool-root, #tool-root * { border-color: var(--line); }

#tool-root :is(section, fieldset, .panel, .card, .box, [class*="-section"], [class*="-panel"], [class*="-card"], [class*="-box"]) {
  background: var(--surface);
  border-color: var(--line);
  border-radius: var(--radius);
  color: var(--fg);
}

#tool-root :is(h1, h2, h3, h4, legend, [class*="-h"], [class*="-title"]) { color: var(--fg); }
#tool-root :is(p, li, span, dd, dt, label, small) { color: inherit; }
#tool-root :is(.hint, .note, .desc, [class*="-hint"], [class*="-note"], [class*="-desc"], small) { color: var(--fg-muted); }
#tool-root a { color: var(--grass-bright); }

/* ---------- form controls: the loudest offenders ---------- */
/* background-COLOR, not the `background` shorthand. The shorthand resets background-image to
 * none, and this rule outranks the `#tool-root select` one below it — `:is()` takes the
 * specificity of its most specific argument, so the attribute selectors in the list make this
 * (1,1,1) against that rule's (1,0,1). The arrow drawn there never painted, on any select, in
 * any ported tool, in either theme, while `appearance: none` had already removed the native
 * one. A dropdown that looks exactly like a text input reads as a styling choice, which is
 * why it survived: nothing about it looks broken until you try to find the control. */
#tool-root :is(input[type="text"], input[type="number"], input[type="search"], input[type="url"],
               input[type="email"], input[type="password"], select, textarea) {
  background-color: var(--surface-2);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 8px 10px;
  font: inherit;
  font-size: 14px;
  outline: none;
  box-shadow: none;
  -webkit-appearance: none;
  appearance: none;
}
#tool-root select {
  /* the native arrow disappears once appearance is stripped */
  background-image: linear-gradient(45deg, transparent 50%, var(--fg-muted) 50%),
                    linear-gradient(135deg, var(--fg-muted) 50%, transparent 50%);
  background-position: calc(100% - 15px) 52%, calc(100% - 10px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 28px;
}
#tool-root :is(input, select, textarea):focus {
  border-color: var(--grass);
  box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.18);
}
#tool-root ::placeholder { color: var(--fg-dim); opacity: 1; }
#tool-root :is(input[type="checkbox"], input[type="radio"], input[type="range"], input[type="color"]) {
  accent-color: var(--grass);
  -webkit-appearance: auto;
  appearance: auto;
}
#tool-root input[type="color"] { background: var(--surface-2); border: 1px solid var(--line); padding: 2px; }

/* File inputs keep the browser's grey "Choose file / No file chosen" chrome unless the
 * button pseudo-element is styled too, which reads as a light-theme leftover on this
 * page. The field itself is styled by the rule above; this is the button inside it. */
#tool-root input[type="file"] {
  padding: 6px 8px;
  font-size: 13px;
  color: var(--fg-muted);
  cursor: pointer;
}
#tool-root input[type="file"]::file-selector-button {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  margin-right: 10px;
  padding: 6px 11px;
  color: var(--fg);
  background: var(--surface-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 120ms, color 120ms;
}
#tool-root input[type="file"]::file-selector-button:hover {
  border-color: var(--line-strong);
  color: var(--grass-bright);
}

/* ---------- buttons ---------- */
#tool-root button:not(.btn):not([class*="tab"]) {
  background: var(--surface-2);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 8px 13px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 120ms, background 120ms;
}
#tool-root button:not(.btn):not([class*="tab"]):hover {
  border-color: var(--line-strong);
  background: var(--surface-3);
}
/* whatever a widget calls its primary action */
#tool-root :is(button[class*="primary"], button[class*="copy"], button[class*="generate"], button[type="submit"]) {
  background: var(--grass-deep);
  border-color: var(--line-strong);
  color: var(--grass-bright);
}

/* ---------- tabs, chips, pills ---------- */
#tool-root :is([class*="tab"]:not([class*="table"]), [class*="chip"], [class*="pill"]) {
  background: var(--surface-2);
  color: var(--fg-muted);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
#tool-root :is([class*="tab"][class*="active"], [class*="chip"][class*="active"],
               [class*="tab"][aria-selected="true"], [class*="pill"][class*="active"]) {
  background: var(--grass-deep);
  border-color: var(--line-strong);
  color: var(--grass-bright);
}

/* ---------- tables ---------- */
#tool-root table { border-collapse: collapse; width: 100%; font-size: 14px; color: var(--fg); }
#tool-root :is(th, td) {
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 9px 12px;
  text-align: left;
  background: transparent;
}
#tool-root th {
  background: var(--surface-2);
  color: var(--fg-dim);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
}
#tool-root tbody tr:hover td { background: var(--surface-2); }
/* zebra stripes from the light theme read as glare here */
#tool-root tbody tr:nth-child(even) td { background: transparent; }

/* ---------- code, output, pre ---------- */
#tool-root :is(pre, code, kbd, samp, [class*="-out"], [class*="output"], [class*="result"]) {
  font-family: var(--font-mono);
  background: var(--surface-2);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
#tool-root :is(pre, [class*="-out"], [class*="output"]) { padding: 12px 14px; overflow-x: auto; }
#tool-root code { padding: 1px 5px; font-size: 0.92em; }
#tool-root pre code { border: 0; padding: 0; background: none; }

/* ---------- imagery ---------- */
/* Minecraft sprites are 16px art; any widget scaling one must not smooth it. */
#tool-root img[src*="/textures/"], #tool-root img[src*="/icons/"],
#tool-root img[width="16"], #tool-root canvas { image-rendering: pixelated; }

/* ---------- hairline dividers ---------- */
#tool-root hr { border: 0; border-top: 1px solid var(--line); margin: 16px 0; }

/* Light theme: undo the assumptions above rather than fighting them twice. */
/* background-color for the same reason as above — this one outranks it again, so using the
 * shorthand here would put the arrow back to none on the light theme only. */
:root[data-theme="light"] #tool-root :is(input, select, textarea) { background-color: #fff; }
:root[data-theme="light"] #tool-root th { background: var(--surface-2); }

/* ---------- light theme: widgets that carry a dark palette of their own ---------- */
/*
 * Two ways a ported widget breaks on the light theme, both measured rather than eyeballed
 * — at 1.12:1 the text is still *there*, it is just the same colour as the button.
 *
 * 1. seed-map-3d redefines the SITE's token names on #tool-root itself:
 *      #tool-root{--bg:#121286;--fg:#e6e6ec;--muted:#8c8c98;--panel:rgba(28,28,38,.92);…}
 *    So inside that widget --fg is always its dark-theme near-white, whatever the page
 *    theme is. The rules above then paint the button with --surface-2, which the widget
 *    does NOT redefine, so it resolves to the site's LIGHT value: near-white text on a
 *    near-white button, 1.12:1 against a required 4.5:1, on every control it owns.
 *
 *    Remapping the site's surface tokens onto the widget's own palette fixes all of them
 *    at once and keeps the app looking the way upstream drew it — which is right, because
 *    it is a full-viewport 3D world viewer with floating panels and is dark by design.
 *    Restating its individual rules here instead would be dead CSS the day it re-ports.
 *
 *    Only seed-map-3d does this. The 2D seed map ships a fully namespaced palette
 *    (--text-primary, --bg-surface, --accent) that collides with nothing, and it is a
 *    light-themed app already. Checked across the whole tools/ tree, not assumed.
 */
:root[data-theme="light"] #tool-root[data-tool="seed-map-3d"] {
  --surface:     var(--panel);
  --surface-2:   var(--bg-2);
  --surface-3:   var(--bg-2);
  --fg-muted:    var(--muted);
  --fg-dim:      var(--muted);
  --line:        var(--border);
  --line-strong: var(--accent);
}

/*
 *    Its form controls need saying twice, because the remap above cannot reach them: the
 *    light-theme rule further up hardcodes `background-color: #fff` on input/select/textarea
 *    rather than going through --surface-2, and it outranks the widget's own rule. So the
 *    field went white while `color: var(--fg)` stayed the widget's near-white — the seed, the
 *    edition and the dimension all rendered at 1.24:1, measured. The value was still painted,
 *    in the same colour as the box behind it.
 *
 *    Fixed by naming the colour rather than the background. --site-fg is --fg under a name no
 *    ported widget has redefined (see tokens.css); asking for --fg here is exactly what
 *    failed. That leaves the field the site's white with the site's ink on it, which is right
 *    now that seed-map-3d.js lifts this card out of the stage and into the toolbar under the
 *    map — it is site chrome down there, not a dark panel floating on the world.
 *
 *    Nothing rescued it at runtime either. theme-normalize.js skipped form controls outright,
 *    and even unskipped it could not see their text: it reads child TEXT NODES, and an
 *    <input> keeps its value in a property. Fixed there too, so the next one is caught.
 */
:root[data-theme="light"] #tool-root[data-tool="seed-map-3d"] :is(input, select, textarea) {
  color: var(--site-fg);
}

/*
 * 2. 3d-skin-editor paints its toolbar icons with `filter: brightness(0) invert(1)`
 *    (vendor/widget-full/widget-full.css) — a dark-theme trick that forces any artwork to
 *    pure white regardless of its own fill. On a light button the icon simply disappears.
 *    Dropping the invert leaves brightness(0), which forces the same artwork to pure black.
 *
 *    Worth remembering how this one hid: a contrast scan that reads `fill` and `stroke`
 *    reports these icons as fine, because the filter is what decides the painted colour and
 *    neither property changes. It was found by dumping computed styles, not by scanning.
 */
:root[data-theme="light"] #tool-root :is(.tool-icon, .action-icon-img) {
  filter: brightness(0);
}

/* ---------- right-to-left pages ---------- */
/* Widget interiors are English technical UIs: file extensions, coordinates, seeds, block
 * counts, command names. Mirrored into an RTL page, the neutral characters in those runs
 * take the paragraph direction and land on the wrong side — the seed map printed -2,048 as
 * "2,048-" and the schematic viewer printed .schem as "schem.". It is the same failure as the
 * bidi mark on /give, one level up: there the fix was per-string, here it is per-widget.
 *
 * An LTR technical panel inside an RTL page is the normal arrangement — the same as a code
 * block or a terminal transcript. The page chrome around the widget (h1, description, nav,
 * footer, the cross-promo block) sits outside #tool-root and stays right-to-left. */
[dir="rtl"] #tool-root { direction: ltr; }

/* ---------- inline sprites beside printed ids ---------- */
/*
 * js/id-sprites.js prepends one of these wherever a tool prints a bare Minecraft id. The
 * reference browsers listed 95 ids and shipped no images at all; the atlas built for the
 * /give picker already had every one of them.
 *
 * Same two rules the picker follows: 16px art must never be smoothed, and it is scaled with
 * a transform rather than background-size, because scaling the background scales the
 * POSITION too and every sprite would point at its neighbour. The negative margins claw
 * back the space the 2x transform paints outside the 16px box, so a decorated row is the
 * same height as an undecorated one.
 */
#tool-root .mc-sprite {
  display: inline-block;
  width: 16px; height: 16px;
  margin: -4px 10px -4px 4px;
  vertical-align: middle;
  background-image: var(--mc-atlas);
  background-repeat: no-repeat;
  image-rendering: pixelated;
  transform: scale(1.5);
  flex: none;
}
[dir="rtl"] #tool-root .mc-sprite { margin: -4px 4px -4px 10px; }

/*
 * The seed tools stop leaving a 449px band of dead app-colour under the map.
 *
 * Both seedmap pages ship the upstream app's PAGE-level sizing, because the port remaps
 * :root and html/body onto #tool-root — so #tool-root inherits `min-height: 100%`, which was
 * correct when it meant "fill the window" and is wrong when it means "fill the article
 * column". Measured on the live site: the stage sized itself to the viewport at 747px while
 * #tool-root stretched to 1196, and the app painted its own background across the 449px
 * difference. That is the flat blue under the 2D map and the flat white under the 3D one —
 * not a stylesheet failing to load, which is what it looks like.
 *
 * It also pushed everything after the tool down: the cross-promo section started at y=1397
 * instead of y=947, below a screenful of nothing.
 *
 * Matched on `:has(> .sm-stage)` rather than on the two slugs, so it keeps working if the
 * pages are renamed and cannot silently apply to a tool that is not this app. Verified by
 * measurement, not by eye: #tool-root 1196 -> 747, exactly its stage, gap 0.
 *
 * NOT fixed in the vendor CSS on purpose — vendor/seedmap is regenerated by
 * scripts/port-seedmap.mjs and any edit there is overwritten on the next re-port.
 */
#tool-root:has(> .sm-stage) {
  min-height: 0;
  height: auto;
}

/* ---------- shared VERSION + SNAPSHOTS bar ---------- */
/*
 * js/version-select.js mounts this above the widget on the tools whose minecraftmaps
 * counterpart carries a version selector. Scoped to #tool-root like everything else here, so
 * a widget's own class rules cannot outrank it.
 */
#tool-root .mct-vbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}
#tool-root .mct-vbar-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
#tool-root .mct-vbar-select { flex: 0 0 auto; min-width: 10ch; width: auto; }
/* aria-pressed IS the state — styling from it keeps one source of truth rather than a class
   the script would also have to keep in step. */
#tool-root .mct-vbar-snap {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
#tool-root .mct-vbar-snap[aria-pressed="true"] { border-color: var(--grass); color: var(--grass); }
/* The note only appears on legacy versions, so it must not reserve space when empty. */
#tool-root .mct-vbar-note:not(:empty) {
  flex: 1 1 20ch;
  min-width: 0;
  font-size: 12px;
  color: var(--fg-muted);
}

/* ---------- horizontal overflow ---------- *
 *
 * Measured, not guessed: scripts/audit-rendered.mjs --overflow laid out all 87 tool pages at 360,
 * 768 and 1440 and compared scrollWidth against clientWidth, walking ancestors for overflow-x
 * first so a deliberately clipped canvas is not counted. 404 findings on 20 tools; on 11 of them
 * the BODY was wider than the viewport, which is the one a reader feels — the whole page slides
 * sideways. Worst was /armor-trim-3d-viewer at 360px: body 936 against a 360 viewport.
 *
 * Only genuinely universal causes are handled here. The rest are fixed in the widget that owns
 * them, because a global rule broad enough to collapse an arbitrary fixed-column grid is also
 * broad enough to wreck the 67 tools that were already fine. CLAUDE.md's note about class-name
 * matching never being finishable cuts the other way too: a layout rule matched on nothing in
 * particular is not a fix, it is a second bug waiting for a different page.
 *
 * The first run of that sweep reported "87 pages rendered, 0 findings, 261 elements inspected —
 * clean". Three elements per page: every URL was /en/<slug>, English has no path prefix, and the
 * sweep measured the 404 page 87 times. Worth remembering before trusting a clean overflow run.
 */

/* A canvas is a replaced element with intrinsic dimensions, so it ignores its container and
 * paints at its attribute size. avatar-maker's #am-gl is 512x512 inside a 294px column, which is
 * where /avatar-maker's 185px of body overflow came from. `height: auto` is what keeps the aspect
 * ratio while `max-width` clamps it — without it the canvas squashes to 294x512. Neither touches
 * the backing store, so nothing is drawn at lower resolution. */
#tool-root canvas { max-width: 100%; height: auto; }

/* A select is sized by its widest option, which no container width can override.
 * dialog-input-builder's action-type select measured 279px inside a 144px cell.
 *
 * `min-width: 0` is the half that matters as a FLEX ITEM: min-width defaults to auto there, which
 * resolves to the select's longest option and refuses to shrink past it. hud-font-pack-tools' 14px
 * of overflow was one target-version select in a nowrap label doing exactly that.
 *
 * .mct-vbar-select above keeps its `min-width: 10ch` — a class beats an element selector at equal
 * id count, so that rule still wins and the version bar is unaffected. Checked, not assumed. */
#tool-root select { max-width: 100%; min-width: 0; }
/* The label around it may wrap instead of forcing its row wide. `flex-direction: row !important`
 * in the widget's own CSS is untouched: wrapping is a different property. */
#tool-root :is([class*="toggle-row"], [class*="-field-row"]) label { flex-wrap: wrap; }

/* A long unbreakable run — a resource-pack path, a registry id, a base64 blob — forces its track
 * wider than the container, and a flex or grid child defaults to min-width:auto, which refuses to
 * shrink below that content. Both halves are needed: min-width:0 lets the box shrink,
 * overflow-wrap lets the text break. hud-font-pack-tools' check rows measured 449px of
 * "assets/minecraft/font/..." inside 306px for exactly this reason. */
#tool-root :is([class*="-row"], [class*="-check"], [class*="-item"], [class*="-field"]) > * { min-width: 0; }
#tool-root :is([class*="-path"], [class*="-id"], [class*="-key"], [class*="-name"]) { overflow-wrap: anywhere; }

/* A fieldset's default `min-inline-size: min-content` is not overridable by width alone, so it
 * refuses to shrink below its widest child and pushes the page out. resource-pack-studio's
 * .rps-fieldset measured 404px inside a 328px column with `min-width: min-content` computed. This
 * is the one legacy quirk in this section that has nothing to do with the ports. */
#tool-root fieldset { min-width: 0; }

/* `anywhere`, not `break-word`. Only `anywhere` affects MIN-CONTENT sizing, which is the whole
 * point: a grid track or flex item is sized by its content's minimum, so a path like
 * `data/<namespace>/enchantment/<name>.json` sets a 359px floor no container width can beat.
 * custom-enchant-trim-builder and skin-viewer both overflowed on one such string. With spaces
 * available nothing breaks differently, so ordinary prose is unaffected. */
#tool-root :is(p, span, label, td, th, li, dd, dt, code, [class*="-label"], [class*="-note"],
               [class*="-hint"], [class*="-json"], [class*="-path"], [class*="-cmd"]) {
  overflow-wrap: anywhere;
}

/* A data table cannot shrink below its columns, so the WRAPPER has to scroll. Same treatment
 * `pre` already gets further up this file, and for the same reason: the content is a grid of
 * values that stops being readable if it reflows. spawn-proofing-calculator's table measured
 * 413px inside 328px. Naming here follows the convention the widgets already use; a table with no
 * wrapper at all is left alone, since adding scroll to the table box itself would need
 * `display: block` and that destroys column alignment. */
#tool-root :is([class*="table-wrap"], [class*="table-wrapper"], [class*="table-scroll"],
               [class*="tablewrap"], [class*="-table-outer"]) {
  overflow-x: auto;
}

/* Flex and grid items default to min-width:auto and refuse to shrink below their content. The
 * earlier rule above covers items in a container whose class contains -row/-check/-item/-field;
 * this covers the rest, where the container is named something the list does not predict —
 * hud-font-pack-tools' toggle rows nest a bare LABEL that then holds the long text. CLAUDE.md's
 * note that a class-name list can never be finished is why this stops guessing at container names
 * and states the property on the items instead.
 *
 * `span` is deliberately NOT in this list. Including it collapsed item-picker's glyph span to 2px
 * inside its button on five tools — a regression the next sweep caught, since the glyph then
 * painted 14px outside a 2px box. An inline span is virtually never the flex item that needs to
 * shrink; the block-level container around it is. */
#tool-root :is(label, p, div, section, fieldset) { min-width: 0; }

/* 3d-skin-editor's body-part picker is a humanoid FIGURE laid out on a fixed 20px grid — head
 * spanning columns 2-3, arms at 1 and 4, legs at 2 and 3. Its chips overflowed their cells by 8px
 * at every viewport, and the cause is this stylesheet: the generic button rule further up applies
 * `padding: 8px 13px`, which gives a chip a 26px minimum inside a 20px track.
 *
 * This is the trap the widget doctrine already records — `button:not(.btn):not([class*="tab"])`
 * got there first and made the schematic viewer's selected tab invisible in both themes. Same
 * rule, different victim. The chips carry their own sizing from the figure grid, so they want no
 * padding from here at all.
 *
 * Fixed from CSS rather than in vendor/widget-full/widget-full.css, which port-skin-tools
 * overwrites and which the doctrine flags as repeatedly mis-patched.
 *
 * `button.chip-button`, not `.chip-button`. The first attempt lost on specificity and the way it
 * lost is worth writing down, because `min-width` and `overflow` from the same block DID apply
 * while `padding` did not — which reads like a cascade mystery until the arithmetic is done.
 * `:is()` takes the specificity of its most specific argument, so
 * `#tool-root :is(.part-figure-main, …) .chip-button` is (1,2,0), while the generic button rule
 * `#tool-root button:not(.btn):not([class*="tab"])` is (1,2,1) — one extra element selector. It
 * only ever set padding, so only padding was contested. Naming the element here ties at (1,2,1)
 * and this block is later in the file. Measured after: 28px chips in 20px tracks became 20px,
 * scrollWidth 124 -> 116. */
#tool-root :is(.part-figure-main, .part-figure-side) button.chip-button {
  padding: 0;
  min-width: 0;
  overflow: hidden;
}
