/* ==========================================================================
   Tool pages v2 — the shell, and the one stylesheet the tools share.

   THE SHARED HALF IS NOT A STYLE CHOICE, IT IS A DEDUPLICATION. The sixty-four
   classic tool views carry 16,475 lines of inline <style> between them, and it
   is one stylesheet copied sixty-odd times and then drifted: `.btn-primary`
   appears in 62 of them, `.form-input` in 55, `.tool-card` in 55, `.badge-*`
   and `.toast-*` in 52-53, `#map` in 52. Every copy is written in hard-coded
   Tailwind slate hexes — #0f172a bodies, #1e293b cards, #94a3b8 labels — which
   is exactly what goes grey-on-grey under the six light palettes.

   So that vocabulary is declared ONCE, here, in theme tokens.
   scripts/tools-v2/build-tool-css.js drops those selectors when it converts a
   tool's own <style>, and what is left lands in public/css/tools-v2/<slug>.css.

   Everything here is scoped to `.tpv2` (the page) or `.tpv2-body` (the wrapper
   round the tool's own markup). The scope is load-bearing, not tidiness: this
   sheet loads AFTER Skynet UI, so a bare `input[type="text"]` here would tie
   with `.sk-input` on specificity and win on source order — the mistake
   header-v2.css documents, which stripped the fill off every primary submit
   button on the site.
   ========================================================================== */

/* --- page frame ---------------------------------------------------------- */

.tpv2 {
  /* A label drawn on a solid accent fill. scripts/tools-v2/theme-inline-styles.js
     wraps an inline `color` in this when the same attribute paints a solid
     accent — an inline colour beats any stylesheet, so the pairing has to be
     inline too, and a named token keeps the page's own colour as the fallback
     so the pass stays re-runnable. `npm run test:themes` holds white on every
     accent to >= 4.5:1 in all twelve palettes. */
  --sk-text-on-accent: #fff;
  background: var(--sk-bg);
  color: var(--sk-text);
  font-family: var(--sk-font);
  min-height: 100vh;
}

.tpv2-main {
  max-width: 1400px;
  margin: 0 auto;
  padding: 1.25rem 1rem 3rem;
}

/* --- the hero every tool page gets from the catalogue -------------------- */

.tpv2-hero {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.tpv2-hero-icon {
  flex: 0 0 auto;
  width: 3rem;
  height: 3rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 0.9rem;
  background: var(--sk-primary-soft);
  border: 1px solid var(--sk-border);
  color: var(--sk-primary-text);
  font-size: 1.25rem;
}

.tpv2-hero-text {
  flex: 1 1 18rem;
  min-width: 0;
}

.tpv2-hero-text h1 {
  margin: 0;
  font-size: clamp(1.35rem, 1.1rem + 1.1vw, 1.9rem);
  line-height: 1.2;
  color: var(--sk-text);
  overflow-wrap: anywhere;
}

.tpv2-cat {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.4rem;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sk-muted);
}

.tpv2-lede {
  margin: 0.45rem 0 0;
  color: var(--sk-text-2);
  font-size: 0.95rem;
  line-height: 1.55;
  max-width: 62ch;
  overflow-wrap: anywhere;
}

/* A tool nobody has written a description for says so, rather than being given
   a sentence attributed to its author. Fifteen of the sixty-four are in this
   state; /tools/v2 counts them in a footnote. */
.tpv2-lede-none {
  margin: 0.45rem 0 0;
  color: var(--sk-muted);
  font-size: 0.9rem;
  font-style: italic;
}

.tpv2-hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

/* --- the tool's own markup ----------------------------------------------- */

/* BORDER-BOX, because the tool markup was written for Tailwind's Preflight and
   Skynet UI does not set it on unclassed elements. Without it, `width: 100%`
   plus `p-2` plus a 1px border is wider than its container by the padding and
   the border — measured on /tools/gameplay-generator/v2, where an input, its
   row and the card around it all computed `content-box` and the row ran 4px
   past the card at 320px. Every one of these pages carries its own padding
   classes, so this is the difference between "as the classic page renders it"
   and "a few pixels wider at every level".
   SCOPED TO THE TOOL'S OWN MARKUP: the shared header, the footer and Skynet
   UI's own components are laid out by the framework and are not this rule's
   business — the same reason nothing else in this file is unscoped. */
.tpv2-body,
.tpv2-body *,
.tpv2-body *::before,
.tpv2-body *::after {
  box-sizing: border-box;
}

.tpv2-body {
  min-width: 0;
}

/* Every controller on these pages shows and hides with Tailwind's `.hidden`,
   and Skynet UI declares `.hidden` BEFORE its component `display` rules — so
   a component that sets its own `display` beats it. Both guards, as
   /killfeed/setup/v2 needs them. */
.tpv2 .hidden,
.tpv2 [hidden] {
  display: none !important;
}

/* A grid or flex ITEM defaults to `min-width: auto`, which is its min-content
   width and not zero, and `.sk-card` is `overflow: hidden` — so a wide table
   or a long classname sizes the track and the card CLIPS the overflow rather
   than scrolling it, with documentElement.scrollWidth never moving. This is
   the /status/v2 bug; npm run audit:mobile is what sees it. */
.tpv2-body > *,
.tpv2-body .grid > *,
.tpv2-body .flex > * {
  min-width: 0;
}

/* THE OTHER HALF OF THAT RULE. Letting every grid and flex item shrink past
   its min-content width is what stops a wide child sizing the track — and it
   is also what lets the TEXT inside a narrow cell run out of its box, which
   `.sk-card` then clips. A 96px cell holding `• zombie_territories.xml` is
   35px of filename outside the card with every box measuring correctly.
   So the two travel together: anything allowed to shrink to nothing is
   allowed to wrap anywhere. `anywhere` rather than `break-word` because only
   `anywhere` also shrinks min-content, which is the property being relied on.
   `npm run audit:mobile` is what sees both halves. */
.tpv2-body .grid > *,
.tpv2-body .flex > *,
.tpv2-body li,
.tpv2-body button,
.tpv2-body .btn-primary,
.tpv2-body .btn-secondary {
  overflow-wrap: anywhere;
}

/* The two things that are genuinely wider than a phone scroll themselves. */
.tpv2-body table {
  width: 100%;
}
.tpv2-body .table-wrap,
.tpv2-body .overflow-x-auto {
  overflow-x: auto;
}

/* A TWELVE-COLUMN GRID IS A TABLE MADE OF DIVS, and three tools build their
   results that way. Its floor is not the text but the ELEVEN GAPS: `gap-4` is
   176px of a 320px screen before a word is laid out, which is what pushed
   /tools/event-conflict-check/v2 10px off the screen at 320 and 2px at 414.
   Tightening the gap on a phone recovers 88px and keeps the columns aligned
   with their header, which collapsing them to fewer columns would not. */
@media (max-width: 640px) {
  .tpv2-body .grid-cols-12 {
    gap: 0.5rem;
  }
  /* Even at the tighter gap a `col-span-1` cell is 10px wide at 320, and
     "Distance" needs 70. The column headings wrap instead of being clipped:
     the classic page lets the whole document scroll sideways here, so the text
     was reachable, and `.sk-card`'s `overflow: hidden` is what turns that into
     content actually lost. A taller header is the cheap half of that trade.
     `anywhere`, not `break-word` — only `anywhere` also shrinks min-content
     width, which is the whole point (the /changelogs/v2 lesson). */
  .tpv2-body [class*="col-span-"] {
    overflow-wrap: anywhere;
  }
  /* A row of actions squeezed below its own min-content is the other half:
     let it take a second line rather than clip its labels. */
  .tpv2-body .flex.gap-2,
  .tpv2-body .flex.gap-3,
  .tpv2-body .flex.gap-4,
  .tpv2-body .flex.space-x-2,
  .tpv2-body .flex.space-x-3 {
    flex-wrap: wrap;
  }
}

/* A DayZ config name has no spaces in it — `CleanupLifetimeDeadInfected` is
   one word 27 characters long — and several controllers inject one into a flex
   row. `min-width: 0` above lets the box shrink; only `anywhere` also shrinks
   its MIN-CONTENT width, which is what stops the word sizing the row and
   getting clipped by the card. (`break-word` does not; that is the
   /changelogs/v2 lesson.) Numerals are excluded: a wrap inside a figure is
   worse than a wide one. */
.tpv2-body .variable-row,
.tpv2-body .variable-header,
.tpv2-body [class*="-row"] > *,
.tpv2-body label,
.tpv2-body td,
.tpv2-body th {
  overflow-wrap: anywhere;
}

/* --- a label on an inline accent fill --------------------------------------
   The tool markup paints a lot of its buttons from a STYLE ATTRIBUTE, which no
   stylesheet can win against, so the compat generator's `.bg-*.text-white`
   pairs never fire on them. scripts/tools-v2/theme-inline-styles.js resolves
   those fills to `var(--sk-…, #literal)`; this is the other half — where the
   fill is a solid accent, the label is literal white in both modes, which
   `npm run test:themes` guarantees at >= 4.5:1 for every palette.
   It applies to everything on that fill, not only to `.text-white`: the label
   that measured 1.08:1 on /tools/contaminated-zone/v2 was a `.form-label`, and
   enumerating the classes that might sit on an accent is a list that is always
   one page out of date. `text-white` alone still resolves to `--sk-text`,
   because white on a SURFACE is the bug that rule exists to prevent, and the
   `-soft` tints do not match here — the comma after the family name is what
   tells `var(--sk-warning,` from `var(--sk-warning-soft,`.
   THE PROPERTY NAME IS PART OF THE MATCH. Without it, `[style*="var(--sk-warning,"]`
   also matched a panel whose amber was only a 1px BORDER, and every
   `.text-white` inside it went literal white — including a grey button, which
   then measured 1.63:1 on the six light palettes. */
.tpv2-body [style*="background-color: var(--sk-primary,"],
.tpv2-body [style*="background-color: var(--sk-primary,"] *,
.tpv2-body [style*="background: var(--sk-primary,"],
.tpv2-body [style*="background: var(--sk-primary,"] *,
.tpv2-body [style*="background-color: var(--sk-success,"],
.tpv2-body [style*="background-color: var(--sk-success,"] *,
.tpv2-body [style*="background: var(--sk-success,"],
.tpv2-body [style*="background: var(--sk-success,"] *,
.tpv2-body [style*="background-color: var(--sk-danger,"],
.tpv2-body [style*="background-color: var(--sk-danger,"] *,
.tpv2-body [style*="background: var(--sk-danger,"],
.tpv2-body [style*="background: var(--sk-danger,"] *,
.tpv2-body [style*="background-color: var(--sk-warning,"],
.tpv2-body [style*="background-color: var(--sk-warning,"] *,
.tpv2-body [style*="background: var(--sk-warning,"],
.tpv2-body [style*="background: var(--sk-warning,"] *,
.tpv2-body [style*="background-color: var(--sk-info,"],
.tpv2-body [style*="background-color: var(--sk-info,"] *,
.tpv2-body [style*="background: var(--sk-info,"],
.tpv2-body [style*="background: var(--sk-info,"] * {
  color: #fff;
}

/* --- the shared vocabulary ----------------------------------------------- */

.tpv2-body h1,
.tpv2-body h2,
.tpv2-body h3,
.tpv2-body h4,
.tpv2-body h5 {
  color: var(--sk-text);
}

.tpv2-body label {
  display: block;
  margin-bottom: 0.25rem;
  color: var(--sk-text-2);
  font-size: 0.875rem;
}

.tpv2-body .tool-card,
.tpv2-body .card,
.tpv2-body .enhanced-card {
  background: var(--sk-surface);
  border: 1px solid var(--sk-border);
  border-radius: 0.9rem;
  padding: 1.25rem;
}

.tpv2-body .tool-card:hover,
.tpv2-body .enhanced-card:hover {
  border-color: var(--sk-border-2);
}

.tpv2-body .btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  background: var(--sk-primary);
  color: #fff;
  border: 1px solid transparent;
  border-radius: 0.6rem;
  padding: 0.6rem 1.25rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
/* White on `--sk-primary` is guaranteed >= 4.5:1 by npm run test:themes, in
   every one of the twelve palettes. That is why the label is a literal here
   and a token everywhere else. */
.tpv2-body .btn-primary:hover:not(:disabled) { background: var(--sk-primary-hover, var(--sk-primary)); }
.tpv2-body .btn-primary:disabled { opacity: 0.55; cursor: not-allowed; }

.tpv2-body .btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  background: var(--sk-surface-2);
  color: var(--sk-text);
  border: 1px solid var(--sk-border-2);
  border-radius: 0.6rem;
  padding: 0.6rem 1.25rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.tpv2-body .btn-secondary:hover:not(:disabled) { background: var(--sk-border); }
.tpv2-body .btn-secondary:disabled { opacity: 0.55; cursor: not-allowed; }

/* --- the `*-enhanced` button names ---------------------------------------
   /tools/spawnable-types writes `btn-enhanced btn-primary-enhanced`, and the
   rules for those live in /css/dashboard-enhanced.css — a sheet the classic
   tool pages load and this one deliberately does not, for the same reason
   tools-dark-overrides.css is left behind. Without them the page's main
   actions would be unstyled <button>s.

   ALIASED, not restated: the same declarations already exist three times in
   this repository (dashboard-enhanced.css, dashboard-v2.css, maps-v2.css) and
   a fourth copy is the one that drifts. `btn-enhanced` is the shared shape and
   the two variants are the two fills this file already declares. */

.tpv2-body .btn-enhanced {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border-radius: 0.6rem;
  padding: 0.6rem 1.25rem;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background-color 0.15s ease;
}
/* A label on `.btn-enhanced` where the markup gives it an accent fill and no
   text class at all: /tools/spawnable-types/v2's "Confirm" is
   `confirm-btn btn-enhanced bg-red-600`, so it fell through to the user
   agent's black and measured 4.35:1 on the danger fill.
   `.btn-secondary-enhanced` below sets its own and wins on source order. */
.tpv2-body .btn-enhanced {
  color: var(--sk-text-on-accent, #fff);
}

.tpv2-body .btn-primary-enhanced {
  background: var(--sk-primary);
  color: var(--sk-text-on-accent, #fff);
}
.tpv2-body .btn-primary-enhanced:hover:not(:disabled) {
  background: var(--sk-primary-hover, var(--sk-primary));
}
.tpv2-body .btn-secondary-enhanced {
  background: var(--sk-surface-2);
  color: var(--sk-text);
  border-color: var(--sk-border-2);
}
.tpv2-body .btn-secondary-enhanced:hover:not(:disabled) { background: var(--sk-border); }
.tpv2-body .btn-enhanced:disabled { opacity: 0.55; cursor: not-allowed; }

.tpv2-body .form-input,
.tpv2-body .form-select,
.tpv2-body input[type="text"],
.tpv2-body input[type="number"],
.tpv2-body input[type="email"],
.tpv2-body input[type="password"],
.tpv2-body input[type="search"],
.tpv2-body select,
.tpv2-body textarea {
  width: 100%;
  background: var(--sk-bg);
  color: var(--sk-text);
  border: 1px solid var(--sk-border-2);
  border-radius: 0.55rem;
  padding: 0.55rem 0.75rem;
  font: inherit;
}

.tpv2-body .form-input:focus,
.tpv2-body .form-select:focus,
.tpv2-body input:focus,
.tpv2-body select:focus,
.tpv2-body textarea:focus {
  outline: none;
  border-color: var(--sk-primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--sk-primary) 25%, transparent);
}

.tpv2-body .form-label {
  display: block;
  margin-bottom: 0.3rem;
  color: var(--sk-text-2);
  font-size: 0.85rem;
  font-weight: 600;
}

/* An inline <code> token must not size the card. `overflow-wrap: anywhere` and
   not `break-word`: only `anywhere` also shrinks min-content width, which is
   what stops one path like `./custom/file1.json` setting the width of the flex
   item it sits in — the call /changelogs/v2 made for long URLs.
   audit:mobile caught /tools/gameplay-generator/v2 at 65px past its card. */
.tpv2-body code,
.tpv2-body small,
.tpv2-body p {
  overflow-wrap: anywhere;
}

.tpv2-body .code-block,
.tpv2-body pre {
  background: var(--sk-bg);
  border: 1px solid var(--sk-border);
  border-radius: 0.7rem;
  padding: 1rem;
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--sk-text);
  white-space: pre-wrap;
  overflow-x: auto;
}

.tpv2-body .badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.6rem;
  border-radius: 9999px;
  font-size: 0.75rem;
  font-weight: 600;
  border: 1px solid var(--sk-border-2);
  background: var(--sk-surface-2);
  color: var(--sk-text);
}
/* A soft tint on a tinted surface loses about a point of contrast, so the
   LABEL takes `--sk-text` and the tint only agrees with it — the same call
   /killfeed/v2's badges make. */
.tpv2-body .badge-success { background: var(--sk-success-soft); border-color: color-mix(in srgb, var(--sk-success) 45%, transparent); color: var(--sk-text); }
.tpv2-body .badge-error   { background: var(--sk-danger-soft);  border-color: color-mix(in srgb, var(--sk-danger) 45%, transparent);  color: var(--sk-text); }
.tpv2-body .badge-warning { background: var(--sk-warning-soft); border-color: color-mix(in srgb, var(--sk-warning) 45%, transparent); color: var(--sk-text); }
.tpv2-body .badge-info    { background: var(--sk-info-soft);    border-color: color-mix(in srgb, var(--sk-info) 45%, transparent);    color: var(--sk-text); }

/* The toast is fixed to the viewport, so it is the one thing that is not
   inside .tpv2-body.
   The BASE toast takes `--sk-text`, not white. White belongs on the three
   solid semantic fills below and nowhere else — `audit:themes` measured the
   base toast at 1.22:1 on all six light palettes, because a toast whose
   variant class does not match (see the note in tests/tool-pages-v2.test.js
   about showToast's swapped arguments) falls back to exactly this rule. */
.tpv2 .toast {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 9999;
  padding: 0.75rem 1.25rem;
  border-radius: 0.6rem;
  font-weight: 600;
  color: var(--sk-text);
  background: var(--sk-surface-2);
  border: 1px solid var(--sk-border-2);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  max-width: min(28rem, calc(100vw - 3rem));
}
.tpv2 .toast-success { background: var(--sk-success); color: #fff; border-color: transparent; }
.tpv2 .toast-error   { background: var(--sk-danger);  color: #fff; border-color: transparent; }
.tpv2 .toast-info    { background: var(--sk-primary); color: #fff; border-color: transparent; }

/* --- Leaflet ------------------------------------------------------------- *
   BORROWED, NOT COPIED. A page that draws a map loads dashboard-v2.css for the
   framework's own theming (container background, zoom control, layers panel,
   the dark-page icon inversion that hangs off data-theme-mode) — a second copy
   is the one that drifts on a Leaflet upgrade. What is here is only the bit
   /css/leaflet-tile-fix.css contributed that is palette-neutral: the tile seam.
   That file itself is NOT loaded — it forces
   `.leaflet-container { background: #0f172a !important }`, a hex that survives
   every palette change.                                                      */

.tpv2 .leaflet-tile {
  outline: none !important;
  border: none !important;
  margin: 0 !important;
  padding: 0 !important;
}
.tpv2 .leaflet-tile-container img {
  margin: -0.5px !important;
  padding: 0 !important;
  outline: 1px solid transparent !important;
  border: none !important;
}

.tpv2-body #map,
.tpv2-body .leaflet-container {
  background: var(--sk-surface-2);
  border: 1px solid var(--sk-border);
  border-radius: 0.7rem;
}

/* --- a note when the tool needs something the reader has not got ---------- */

.tpv2-note {
  background: var(--sk-surface);
  border: 1px solid var(--sk-border);
  border-radius: 0.9rem;
  padding: 1.5rem;
  max-width: 44rem;
}

.tpv2-note h2 {
  margin: 0 0 0.5rem;
  font-size: 1.1rem;
  color: var(--sk-text);
}

.tpv2-note p {
  margin: 0 0 0.9rem;
  color: var(--sk-text-2);
  line-height: 1.6;
}

/* --- touch ---------------------------------------------------------------
   Skynet UI sizes for a mouse. Below 16px iOS zooms the page on focus, which
   is why the font-size is here and not only the height.                      */

@media (pointer: coarse) {
  .tpv2-body .btn-primary,
  .tpv2-body .btn-secondary,
  .tpv2-body button,
  .tpv2-body .form-input,
  .tpv2-body .form-select,
  .tpv2-body input,
  .tpv2-body select,
  .tpv2-body textarea {
    min-height: 44px;
    font-size: 16px;
  }
}

@media (max-width: 640px) {
  .tpv2-main { padding: 1rem 0.85rem 2.5rem; }
  /* NESTED PADDING IS THE PHONE PROBLEM. These pages wrap the tool in
     `min-h-screen py-8 px-4` and then in `tool-card p-6`, and the shell adds
     the page gutter on top: 320px of screen was down to 193px of content
     before a single word was laid out, measured on
     /tools/gameplay-generator/v2. The shell owns the gutter, so the inner
     padding gives way rather than being paid three times. */
  .tpv2-body .tool-card,
  .tpv2-body .enhanced-card { padding: 0.75rem; }
  .tpv2-body .p-6,
  .tpv2-body .px-4 { padding-left: 0.5rem; padding-right: 0.5rem; }
  .tpv2 .toast { right: 0.85rem; left: 0.85rem; bottom: 0.85rem; max-width: none; }
}

/* --- what /css/dashboard-enhanced.css used to give these pages ------------
   Three names the classic tool views use and none of them declares: they came
   from dashboard-enhanced.css, which is not carried across (it is written in
   the same hard-coded slate hexes as tools-dark-overrides.css). Re-expressed
   here in theme tokens, once, rather than six times in six generated sheets. */

.tpv2-body .stat-card-enhanced {
  background: var(--sk-surface-2);
  border: 1px solid var(--sk-border);
  border-radius: 0.9rem;
  padding: 1.1rem;
}

.tpv2-body .stat-icon {
  width: 2.75rem;
  height: 2.75rem;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 0.75rem;
  background: var(--sk-primary-soft);
  border: 1px solid var(--sk-border-2);
}

.tpv2-body .section-header-enhanced {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--sk-border);
}

.tpv2-body .section-header-enhanced h2,
.tpv2-body .section-header-enhanced h3 {
  margin: 0;
  font-size: 1.05rem;
}

/* The header's glyph. An inline <svg class="icon"> carries no width or height
   attribute, so with no rule for it a browser draws it at the SVG default of
   300x150 — `npm run audit:mobile` caught the path 21px outside its own box on
   /tools/spawnable-types/v2 at every phone width. What sizes it on the classic
   page is /css/dashboard-enhanced.css, the sheet this design drops; the fixed
   #9333ea in it is exactly why, so the colour comes from the palette. */
.tpv2-body .section-header-enhanced .icon {
  width: 2rem;
  height: 2rem;
  flex: 0 0 auto;
  color: var(--sk-primary-text);
}

/* --- modals --------------------------------------------------------------
   WORTH KNOWING: `.modal-backdrop` is declared in no stylesheet this
   repository ships, so /tools/classname-finder's three modals have always
   rendered as ordinary blocks in the page flow rather than over it — visible
   the moment the controller removes `.hidden`. `.modal-container` and
   `.modal-content` do exist, in dashboard-v2.css, which that page does not
   load. All three are given a real overlay here. The classic page is
   unchanged; this fixes it only where the v2 shell can. */

.tpv2 .modal-backdrop,
.tpv2 .modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(0, 0, 0, 0.6);
  overflow-y: auto;
}

.tpv2 .modal-backdrop > *,
.tpv2 .modal-overlay > *,
.tpv2 .modal-container {
  width: min(48rem, 100%);
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
  background: var(--sk-surface);
  border: 1px solid var(--sk-border-2);
  border-radius: 0.9rem;
}

/* --- native controls -----------------------------------------------------
   `form-radio` and `form-checkbox` are @tailwindcss/forms class names and that
   plugin has never been loaded — on the Tailwind CDN or here — so they style
   nothing on either page and are passed to the class checker as contract
   markers. The controls themselves take the palette's accent, which is new and
   is the whole of the change. */

.tpv2-body input[type="checkbox"],
.tpv2-body input[type="radio"] {
  width: auto;
  min-height: 0;
  accent-color: var(--sk-primary);
}

/* --- the `dayz-*` accent vocabulary ---------------------------------------
   `bg-dayz-accent`, `text-dayz-accent`, `hover:bg-dayz-hover` and five more
   are Tailwind-SHAPED but not Tailwind: no config this site loads defines that
   colour, so the compat generator rightly emits nothing for them. What paints
   them on the classic pages is /css/tools-dark-overrides.css, eight
   `!important` purples — and that sheet is the one thing deliberately not
   carried across, because it freezes one palette onto pages that now have
   twelve.

   So the vocabulary is re-expressed here in tokens, the way /wiki/v2 handles
   `item-card` and friends. It is an ACCENT role — the overrides sheet says
   #7c3aed / #9333ea, which is this site's primary — so it follows the reader's
   palette instead of staying purple on amber and teal. Without this,
   /tools/mission-validator-zip/v2's upload button would have a white label on
   no background at all.

   The `-hover` half is a second, darker purple in that sheet; a palette has one
   accent and its own hover, so both names resolve to the same token and the
   hover state comes from the opacity shift. */

.tpv2-body .bg-dayz-accent {
  background-color: var(--sk-primary);
  color: var(--sk-text-on-accent, #fff);
}
/* …and everything drawn on it. Inheritance is not enough: the overlay's label
   carries `text-white`, which the compat generator resolves to `--sk-text`
   (correctly — white on a surface is the bug that mapping exists to prevent),
   and a class beats an inherited value. /tools/mission-validator-zip/v2's
   "Drop your ZIP file here" measured 1.71:1 on crimson-light. */
.tpv2-body .bg-dayz-accent * {
  color: var(--sk-text-on-accent, #fff);
}
.tpv2-body .bg-dayz-accent:hover,
.tpv2-body .hover\:bg-dayz-hover:hover {
  background-color: var(--sk-primary);
  filter: brightness(0.92);
}
.tpv2-body .text-dayz-accent,
.tpv2-body .hover\:text-dayz-hover:hover,
.tpv2-body .group:hover .group-hover\:text-dayz-accent {
  color: var(--sk-primary-text);
}
.tpv2-body .border-dayz-accent,
.tpv2-body .hover\:border-dayz-accent:hover {
  border-color: var(--sk-primary);
}
.tpv2-body .ring-dayz-accent,
.tpv2-body .focus-within\:ring-dayz-accent:focus-within {
  --tw-ring-color: var(--sk-primary);
  box-shadow: 0 0 0 2px var(--sk-primary);
}

/* --- prose links ----------------------------------------------------------
   Skynet UI paints a bare `<a>` with `--sk-primary`, which is a FILL token —
   as text on a card it is about 2.8-3.5:1. `--sk-primary-text` is the readable
   half in both modes. Every other v2 page learned this (the footer,
   /partnerships/v2, the maps pages); the tool pages never got the rule, and
   `npm run audit:themes` found it on /tools/globals-configurator/v2's category
   tabs at 3.14-3.55:1 depending on the palette.

   IT CARRIES NO SPECIFICITY, deliberately. The first cut was
   `.tpv2-body a:not(.sk-btn)`, which at (0, 2, 0) outranked every colour the
   tool markup sets on a link — it repainted `text-white` on an accent fill
   ("Upgrade Now", 2.07-2.66:1) and the label of `.btn-primary-enhanced`
   ("Join Discord", 1.94-2.31:1). That is the `/partnerships/v2` 1.94:1 finding
   arriving again in a vocabulary no exclusion list had heard of, which is the
   argument against exclusion lists.
   Wrapped in `:where()` the whole selector is (0, 0, 1): it ties with Skynet
   UI's own `a` rule and wins on source order, and ANY class the markup puts on
   a link — a utility, a button, an accent fill — beats it. */
:where(.tpv2-body) a:where(:not(.sk-btn)) {
  color: var(--sk-primary-text);
}
