/**
 * Portal skins + shared chrome polish.
 *
 * Two jobs, in this order:
 *
 *  1. Polish — tokens every skin shares (surface, radius, focus ring). One
 *     scale instead of per-page improvisation, so the portal looks calm and
 *     consistent and a new page inherits it for free.
 *
 *  2. Skins — `html[data-skin="<id>"]` re-points the chrome tokens that
 *     `templates/base.html` already reads. Every id here is declared in
 *     `portal_skins.py`; `tests/test_portal_skins.py` keeps the two in step.
 *
 * Load order matters: this sheet is linked *after* base.html's inline <style>
 * (so it can override the historical hard-coded values) and *before* a page's
 * own {% block styles %} (so a page can still refine something local).
 *
 * Rules of this file:
 *   - tokens first, rules second; a colour appears once, in a token;
 *   - no `!important` unless the rule it beats is an `!important` in base;
 *   - no skin may reduce text contrast below WCAG AA on its own surface.
 */

/* ------------------------------------------------------------------ */
/* 1. Shared tokens and chrome polish                                   */
/* ------------------------------------------------------------------ */

/* These are the warm defaults (:root = 熔金, the default skin). 经典 re-pins
   the cool values in its own block, so it still renders exactly the palette it
   did before this file existed. Every colour appears once, in a token. */
:root {
  /* Surfaces */
  --portal-surface-bg: #f6f1e8;
  --portal-surface-raise: #fffcf6;
  --portal-line: #e8dfd1;
  --portal-line-strong: #d9cbb6;
  --portal-ink: #2b2117;
  --portal-ink-muted: #7c6c58;

  /* Navbar structure (skin-neutral shape, warm defaults) */
  --nav-surface: #fffcf6;
  --nav-muted: #7c6c58;
  --nav-muted-bg: #f4ece0;
  --nav-border: #e8dfd1;
  --nav-shadow: 0 1px 0 rgba(90, 60, 30, 0.07);

  /* The accent: the only colour a primary button, a link and a focus ring use.
     It has to live here as well as in each skin block, and the reason is a bug
     this file caused: ``.btn-primary`` below reads ``var(--nav-brand)``, and
     AdminLTE's own rule is ``color:#fff;background:#007bff``. When only the skin
     blocks declared the token, the default skin (熔金 has no block of its own --
     :root *is* 熔金) resolved the var to nothing, so the background fell back to
     transparent while the text stayed white: a white label on a white page.
     Declaring it here makes the token exist for every skin, and #8a4406 carries
     white text at 7.2:1 (AA needs 4.5) and reads at 6.8:1 as link text on the
     sand surface. The copper in --portal-seam is decorative; this one is
     readable. */
  --nav-brand: #8a4406;
  --nav-accent: #8a4406;
  --nav-accent-hover: #6d2c05;
  /* Text ON the accent. A light accent (quench's copper) cannot carry white
     text, so the label colour is a token per skin instead of a hard-coded #fff
     in the .btn-primary rule. Default: white, because the default accent is a
     dark copper (7.2:1). */
  --nav-on-accent: #ffffff;

  /* Sidebar chrome. The warm dark field is the default; a light skin re-pins
     all of these in its own block. */
  --sidebar-bg: #241c15;
  --sidebar-brand: #1c1611;
  --sidebar-ink: rgba(255, 246, 235, 0.88);
  --sidebar-ink-strong: #fff6ea;
  --sidebar-ink-dim: rgba(255, 246, 232, 0.58);
  --sidebar-hover-bg: rgba(255, 240, 222, 0.06);
  --sidebar-active-bg: rgba(255, 236, 214, 0.1);
  --sidebar-tree-bg: rgba(0, 0, 0, 0.22);
  --sidebar-line: rgba(255, 238, 220, 0.09);
  --sidebar-scroll-thumb: rgba(255, 240, 225, 0.26);

  /* One radius scale: controls, cards. A skin re-points these to change the
     *shape* of the page, not just its colour -- 淬火/工业 are square, 砂型/青瓷
     round. This is what stops five skins reading as one page in five hues. */
  --portal-radius-sm: 0.3rem;
  --portal-radius: 0.45rem;

  /* Density/edge controls a skin may re-point. Structural, not decorative. */
  --portal-label-tracking: 0;
  --portal-label-transform: none;

  /* The "pour seam": a hairline under the top bar. This is the one place the
     metal-cast idea is allowed to be literal -- 2px, no glow, no texture. */
  --portal-seam: linear-gradient(90deg, #6d2c05, #b95c10 32%, #e8a33a 54%, #b95c10 76%, #6d2c05);

  /* Hero masthead (portal/index.html). It is a dark band in *every* skin, so it
     gets its own ground instead of borrowing --sidebar-bg: a light skin would
     otherwise turn the hero white under white text. Warm defaults here. */
  --portal-hero-bg:
    radial-gradient(1100px 520px at 92% 8%, rgba(232, 163, 58, 0.30), transparent 58%),
    radial-gradient(720px 400px at 0% 100%, rgba(185, 92, 16, 0.30), transparent 55%),
    linear-gradient(152deg, #140f0b 0%, #241c15 55%, #3a2413 100%);
  --portal-hero-ink: #f6efe4;
  --portal-hero-sub: rgba(246, 239, 228, 0.82);
  --portal-hero-edge: rgba(246, 239, 228, 0.42);
  --portal-hero-wash: rgba(246, 239, 228, 0.12);
}

/* Calm page ground. */
body .content-wrapper {
  background: var(--portal-surface-bg);
}

/* The pour seam under the top bar. It is drawn with `background-image` on a
   selector that matches the header's own `background: var(--nav-surface)`
   shorthand in base.html -- a bare `.main-header` rule loses that fight, and
   the shorthand would reset background-image anyway. `.navbar.topnav-enhanced`
   is included so the seam survives either header markup. A skin may set
   `--portal-seam: none` to switch the idea off. */
@media screen {
  .main-header.navbar,
  .main-header.navbar.topnav-enhanced {
    background-image: var(--portal-seam, none);
    background-repeat: no-repeat;
    background-position: left bottom;
    background-size: 100% 2px;
  }
}

@media print {
  .main-header.navbar,
  .main-header.navbar.topnav-enhanced {
    background-image: none !important;
  }
}

/* A page body that starts with a heading should breathe, not hug the header. */
.content-header > .container-fluid {
  padding-top: 0.35rem;
}

/* Cards: hairline instead of a stack of borders + shadow. */
.card {
  border: 1px solid var(--portal-line);
  border-radius: var(--portal-radius);
  box-shadow: none;
}

.card-header {
  background: transparent;
  border-bottom: 1px solid var(--portal-line);
  padding: 0.7rem 1rem;
}

.card-header > .card-title,
.card-header > strong {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--portal-ink);
}

.card-body {
  padding: 1rem;
}

/* A card outline is a 3px colour stripe in AdminLTE; a 2px hairline reads
   calmer at this density and keeps the colour as a hint, not decoration. */
.card.card-outline {
  border-top-width: 2px;
}

/* Buttons: one accent (the skin's), quiet secondaries. */
.btn {
  border-radius: var(--portal-radius-sm);
  font-weight: 500;
}

/* A primary button must never resolve to no background. AdminLTE sets
   ``color:#fff`` here, so an undefined ``--nav-brand`` produced white-on-white
   rather than an unstyled button — the failure was invisible to every test that
   only checked the token existed in *a* skin block. The fallback keeps the
   button readable even if a future skin forgets the token entirely. */
.btn-primary {
  background-color: var(--nav-brand, #8a4406);
  border-color: var(--nav-brand, #8a4406);
  color: var(--nav-on-accent, #fff);
}

.btn-primary:hover,
.btn-primary:focus,
.btn-primary:not(:disabled):not(.disabled):active {
  background-color: var(--nav-accent-hover, #6d2c05);
  border-color: var(--nav-accent-hover, #6d2c05);
  color: var(--nav-on-accent, #fff);
}

.btn-outline-primary {
  color: var(--nav-brand, #8a4406);
  border-color: var(--nav-brand, #8a4406);
}

.btn-outline-primary:hover,
.btn-outline-primary:focus {
  background-color: var(--nav-brand, #8a4406);
  border-color: var(--nav-brand, #8a4406);
  color: var(--nav-on-accent, #fff);
}

/* Focus: visible on every control without a glow that fights the palette.
   Keyboard users get a real ring; mouse users are not distracted by it. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
.btn:focus-visible,
.nav-link:focus-visible {
  outline: 2px solid var(--nav-accent);
  outline-offset: 2px;
}

/* Forms: label quieter than the value, error text never bigger than the field. */
.form-control {
  border-radius: var(--portal-radius-sm);
  border-color: var(--portal-line);
  color: var(--portal-ink);
}

.form-control:focus {
  border-color: var(--nav-accent);
  /* AdminLTE's default focus glow is coloured Bootstrap blue, which fights a
     skin's accent. The border plus the visible focus ring above is enough. */
  box-shadow: none;
}

label {
  color: var(--portal-ink);
  font-weight: 500;
}

/* Tables: header is a label, rows are data. Zebra off by default, hover on. */
.table thead th {
  border-bottom: 1px solid var(--portal-line);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--portal-ink-muted);
  white-space: nowrap;
}

.table td,
.table th {
  vertical-align: middle;
}

.table-hover tbody tr:hover {
  background-color: var(--portal-line);
}

/* Alerts: one hairline and a coloured left edge; no heavy fill. */
.alert {
  border-radius: var(--portal-radius-sm);
  border: 1px solid var(--portal-line);
}

/* Skin picker (admin only): a radio card, the whole tile clickable. */
.skin-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 0.9rem;
}

.skin-note {
  margin: 0 0 0.75rem;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--portal-ink-muted);
}

.skin-card {
  position: relative;
  display: block;
  margin: 0;
  padding: 0.9rem;
  border: 1px solid var(--portal-line);
  border-radius: var(--portal-radius);
  background: var(--portal-surface-raise);
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.skin-card:hover {
  border-color: var(--nav-accent);
}

/* The radio is visually hidden, so the tile carries focus itself -- otherwise a
   keyboard user tabs into an invisible control and sees nothing move. */
.skin-card input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.skin-card:focus-within {
  border-color: var(--nav-accent);
  outline: 2px solid var(--nav-accent);
  outline-offset: 2px;
}

.skin-card.is-on {
  border-color: var(--nav-accent);
  box-shadow: 0 0 0 2px var(--nav-accent) inset;
}

/* Previewed but not applied: the tile is marked, yet clearly *not* the look in
   use. A dashed edge says "candidate"; only the solid badge says "live", so the
   two states can never be read as the same thing. */
.skin-card.is-preview {
  border-color: var(--nav-accent);
  border-style: dashed;
  box-shadow: none;
}

/* A tiny chrome mock (top bar, sidebar, page) instead of three colour stripes:
   the swatch then shows the *composition* of the look, not just its palette.
   Only the three declared colours are used, so it cannot drift from the skin. */
.skin-swatch {
  position: relative;
  display: block;
  height: 62px;
  border-radius: var(--sw-radius, 6px);
  overflow: hidden;
  border: 1px solid var(--portal-line);
  margin-bottom: 0.6rem;
}

/* A skin whose *top bar* is dark must look like it in the tile, otherwise two
   looks with different chrome polarity draw an identical mock. */
.skin-swatch .sw-top {
  position: absolute;
  inset: 0 0 auto 0;
  height: 13px;
  background: #ccc;
  border-bottom: 2px solid transparent;
}

.skin-swatch .sw-side {
  position: absolute;
  inset: 13px auto 0 0;
  width: 21%;
  background: #ccc;
}

.skin-swatch .sw-page {
  position: absolute;
  inset: 13px 0 0 21%;
  background: #eee;
  padding: 9px 10px;
}

/* The page corner picks up the tile's radius too, so a square look reads as
   square here rather than only on the real page. */
.skin-swatch .sw-page {
  border-top-left-radius: var(--sw-radius, 6px);
}

/* A dark-chrome look gets a light hairline under its bar. Without this the
   dark bar simply blends into a dark sidebar and the tile reads as one block,
   which hides the very difference (`chrome`) the tile exists to show. */
.skin-swatch.sw-dark-chrome {
  border-color: rgba(0, 0, 0, 0.35);
}

.skin-swatch.sw-dark-chrome .sw-top {
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.16) inset;
}

.skin-swatch .sw-rule {
  display: block;
  height: 4px;
  border-radius: 2px;
  opacity: 0.62;
}

.skin-swatch .sw-rule + .sw-rule {
  margin-top: 6px;
}

.skin-swatch .sw-rule-short {
  width: 62%;
  opacity: 0.4;
}

.skin-name {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-weight: 600;
  color: var(--portal-ink);
}

.skin-desc {
  display: block;
  margin: 0.2rem 0 0;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--portal-ink-muted);
}

/* "In use" must be unmistakable at a glance: a filled badge in the accent, not
   a word in the same weight as the description. The dotted outline on the tile
   (`.skin-card.is-on`) is the second, redundant cue. */
.skin-on-badge {
  display: inline-block;
  padding: 0.06rem 0.4rem;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--nav-on-accent, #fff);
  background: var(--nav-brand);
  white-space: nowrap;
}

/* The preview state borrows the same pill shape in *outline*, so the two read
   as a pair (state badges) while remaining distinguishable without colour. */
.skin-preview-badge {
  display: inline-block;
  padding: 0.06rem 0.4rem;
  border-radius: 999px;
  border: 1px dashed var(--nav-accent);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--nav-accent);
  background: transparent;
  white-space: nowrap;
}

/* Preview is not applied. Same line carries the name being previewed. */
.skin-status {
  flex: 1 0 100%;
  margin: 0.6rem 0 0;
  font-size: 0.82rem;
  color: var(--portal-ink-muted);
}

/* ------------------------------------------------------------------ */
/* 2. Skins                                                             */
/* ------------------------------------------------------------------ */

/* NOTE ON THE DEFAULT: `:root` above *is* 熔金 (molten). A skin block only has to
   exist when it differs from that default, which is why molten has no block of
   its own -- there is exactly one place its colours are written. */

/* sand: the lightest warm look -- off-white ground, sand sidebar, quieter copper.
   Shape: ROUND. This is the "paper" look, so every edge is softer than the
   default and the pour seam disappears -- a soft page should not have a
   machine-hard hairline under the bar. */
html[data-skin='sand'] {
  --nav-brand: #9c5a1d;
  --nav-accent: #9c5a1d;
  --nav-accent-hover: #6b3a10;
  --nav-muted: #847358;
  --nav-muted-bg: #f4ece0;
  --nav-border: #ece3d4;
  --nav-shadow: 0 1px 2px rgba(90, 60, 30, 0.05);
  --portal-seam: none;

  --portal-radius-sm: 0.5rem;
  --portal-radius: 0.85rem;

  --sidebar-bg: #fbf7ef;
  --sidebar-brand: #f4ece0;
  --sidebar-accent: #9c5a1d;

  --sidebar-ink: #4a3c2c;
  --sidebar-ink-strong: #221a10;
  --sidebar-ink-dim: #6f6047;
  --sidebar-hover-bg: #f3ebdd;
  --sidebar-active-bg: #efe4d3;
  --sidebar-tree-bg: #f7f1e6;
  --sidebar-line: #e7dccb;
  --sidebar-scroll-thumb: rgba(90, 60, 30, 0.20);

  --portal-surface-bg: #f8f4ec;
  --portal-surface-raise: #fffdf8;
  --portal-line: #ece3d4;
  --portal-line-strong: #ded1bc;
  --portal-ink: #33291d;
  --portal-ink-muted: #847358;

  /* A light skin still wants a dark masthead; only the light temperature moves. */
  --portal-hero-bg:
    radial-gradient(1100px 520px at 92% 8%, rgba(201, 168, 119, 0.26), transparent 58%),
    radial-gradient(720px 400px at 0% 100%, rgba(140, 106, 69, 0.30), transparent 55%),
    linear-gradient(152deg, #1a1510 0%, #302619 55%, #4a3a26 100%);
  --portal-hero-ink: #faf3e8;
  --portal-hero-sub: rgba(250, 243, 232, 0.82);
  --portal-hero-edge: rgba(250, 243, 232, 0.42);
  --portal-hero-wash: rgba(250, 243, 232, 0.12);
}

/* quench: the warm dark field. Copper reads as reflection against near-black. */
html[data-skin='quench'] {
  /* Copper is a *light* accent here: #d9862b carries white text at only 2.84:1,
     which is the "Change password button is unreadable" defect. Two options --
     darken the copper and lose the reflection, or keep the copper and set its
     label dark. This keeps the look and sets the ink: 6.4:1 on #d9862b. The
     hover moves lighter (#f0a849) so the shift is still perceptible while the
     dark ink improves to 9.0:1. */
  --nav-brand: #d9862b;
  --nav-accent: #d9862b;
  --nav-accent-hover: #f0a849;
  --nav-on-accent: #191411;
  --nav-muted: #a2937f;
  --nav-muted-bg: #2b231c;
  --nav-border: #372c23;
  --nav-shadow: none;
  --portal-seam: linear-gradient(90deg, #8a4406, #d9862b 45%, #f5c473 62%, #a25a12);

  /* SQUARE + dense: zero radius, no shadow, letter-spaced labels. The
     "instrument panel" reading -- this is 淬火's signature, and it is a shape
     change, not a darker 熔金. */
  --portal-radius-sm: 0;
  --portal-radius: 0;
  --portal-label-tracking: 0.04em;

  --sidebar-bg: #120e0b;
  --sidebar-brand: #0d0a08;
  --sidebar-accent: #f0a849;

  --sidebar-ink: rgba(255, 244, 230, 0.82);
  --sidebar-ink-strong: #fff8ee;
  --sidebar-ink-dim: rgba(255, 240, 222, 0.56);
  --sidebar-hover-bg: rgba(255, 235, 210, 0.05);
  --sidebar-active-bg: rgba(255, 235, 210, 0.09);
  --sidebar-tree-bg: rgba(0, 0, 0, 0.28);
  --sidebar-line: rgba(255, 235, 210, 0.08);
  --sidebar-scroll-thumb: rgba(255, 235, 210, 0.24);

  --portal-surface-bg: #191411;
  --portal-surface-raise: #221b16;
  --portal-line: #372c23;
  --portal-line-strong: #4a3a2c;
  --portal-ink: #f2e8db;
  --portal-ink-muted: #a2937f;

  /* Already dark: a shorter step, so the hero still reads as a distinct band. */
  --portal-hero-bg:
    radial-gradient(1100px 520px at 92% 8%, rgba(240, 168, 73, 0.22), transparent 58%),
    radial-gradient(720px 400px at 0% 100%, rgba(162, 90, 18, 0.26), transparent 55%),
    linear-gradient(152deg, #0b0806 0%, #120e0b 55%, #1f1610 100%);
  --portal-hero-ink: #fff6ea;
  --portal-hero-sub: rgba(255, 246, 234, 0.82);
  --portal-hero-edge: rgba(255, 246, 234, 0.42);
  --portal-hero-wash: rgba(255, 246, 234, 0.12);
}

/* classic: the historical navy + gold chrome. It keeps the legacy palette
   variables from theme_json (--nav-* / --sidebar-bg / --sidebar-accent), so an
   installation that had tuned those inputs still gets exactly that look. Only
   the tokens the legacy palette never had are pinned here, to the cool values
   the portal used before this stylesheet existed. */
html[data-skin='classic'] {
  --nav-muted: #5a6a7a;
  --nav-muted-bg: #f3f4f6;
  --nav-border: #e2e8f0;
  --nav-surface: #ffffff;
  --nav-shadow: 0 1px 0 rgba(27, 58, 90, 0.06);
  --portal-seam: none;

  /* SQUARE, classic density: the 2019-era admin look this skin reproduces.
     Deliberately the tightest radius, so going back to 经典 also goes back to
     the original geometry instead of only the original colours. */
  --portal-radius-sm: 0.2rem;
  --portal-radius: 0.25rem;

  --sidebar-ink: rgba(255, 255, 255, 0.85);
  --sidebar-ink-strong: #ffffff;
  /* 0.45 was 4.41:1 on this ground -- just under AA for the small nav headers. */
  --sidebar-ink-dim: rgba(255, 255, 255, 0.52);
  --sidebar-hover-bg: rgba(255, 255, 255, 0.06);
  --sidebar-active-bg: rgba(255, 255, 255, 0.1);
  --sidebar-tree-bg: rgba(0, 0, 0, 0.15);
  --sidebar-line: rgba(255, 255, 255, 0.08);
  --sidebar-scroll-thumb: rgba(255, 255, 255, 0.28);

  --portal-surface-bg: #f4f6f9;
  --portal-surface-raise: #ffffff;
  --portal-line: #e2e8f0;
  --portal-line-strong: #cfd8e3;
  --portal-ink: #1f2d3a;
  --portal-ink-muted: #5a6a7a;

  /* The original hero, unchanged. */
  --portal-hero-bg:
    radial-gradient(1100px 520px at 92% 8%, rgba(26, 122, 140, 0.32), transparent 58%),
    radial-gradient(720px 400px at 0% 100%, rgba(30, 74, 110, 0.4), transparent 55%),
    linear-gradient(152deg, #06131e 0%, #0d2740 52%, #153852 100%);
  --portal-hero-ink: #f4f7fa;
  --portal-hero-sub: rgba(244, 247, 250, 0.82);
  --portal-hero-edge: rgba(244, 247, 250, 0.42);
  --portal-hero-wash: rgba(244, 247, 250, 0.12);
}

/* porcelain: light, airy, teal accent; light sidebar. */
html[data-skin='porcelain'] {
  --nav-brand: #0f6b73;
  --nav-accent: #0f6b73;
  --nav-accent-hover: #0b5057;
  --nav-muted: #5c6b76;
  --nav-muted-bg: #eef4f5;
  --nav-border: #dde6e8;
  --nav-shadow: 0 1px 0 rgba(15, 70, 80, 0.06);
  --portal-seam: linear-gradient(90deg, #0b5057, #14909a 50%, #0b5057);

  /* A middle radius, deliberately NOT the largest: 砂型 owns the softest edge,
     so the two light looks differ in temperature *and* in edge weight. Making
     this the biggest radius too would put them back in the same family. */
  --portal-radius-sm: 0.32rem;
  --portal-radius: 0.5rem;

  --sidebar-bg: #ffffff;
  --sidebar-brand: #f7f9fa;
  --sidebar-accent: #0f6b73;

  --sidebar-ink: #3c4a54;
  --sidebar-ink-strong: #16232c;
  --sidebar-ink-dim: #66737f;
  --sidebar-hover-bg: #eef4f5;
  --sidebar-active-bg: #e3eff0;
  --sidebar-tree-bg: #f7f9fa;
  --sidebar-line: #e4ebed;
  --sidebar-scroll-thumb: rgba(15, 60, 70, 0.22);

  --portal-surface-bg: #f7f9fa;
  --portal-surface-raise: #ffffff;
  --portal-line: #e2e9eb;
  --portal-line-strong: #cfdce1;
  --portal-ink: #1f2d33;
  --portal-ink-muted: #5f7480;

  /* Cool skin, cool masthead -- the look it always had. */
  --portal-hero-bg:
    radial-gradient(1100px 520px at 92% 8%, rgba(26, 122, 140, 0.32), transparent 58%),
    radial-gradient(720px 400px at 0% 100%, rgba(30, 74, 110, 0.4), transparent 55%),
    linear-gradient(152deg, #06131e 0%, #0d2740 52%, #153852 100%);
  --portal-hero-ink: #f4f7fa;
  --portal-hero-sub: rgba(244, 247, 250, 0.82);
  --portal-hero-edge: rgba(244, 247, 250, 0.42);
  --portal-hero-wash: rgba(244, 247, 250, 0.12);
}

/* industrial: the plant-console look. The only skin with DARK chrome, square
   corners and a visible rule grid -- so it is not "熔金 in grey". Panel grey
   ground, safety-orange accent, cold dark sidebar; labels are uppercase and
   letter-spaced, the way an MES/HMI panel spells them. */
html[data-skin='industrial'] {
  /* Safety orange is a *light* accent: #ffffff on #ea580c is 3.0:1, which fails
     AA for the primary button label. Darkening the accent would lose the
     safety-colour cue, so the label goes dark instead: #1c2126 on #ea580c is
     4.56:1 (just over AA) and on the hover #f97316 it improves to 5.79:1. */
  --nav-brand: #ea580c;
  --nav-accent: #ea580c;
  --nav-accent-hover: #f97316;
  --nav-on-accent: #1c2126;

  /* Dark chrome. The top bar is the ground; its ink is cold light grey, and the
     hairline under it is the accent, which is the "instrument bezel" cue. */
  --nav-surface: #22262a;
  --nav-muted: #a8b0b8;
  --nav-muted-bg: #2b3036;
  --nav-border: #343a40;
  --nav-shadow: none;
  --portal-seam: linear-gradient(90deg, #7c2d12, #c2410c 35%, #ea580c 50%, #c2410c 65%, #7c2d12);

  /* SQUARE, and no shadow anywhere: a panel has edges, not elevation. */
  --portal-radius-sm: 0;
  --portal-radius: 0;
  --portal-label-tracking: 0.06em;
  --portal-label-transform: uppercase;

  --sidebar-bg: #1a1d21;
  --sidebar-brand: #14171a;
  --sidebar-accent: #ea580c;

  --sidebar-ink: rgba(226, 232, 237, 0.86);
  --sidebar-ink-strong: #f4f7f9;
  --sidebar-ink-dim: rgba(214, 222, 229, 0.62);
  --sidebar-hover-bg: rgba(234, 88, 12, 0.10);
  --sidebar-active-bg: rgba(234, 88, 12, 0.18);
  --sidebar-tree-bg: rgba(0, 0, 0, 0.24);
  --sidebar-line: rgba(226, 232, 237, 0.10);
  --sidebar-scroll-thumb: rgba(226, 232, 237, 0.24);

  /* Cold panel grey, not paper: #eceef0 is the drawn-metal ground. */
  --portal-surface-bg: #eceef0;
  --portal-surface-raise: #f7f8f9;
  --portal-line: #cfd4d9;
  --portal-line-strong: #aeb6bd;
  --portal-ink: #1c2126;
  --portal-ink-muted: #55606a;

  /* A flat, hard masthead: no soft radial wash, one vertical step. */
  --portal-hero-bg:
    linear-gradient(180deg, #14171a 0%, #22262a 100%);
  --portal-hero-ink: #f4f7f9;
  --portal-hero-sub: rgba(226, 232, 237, 0.80);
  --portal-hero-edge: rgba(234, 88, 12, 0.55);
  --portal-hero-wash: rgba(234, 88, 12, 0.14);
}

/* industrial reads its dark chrome through the *navbar* tokens alone, so the
   ink rules that base.html writes for a light bar have to be re-pointed here:
   .navbar-primary/.navbar-toolbar take --nav-brand/--nav-muted as *text*
   colours, which on a dark ground would be orange-on-charcoal for the links
   and near-invisible for the muted toolbar. Both are re-stated as this skin's
   own ink, and the active marker becomes the accent hairline. */
html[data-skin='industrial'] .navbar-primary .nav-link,
html[data-skin='industrial'] .navbar-primary .nav-link.nav-emphasis,
html[data-skin='industrial'] .navbar-primary .nav-link.nav-cta {
  color: #e8edf1 !important;
}

html[data-skin='industrial'] .navbar-primary .nav-link:hover {
  color: #ffffff !important;
  text-decoration-color: var(--nav-accent);
}

html[data-skin='industrial'] .navbar-primary .nav-link.active,
html[data-skin='industrial'] .navbar-primary .nav-link.nav-emphasis.active,
html[data-skin='industrial'] .navbar-primary .nav-link.nav-cta.active {
  color: #ffffff !important;
  box-shadow: inset 0 -2px 0 var(--nav-accent);
}

html[data-skin='industrial'] .navbar-primary .nav-link .nav-link-icon {
  color: var(--nav-accent);
  opacity: 1;
}

html[data-skin='industrial'] .navbar-toolbar .nav-link,
html[data-skin='industrial'] .navbar-toolbar .nav-link i {
  color: #cbd3da !important;
}

html[data-skin='industrial'] .navbar-toolbar .nav-link:hover,
html[data-skin='industrial'] .navbar-toolbar .nav-link:hover i {
  color: #ffffff !important;
}


/* ------------------------------------------------------------------ */
/* 3. Shape                                                            */
/* ------------------------------------------------------------------ */

/* A skin's `shape` is realised here, not by repeating five near-identical
   rules: the tokens above decide radius and weight, and these are the few
   structures that need a real difference to read as a different *product*.
   Only 工业 (and to a lesser extent 淬火) opt into the last group. */

/* Every skin: a card's corner follows its radius scale, which is what makes a
   square look read as square on a real page. Elevation is deliberately NOT
   re-declared here -- AdminLTE's own card shadow stays for the skins that did
   not ask to be flat, so this file does not silently restyle every page. */
.card,
.skin-card {
  border-radius: var(--portal-radius);
}

/* A skin that declares no elevation gets none: a machined panel has edges, not
   a drop shadow. Scoped to the two flat skins rather than declared for all. */
html[data-skin='industrial'] .card,
html[data-skin='quench'] .card {
  box-shadow: none;
}

/* Uppercase, letter-spaced labels: 工业 spells its section labels the way a
   plant panel does, and 淬火 spaces them slightly. Driven by the tokens so a
   skin only declares the treatment once; the defaults are 0/none, which leaves
   every other look exactly as it was. Scoped to nav headers and card headers so
   it never touches body copy or a user's own text. */
.nav-header,
.card-header > strong,
.skin-name {
  letter-spacing: var(--portal-label-tracking, 0);
  text-transform: var(--portal-label-transform, none);
}

/* industrial: a visible rule grid. Two hairlines only -- the content column is
   separated from the chrome, and each card is boxed. No page-wide background
   pattern: that is decoration, not structure. */
html[data-skin='industrial'] .content-wrapper {
  border-left: 1px solid var(--portal-line-strong);
}

html[data-skin='industrial'] .card {
  border-color: var(--portal-line-strong);
}

html[data-skin='industrial'] .card-header {
  border-bottom: 1px solid var(--portal-line-strong);
  background: var(--portal-surface-raise);
}

html[data-skin='industrial'] .card-footer {
  border-top: 1px solid var(--portal-line-strong);
}

/* ------------------------------------------------------------------ */
/* 4. Light-sidebar structure                                          */
/* ------------------------------------------------------------------ */

/* base.html picks AdminLTE's own `sidebar-light` class for a light skin, so the
   brand strip, user panel and tree fills already come from AdminLTE. What is
   left here is only what its light defaults get wrong at this density: the
   logo plate's drop shadow, the outer edge, and nav header ink that must still
   pass AA on white. The selectors are compound (`.sidebar-light.sidebar-saili`)
   because both classes sit on the same <aside>. */
.sidebar-light.sidebar-saili {
  border-right: 1px solid var(--sidebar-line);
  box-shadow: none !important;
}

.sidebar-light.sidebar-saili .brand-link .brand-lockup {
  box-shadow: none;
}

.sidebar-light.sidebar-saili .nav-header {
  color: var(--sidebar-ink-dim);
}

/* The user panel's small print sits on a dark strip in classic; on a light one
   `.text-muted` at 0.72rem is too faint against white, so both polarities read
   the skin's ink token instead. */
.sidebar-saili .user-panel .info a {
  color: var(--sidebar-ink-strong);
}

.sidebar-saili .user-panel .info small a,
.sidebar-saili .status {
  color: var(--sidebar-ink-dim) !important;
}

.sidebar-saili .user-panel .info small a:hover {
  color: var(--sidebar-ink-strong) !important;
}
