/* Halloween — a theme file. See themes/README.md for the contract.
   Loaded AFTER public/css/styles.css, so it wins on source order with no
   !important anywhere.

   BOTH VARIANTS. Light is a moonlit page, dark is the small hours; neither is
   the other inverted.

   ---------------------------------------------------------------------------
   THE BRIEF, AND THE ONE HARD PROBLEM IN IT
   ---------------------------------------------------------------------------
   Professional first, playful second. This is the register a nonprofit's staff
   read all day and a funder may walk past, so the split is:

       THE CHROME MAY BE PLAYFUL.  THE DATA STAYS SOBER.

   Every prop below sits in chrome — the sidebar's corner, the sign-in page, an
   empty state where there is by definition no data to obscure. Nothing is drawn
   behind a row, a figure, a form control or a report cell.

   ---------------------------------------------------------------------------
   VIOLET IS THE ACCENT; WARMTH IS THE SEMANTIC AXIS
   ---------------------------------------------------------------------------
   THIS SECTION REPLACES THE ONE THAT USED TO STAND HERE (2026-08-29). The old
   text said "EMBER ORANGE IS DECORATION ONLY … It is never a chip, a row, a
   figure, a border on data" and, further down, that the status chips "are
   DELIBERATELY NOT SET ANYWHERE IN THIS FILE" because recolouring them was "the
   failure themes/README.md names". Both claims are now wrong and both are gone.
   The maintainer reversed the rule:

     "You are allowed to change the semantic colours on the app. I think it's
      better if we match them with the themes. There's no reason why we have to
      have standard colours across themes."

   What the old rule was protecting was never the hex value — it was the fact
   that a glance at a chip tells you which of three states you are in. That
   survives a change of hue, and themes/README.md now states the four things it
   does NOT survive. This file satisfies all four; see the SEMANTIC section.

   So the colour plan is no longer "orange is quarantined", it is:

     * The INTERACTIVE / STRUCTURAL colour is BRUISED VIOLET. Links, the primary
       button, the active nav pill, `accent-color`, the sorted-column marker,
       the section-heading bar. Violet is the accent, so — README constraint 2 —
       NO status wears it. That is the same firewall as before, drawn from the
       other side: status used to be fenced off from the theme; now the theme's
       accent is fenced off from status.
     * The QUIET STRUCTURAL colour is MOONLIGHT — a desaturated grey-green for
       column headings, sidebar group labels and rules.
     * SEMANTIC STATE IS A HEAT RAMP, and heat means trouble:

           moonlight (cool)  fine        available / approved / received
           candlelight (dim) in hand     checked out
           candle-amber      caution     in maintenance / pending / ordered
           ember             bad         not working / rejected
           blood             worst       lost / errors / overdue
           gravestone (grey) closed      disposed / donated / cancelled

       Cool = fine, and the hotter and redder it gets the worse the news. That
       is README constraint 3 carried by hue rather than by memorised pairings,
       which is why `lost` cannot accidentally end up looking pleasant.
     * The PROPS keep their orange (cobweb tint, candle glow, both pumpkins).
       They now share a hue family with the caution/bad chips, which is a real
       cost and a deliberate one: all four sit in chrome or in an empty state —
       the sidebar's corner and foot, /login, and a paragraph that exists
       precisely because there is no data — so no prop is ever drawn within a
       row's reading distance of a chip.

   THE FOCUS RING STAYS EMBER, and this is the one place the reversal costs
   something. Focus is now the same hue family as the caution/bad chips instead
   of being the only warm thing in the reading path. It is still the right
   trade: focus is transient, exists on exactly one element at a time, is drawn
   as an outline OUTSIDE the box rather than as a fill or a text colour, and is
   never a property of a record — and a violet ring on violet links is the one
   focus colour this palette cannot afford.

   `<mark>` (the overdue-date highlight, `--pico-mark-*`) is deliberately still
   NOT set here. It is the app's amber, and this theme's caution tier is a
   candle-amber of the same family, so the inherited value already agrees with
   the re-pitch. Setting it would be a change with no effect worth the line.

   ---------------------------------------------------------------------------
   NO NEW WEBFONT, ON PURPOSE
   ---------------------------------------------------------------------------
   The font list is in views/partials/header.ejs, which a theme file may not
   touch (see themes/README.md). Outfit/Karla stay. A "spooky" display face is
   also the fastest way to make a working tool look like a party invitation, so
   the typographic move here is small-caps tracking on column headings and
   tabular figures on money — not a costume face.

   ---------------------------------------------------------------------------
   VARIANT SELECTION — THE THREE-BLOCK PATTERN, NOT THE TWO-BLOCK ONE
   ---------------------------------------------------------------------------
   The role tokens are set three times, exactly as styles.css and apple.css set
   theirs, and the third block is not redundant:

     1  :root[data-palette="halloween"]:not([data-theme="dark"])          light
     2  @media (prefers-color-scheme: dark) … :not([data-theme="light"])  dark
     3  :root[data-palette="halloween"][data-theme="dark"]                dark

   public/js/themeToggle.js REMOVES `data-theme` for a two-variant theme when the
   user has never touched the switch, so "no attribute, follow the OS" is a real
   state and not a corner case. With only blocks 1 and 3, a first-time user on a
   dark-mode machine would get this theme's LIGHT tokens under styles.css's DARK
   component rules — light-mode grounds behind dark-mode status chips. Block 2 is
   what makes the OS-follows case land on a consistent pair. The three blocks are
   mutually exclusive and jointly exhaustive, so no default is needed.

   Only the fourteen role tokens are repeated. Every Pico/app mapping is written
   ONCE, in the shared block below, in terms of those roles — which is the point
   of naming them by role in the first place. */

/* ===========================================================================
   SHARED — the mapping table, plus everything that is the same in both variants
   =========================================================================== */
:root[data-palette="halloween"] {
  /* OPTIONAL UI SOUND (X1, 2026-09-04): explicitly OFF, not merely undeclared
     — see matrix.css's identical note. chrome.css's base :root now defaults
     every unscoped theme to sound on with the chime voice; this theme wants
     a low, long-reverb `hollow` voice of its own eventually, and reusing
     chime in the meantime would just be the wrong costume, not an improvement
     on silence. BUILT 2026-09-05: `hollow` is that voice — struck like chime but
     a fourth lower and very wet (send 0.75 against chime's 0.32), with every
     note struck twice 0.8% apart so the pair beats at about 1.8Hz and the bar
     sounds like a dried gourd. It keeps the four shared gestures and, like
     chime, stays silent on tick/hover/focus/open: a long reverberant note is the
     wrong thing to fire on a keystroke. See VOICES.hollow in
     public/js/uiSound.js. */
  --theme-sound: 1;
  /* The candle flicker. See the MOTION section below. */
  --theme-motion: 1;
  --theme-voice: hollow;

  /* The sidebar is FIXED DARK CHROME IN BOTH VARIANTS, which is the base app's
     own rule (see the --sidebar-* comment in styles.css) and is why these live
     here rather than in the variant blocks. It is also what lets the corner
     cobweb and the candle glow be drawn once: they always sit on this ground.
     Measured against #130d1d:
       link      #cfc4dd  11.42:1
       ink       #efe9f6  16.01:1
       group lbl #9fb0a6   8.37:1
       active    #ffffff  19.04:1 (and 9.20:1 on the violet pill below) */
  --hw-night: #130d1d;
  --sidebar-bg: var(--hw-night);
  --sidebar-ink: #efe9f6;
  --sidebar-muted: #9fb0a6;
  --sidebar-link: #cfc4dd;
  --sidebar-link-active: #ffffff;
  --sidebar-hover-bg: #241a33;
  /* NOT the raw link violet: .sidebar-nav a.active hardcodes white label text in
     styles.css, so this ground has to carry white at AA. #5a2f96 measures
     9.20:1 against #ffffff. */
  --sidebar-active-bg: #5a2f96;

  /* ---- Pico mapping. Written once, in terms of the role tokens. ---- */
  --pico-background-color: var(--hw-ground);
  --pico-color: var(--hw-ink);
  --pico-muted-color: var(--hw-ink-2);
  --pico-h1-color: var(--hw-ink);
  --pico-h2-color: var(--hw-ink);
  --pico-h3-color: var(--hw-ink);
  --pico-h4-color: var(--hw-ink);
  --pico-h5-color: var(--hw-ink);
  --pico-h6-color: var(--hw-ink);

  --pico-primary: var(--hw-violet);
  --pico-primary-background: var(--hw-violet-deep);
  --pico-primary-hover: var(--hw-violet-hover);
  --pico-primary-hover-background: var(--hw-violet-deep-hover);
  --pico-primary-underline: var(--hw-violet-underline);
  --pico-primary-focus: var(--hw-violet-focus);
  --pico-primary-inverse: #ffffff;

  --pico-secondary: var(--hw-ink-2);
  --pico-secondary-background: var(--hw-surface-2);
  --pico-secondary-hover: var(--hw-ink);
  --pico-secondary-hover-background: var(--hw-rule);
  --pico-secondary-focus: var(--hw-violet-focus);
  --pico-secondary-inverse: var(--hw-ink);

  /* The status chips ARE set by this file now — see the SEMANTIC section near
     the foot. `<mark>` still is not, for the reason given in the header. */

  --pico-card-background-color: var(--hw-surface);
  --pico-card-sectioning-background-color: var(--hw-surface-2);
  --pico-card-border-color: var(--hw-rule-firm);
  --pico-border-color: var(--hw-rule-firm);
  --pico-muted-border-color: var(--hw-rule);

  --pico-form-element-background-color: var(--hw-surface-2);
  --pico-form-element-selected-background-color: var(--hw-surface-2);
  --pico-form-element-border-color: var(--hw-rule-firm);
  --pico-form-element-color: var(--hw-ink);
  --pico-form-element-placeholder-color: var(--hw-ink-3);
  --pico-form-element-active-background-color: var(--hw-surface);
  --pico-form-element-active-border-color: var(--hw-violet);
  --pico-form-element-focus-color: var(--hw-violet-focus);

  --pico-table-border-color: var(--hw-rule);
  --pico-table-row-stripped-background-color: var(--hw-stripe);

  --pico-code-background-color: var(--hw-void);
  --pico-code-color: var(--hw-violet);
  --pico-blockquote-border-color: var(--hw-violet);

  --pico-dropdown-background-color: var(--hw-surface);
  --pico-dropdown-border-color: var(--hw-rule-firm);
  --pico-dropdown-hover-background-color: var(--hw-surface-2);

  --pico-switch-background-color: var(--hw-rule-firm);
  --pico-switch-checked-background-color: var(--hw-violet-deep);
  --pico-range-thumb-color: var(--hw-violet);
}

/* ===========================================================================
   LIGHT — a moonlit page
   ===========================================================================
   Not "orange on cream", which is the costume-shop default and which would put
   the theme's signature hue straight into the reading path. The ground is a cool
   grey-lilac, the ink is deep aubergine, and the two working colours are a deep
   violet and a slate green. Ember survives only as decoration.

   Measured (WCAG 2.x contrast, computed not eyeballed):
     ink       #1d1527 on ground #f3f0f8  15.67:1   on card #ffffff  17.66:1
                                          on #e9e3f2 14.08:1  on #ded5ea 12.46:1
     muted     #4c3d61 on ground           8.68:1   on card           9.78:1
               (this is <small>, the field hints and the report caveats)
     link      #6b2ea8 on ground           7.22:1   on card           8.14:1
                                          on #e9e3f2  6.48:1
                                          on #ded5ea  5.74:1  <- the sticky
               table header ground, which is the case that actually set this
               value; the obvious lighter violet failed here and nowhere else.
     moon      #37564a on ground           7.19:1   on #ded5ea        5.71:1
     ember     #b8480e on ground           4.69:1   on card           5.29:1
               (decoration, held to the text bar anyway)
     button    #ffffff on #5a2593          9.90:1
     placeholder #6a5c80 on card           6.08:1 */
:root[data-palette="halloween"]:not([data-theme="dark"]) {
  --hw-void: #ded5ea;
  --hw-ground: #f3f0f8;
  --hw-surface: #ffffff;
  --hw-surface-2: #e9e3f2;
  --hw-ink: #1d1527;
  --hw-ink-2: #4c3d61;
  --hw-ink-3: #6a5c80;
  --hw-rule: #d9d0e6;
  --hw-rule-firm: #b6a8c9;
  --hw-violet: #6b2ea8;
  --hw-violet-hover: #52207f;
  --hw-violet-deep: #5a2593;
  --hw-violet-deep-hover: #4a1c7c;
  --hw-violet-underline: rgba(107, 46, 168, 0.42);
  --hw-violet-focus: rgba(107, 46, 168, 0.28);
  --hw-moon: #37564a;
  --hw-ember: #b8480e;
  --hw-stripe: rgba(90, 37, 147, 0.05);

  /* The base zebra overlay is rgba(120, 100, 70, 0.07) — a warm brown tuned for
     the Street Culture paper ground. Over a lilac page it reads as a stain, so
     the stripe is re-tinted to the palette's own violet. */
  --scroll-shadow-color: rgba(29, 21, 39, 0.2);

  /* ---- SEMANTIC STATE, LIGHT. The heat ramp from the header comment. ----
     Ratios are computed, not eyeballed, and are quoted against the surface each
     chip actually sits on: the two zebra rows. A row is the page ground
     (#f3f0f8) on odd rows and that ground composited with --hw-stripe on even
     ones, which is #ebe6f3 — the WORSE of the two for every chip here, so it is
     the one the numbers below are held to. `ink/pill` is the readable-text
     figure (the pill is opaque, so this is what AA actually applies to);
     `ink/row` is the same text measured against the bare row as well, so the
     chips clear 4.5:1 on either reading of the constraint.

     name              ink/pill   ink/row(even)   pill/row(even) = how loud
     good     moon       7.60         8.12            1.07
     hand     candle     6.64         6.91            1.04   <- quietest, 341 rows
     warn     amber      6.29         6.99            1.11
     bad      ember      5.48         6.78            1.24
     worst    blood      5.74         9.35            1.63   <- loudest
     closed   stone      7.36         8.04            1.09
     buried   ash        7.84         9.53            1.22  (achromatic: dead,
                                                            not alarming)      */
  --hw-good-bg: #c6e9d7;
  --hw-good-ink: #0e4c39;
  --hw-good-edge: #93d1b5;
  --hw-hand-bg: #ece3d0;
  --hw-hand-ink: #5d4a2c;
  --hw-warn-bg: #fbd991;
  --hw-warn-ink: #6b4405;
  --hw-bad-bg: #f9c8a8;
  --hw-bad-ink: #85360a;
  --hw-worst-bg: #f1a49b;
  --hw-worst-ink: #711712;
  --hw-worst-edge: #dd8175;
  --hw-closed-bg: #e1dde5;
  --hw-closed-ink: #45424a;
  --hw-buried-bg: #d6d2da;
  --hw-buried-ink: #3a373f;

  /* The overdue filter chip has NO fill until it is selected, so its ink is
     measured against the page itself: 7.80 on the ground, 8.79 on a card, 7.01
     on --hw-surface-2. Border 4.60 on the ground; selected fill carries white
     at 7.26. */
  --hw-alert-ink: #8f2019;
  --hw-alert-line: #b8493a;
  --hw-alert-fill: #a3291d;

  /* Bar fills need ~3:1 against their surface, not text contrast — the same
     reasoning styles.css gives for its own chart colours. Worst case of the
     three surfaces (ground / card / --hw-surface-2) is quoted. */
  --hw-bar-good: #16745a;        /* 4.55 */
  --hw-bar-hand: #8a7346;        /* 3.62 */
  --hw-bar-warn: #9e7310;        /* 3.41 */
  --hw-bar-bad: #a8500f;         /* 4.38 */
  --hw-bar-worst: #9c2a22;       /* 6.05 */
  --hw-bar-closed: #6f6a76;      /* 4.18 */
  --hw-bar-buried: #56515c;      /* 6.14 */
}

/* ===========================================================================
   DARK — the small hours
   ===========================================================================
   A violet-black rather than a neutral one: #100b18 has enough blue-red in it
   that the amber and salmon status chips inherited from styles.css sit on it as
   warm marks rather than dissolving into it, which is the whole reason those
   chips are left alone.

   Measured:
     ink       #ece6f4 on ground #100b18  15.87:1  on surface #191122 15.02:1
                                          on #221830 13.85:1  on #0b0713 16.31:1
     muted     #b3a6c4 on ground           8.46:1  on surface          8.01:1
     link      #c79dff on ground           8.95:1  on surface          8.47:1
                                          on #221830 7.81:1  on #0b0713 9.20:1
     moon      #a9bab0 on ground           9.54:1  on #0b0713          9.81:1
     ember     #ff8a3d on ground           8.27:1  on surface          7.82:1
     button    #ffffff on #5a2f96          9.20:1
     placeholder #9184a3 on #221830        4.86:1

   THE TWO DARK BLOCKS ARE IDENTICAL AND MUST STAY SO. See the header comment:
   one covers "the OS is dark and the user never touched the switch", the other
   covers "the user chose dark". They cannot be merged — a media query and a bare
   selector cannot share a selector list. */
@media (prefers-color-scheme: dark) {
  :root[data-palette="halloween"]:not([data-theme="light"]) {
    --hw-void: #0b0713;
    --hw-ground: #100b18;
    --hw-surface: #191122;
    --hw-surface-2: #221830;
    --hw-ink: #ece6f4;
    --hw-ink-2: #b3a6c4;
    --hw-ink-3: #9184a3;
    --hw-rule: #2b2038;
    --hw-rule-firm: #3f3053;
    --hw-violet: #c79dff;
    --hw-violet-hover: #ddc0ff;
    --hw-violet-deep: #5a2f96;
    --hw-violet-deep-hover: #6d3ab0;
    --hw-violet-underline: rgba(199, 157, 255, 0.45);
    --hw-violet-focus: rgba(199, 157, 255, 0.32);
    --hw-moon: #a9bab0;
    --hw-ember: #ff8a3d;
    --hw-stripe: rgba(199, 157, 255, 0.045);

    /* Light, not darker: there is no headroom below #100b18 for a black fade to
       register — the same reasoning styles.css gives for its own dark ground. */
    --scroll-shadow-color: rgba(236, 230, 244, 0.16);

    /* Semantic state, dark — byte-identical to the block below it. */
    --hw-good-bg: #0d3a2d;
    --hw-good-ink: #7fd9b6;
    --hw-good-edge: #1d6a54;
    --hw-hand-bg: #2a221a;
    --hw-hand-ink: #cdba97;
    --hw-warn-bg: #48320a;
    --hw-warn-ink: #ffd583;
    --hw-bad-bg: #5a290b;
    --hw-bad-ink: #ffad7d;
    --hw-worst-bg: #711b16;
    --hw-worst-ink: #ffb2a8;
    --hw-worst-edge: #a33026;
    --hw-closed-bg: #232128;
    --hw-closed-ink: #a5a0ae;
    --hw-buried-bg: #1c1b21;
    --hw-buried-ink: #8e8996;
    --hw-alert-ink: #ff9d92;
    --hw-alert-line: #c25a4a;
    --hw-alert-fill: #b03026;
    --hw-bar-good: #3fc094;
    --hw-bar-hand: #a89268;
    --hw-bar-warn: #dda226;
    --hw-bar-bad: #ea7534;
    --hw-bar-worst: #e2504a;
    --hw-bar-closed: #7d7788;
    --hw-bar-buried: #6d6777;
  }
}

:root[data-palette="halloween"][data-theme="dark"] {
  --hw-void: #0b0713;
  --hw-ground: #100b18;
  --hw-surface: #191122;
  --hw-surface-2: #221830;
  --hw-ink: #ece6f4;
  --hw-ink-2: #b3a6c4;
  --hw-ink-3: #9184a3;
  --hw-rule: #2b2038;
  --hw-rule-firm: #3f3053;
  --hw-violet: #c79dff;
  --hw-violet-hover: #ddc0ff;
  --hw-violet-deep: #5a2f96;
  --hw-violet-deep-hover: #6d3ab0;
  --hw-violet-underline: rgba(199, 157, 255, 0.45);
  --hw-violet-focus: rgba(199, 157, 255, 0.32);
  --hw-moon: #a9bab0;
  --hw-ember: #ff8a3d;
  --hw-stripe: rgba(199, 157, 255, 0.045);
  --scroll-shadow-color: rgba(236, 230, 244, 0.16);

  /* ---- SEMANTIC STATE, DARK. Same ramp; see the light block for the method.
     Rows here are #100b18 (odd) and #181222 (even, ground + --hw-stripe); the
     EVEN row is the worse case for every chip in this variant, both for the
     pill against the row and for the ink against it, so it is what is quoted.

     name              ink/pill   ink/row(even)   pill/row(even) = how loud
     good     moon       7.53        10.89            1.45
     hand     candle     8.25         9.64            1.17   <- quietest, 341 rows
     warn     amber      8.70        13.15            1.51
     bad      ember      6.56        10.04            1.53
     worst    blood      6.51        10.61            1.63   <- loudest
     closed   stone      6.24         7.17            1.15
     buried   ash        5.02         5.37            1.07                     */
  --hw-good-bg: #0d3a2d;
  --hw-good-ink: #7fd9b6;
  --hw-good-edge: #1d6a54;
  --hw-hand-bg: #2a221a;
  --hw-hand-ink: #cdba97;
  --hw-warn-bg: #48320a;
  --hw-warn-ink: #ffd583;
  --hw-bad-bg: #5a290b;
  --hw-bad-ink: #ffad7d;
  --hw-worst-bg: #711b16;
  --hw-worst-ink: #ffb2a8;
  --hw-worst-edge: #a33026;
  --hw-closed-bg: #232128;
  --hw-closed-ink: #a5a0ae;
  --hw-buried-bg: #1c1b21;
  --hw-buried-ink: #8e8996;

  /* Unfilled chip: ink 9.69 on the ground, 9.16 on a card, 8.45 on
     --hw-surface-2. Border 4.48 on the ground; selected fill carries white at
     6.37. */
  --hw-alert-ink: #ff9d92;
  --hw-alert-line: #c25a4a;
  --hw-alert-fill: #b03026;

  --hw-bar-good: #3fc094;        /* 7.39 */
  --hw-bar-hand: #a89268;        /* 5.62 */
  --hw-bar-warn: #dda226;        /* 7.47 */
  --hw-bar-bad: #ea7534;         /* 5.71 */
  --hw-bar-worst: #e2504a;       /* 4.41 */
  --hw-bar-closed: #7d7788;      /* 3.92 */
  --hw-bar-buried: #6d6777;      /* 3.11 */
}

/* ===========================================================================
   STRUCTURE
   =========================================================================== */

/* The section-heading mark. VIOLET, not ember, and that is the orange rule
   applied rather than a taste call: an h2 sits directly above a table, so a
   3px ember bar would put the theme's signature hue a few pixels from the
   amber chips it must not compete with. */
:root[data-palette="halloween"] main h2 {
  padding-left: 0.7rem;
  border-left: 3px solid var(--hw-violet);
}

/* Column headings in moonlight, small-capped and tracked. This is the theme's
   one typographic move (no display face is loaded — see the header), and it is
   applied only where the text is a LABEL rather than content.

   `thead th` MUST carry an opaque background: styles.css makes every table
   header sticky and explicitly relies on each theme providing one ("ONE RULE,
   NO PER-THEME COPIES" — the header would otherwise be transparent and rows
   would scroll visibly underneath it).

   The sort control inside the th declares `color/font/text-transform/letter-
   spacing: inherit`, so it picks all of this up; the SORTED column then takes
   --pico-primary (violet) from the base stylesheet, measured 9.20:1 on the dark
   header ground and 5.74:1 on the light one. */
:root[data-palette="halloween"] thead th {
  background: var(--hw-void);
  color: var(--hw-moon);
  text-transform: uppercase;
  letter-spacing: 0.09em;
  font-size: 0.72rem;
}

:root[data-palette="halloween"] .sidebar-group-label {
  letter-spacing: 0.11em;
}

/* Figures line up in a column. Tabular numerals only — deliberately NOT a mono
   family swap, which would be a second typographic idea competing with the
   heading treatment on a page whose job is to be read. */
:root[data-palette="halloween"] .stat-tile-value,
:root[data-palette="halloween"] .stat-summary-item strong,
:root[data-palette="halloween"] .numeric-cell,
:root[data-palette="halloween"] .numeric-input,
:root[data-palette="halloween"] .chart-value {
  font-variant-numeric: tabular-nums;
}

/* The base zebra rule is
   `tbody tr:nth-child(even):not(.report-subtotal-row):not(.report-total-row) td`
   = (0,3,3), and it hardcodes a warm brown that reads as a stain over a violet
   ground. Prefixing the palette selector makes this (0,4,4) so it wins on
   specificity rather than on this file happening to load second — the
   equal-specificity trap styles.css says it has been bitten by twice. The
   :not()s are kept for the reason that rule states: without them a rollup row
   landing on an even index loses its "this is a summary line" ground. */
:root[data-palette="halloween"] tbody tr:nth-child(even):not(.report-subtotal-row):not(.report-total-row) td,
:root[data-palette="halloween"] tbody tr:nth-child(even):not(.report-subtotal-row):not(.report-total-row) th {
  background-color: var(--hw-stripe);
}

/* ===========================================================================
   SEMANTIC — the fourteen status chips, the two banners, the overdue chip
   ===========================================================================
   Re-pitched 2026-08-29 under the reversed rule; see the header comment for the
   ramp and themes/README.md for the four constraints. Three mechanical things
   about this section, each of which has a wrong version that only shows up in
   one variant or one screen:

   ONE RULE PER FAMILY, NOT THREE. styles.css defines every one of these chips
   THREE times — a bare rule for light, one inside
   `@media (prefers-color-scheme: dark) :root:not([data-theme="light"]) .x`, and
   one at `:root[data-theme="dark"] .x`. A theme that overrides only the bare
   rule recolours the light variant and leaves both dark paths on the app's
   teal/amber/salmon, and the OS-follows path is the one nobody tests because it
   needs a machine in dark mode with the switch never touched. That failure is
   made unrepresentable here by putting the colours in the THREE VARIANT BLOCKS
   ABOVE as tokens — which already exist, already cover all three cases, and are
   already asserted to be exhaustive — and writing each rule below exactly once
   against a variable. There is no way for a rule here to cover one variant and
   miss another.

   SPECIFICITY IS WON, NOT BORROWED, WHERE IT CAN BE. The strongest base rule is
   `:root[data-theme="dark"] .status-available`, which is (0,3,0) — a bare
   `:root[data-palette="halloween"] .status-available` only ties it and would
   depend on load order. `views/partials/statusBadge.ejs` is the ONLY thing in
   the app that emits a `status-*` class and it always emits `status-badge`
   alongside, so `.status-badge.status-available` takes these to (0,4,0) and
   they win outright. `.filter-chip-alert` (always beside `.filter-chip`, both
   places it is used) and `.chart-bar-*` (always beside `.chart-bar`) get the
   same treatment.

   THE TWO BANNERS ARE THE EXCEPTION AND STAY AT (0,3,0). `[role="alert"]` and
   `.flash-success` have no reliable second class: the flash partial renders
   `p.flash.flash-success`, but `views/backups/index.ejs` renders a bare
   `<p class="flash-success">`, so keying on `.flash` would recolour the toast
   and quietly miss the stale-backup notice. They tie the base dark rules and
   win on source order, which is the theme layer's documented contract
   (themes/README.md, "Load order and the cascade") and not an accident.
   `.toast` needs nothing: styles.css takes its colour from these two, and its
   close button is already `color: inherit`. */

/* GOOD — moonlight. Cool, calm, and the only cool chip in the set, which is
   what separates it from `checked out` at a glance in a 394-row table. Not
   violet: constraint 2 forbids the accent here, and a chip the colour of a link
   reads as "on brand" rather than "this is fine". */
:root[data-palette="halloween"] .status-badge.status-available,
:root[data-palette="halloween"] .status-badge.status-approved,
:root[data-palette="halloween"] .status-badge.status-received {
  background: var(--hw-good-bg);
  color: var(--hw-good-ink);
}

/* IN HAND — candlelight. THIS IS THE CHIP THAT MATTERS: 341 of 397 assets are
   checked out, so whatever it wears is the colour of every screen. It is
   deliberately split out of the caution tier it shares with `pending`/`ordered`
   in the base app, because being issued to a member of staff is the resting
   state of this inventory and not a thing anyone needs to act on — most of it
   is handed out indefinitely (see lib/checkoutStatus.js).

   It is the QUIETEST LIVE chip in both variants by construction: its pill is
   1.04:1 against the light zebra row and 1.17:1 against the dark one, below
   every state that is still in play, so a screen of them reads as texture
   rather than as 341 alarms. Only the closed states go quieter, and only in
   dark (stone 1.15, ash 1.07) — a finished record should recede further than a
   live one. It is still not silent — a near-neutral warm ground against a
   violet-lilac page is visible as warmth — and its ink clears 4.5:1 twice over,
   on the pill and on the bare row. What tells it from `available` is
   temperature, not brightness: warm parchment against cool moonlight survives
   both a glance and a dichromat's rendering of the pair, which two greens of
   different lightness would not. */
:root[data-palette="halloween"] .status-badge.status-checked_out {
  background: var(--hw-hand-bg);
  color: var(--hw-hand-ink);
}

/* CAUTION — candle-amber. Something is in progress and will need a second
   look. `in_maintenance` moves here from the base app's salmon, because an item
   away being fixed is a caution and not a loss; the two procurement stages that
   are genuinely mid-flight join it. */
:root[data-palette="halloween"] .status-badge.status-in_maintenance,
:root[data-palette="halloween"] .status-badge.status-pending,
:root[data-palette="halloween"] .status-badge.status-ordered {
  background: var(--hw-warn-bg);
  color: var(--hw-warn-ink);
}

/* BAD — ember. `not_working` moves here out of the base app's GREY, which is
   the one re-pitch in this file that changes what a chip means rather than
   which hue says it: a broken asset is bad news and grey said "inert". */
:root[data-palette="halloween"] .status-badge.status-not_working,
:root[data-palette="halloween"] .status-badge.status-rejected {
  background: var(--hw-bad-bg);
  color: var(--hw-bad-ink);
}

/* WORST — blood. `lost` is the only asset state that means the organisation
   cannot account for something it owns, so it is the loudest chip on the page
   in both variants — 1.63:1 against the row, ahead of `not working` (1.24
   light / 1.53 dark) and a caution (1.11 / 1.51) — as well as the reddest.
   Loud AND hot: constraint 3 is carried twice over rather than resting on hue
   alone. */
:root[data-palette="halloween"] .status-badge.status-lost {
  background: var(--hw-worst-bg);
  color: var(--hw-worst-ink);
}

/* CLOSED — gravestone. Disposed, donated and cancelled are finished records,
   so they are achromatic: nothing to act on, and no temperature to read as
   good or bad. `donated` sits a shade deeper than the other two, matching the
   base app's own two-weight grey. */
:root[data-palette="halloween"] .status-badge.status-disposed,
:root[data-palette="halloween"] .status-badge.status-cancelled {
  background: var(--hw-closed-bg);
  color: var(--hw-closed-ink);
}

:root[data-palette="halloween"] .status-badge.status-donated {
  background: var(--hw-buried-bg);
  color: var(--hw-buried-ink);
}

/* The flash banners and the asset form's validation summary. Same two ends of
   the ramp as the chips — an error is the `lost` blood, a confirmation is the
   `available` moonlight — so a save that worked and a save that did not are the
   same two colours here as everywhere else in the app. */
:root[data-palette="halloween"] [role="alert"] {
  background: var(--hw-worst-bg);
  color: var(--hw-worst-ink);
  border-color: var(--hw-worst-edge);
}

:root[data-palette="halloween"] .flash-success {
  background: var(--hw-good-bg);
  color: var(--hw-good-ink);
  border-color: var(--hw-good-edge);
}

/* The overdue filter chip on /checkouts. Unlike every chip above it has NO
   fill until it is selected, so its ink is the thing measured against the page
   rather than against a pill — 7.80:1 light, 9.69:1 dark. The class is applied
   only when the count is non-zero, so this really is an alarm and takes the
   blood end of the ramp. */
:root[data-palette="halloween"] .filter-chip.filter-chip-alert {
  color: var(--hw-alert-ink);
  border-color: var(--hw-alert-line);
}

/* Hover keeps the alarm's own hue. Without this the base `.filter-chip:hover`
   rule would have nothing to say — it hard-codes SCP's teal, which the (0,4,0)
   rule above already outranks — so the chip would lose its hover feedback
   entirely rather than gain a stray brand colour. */
:root[data-palette="halloween"] .filter-chip.filter-chip-alert:hover,
:root[data-palette="halloween"] .filter-chip.filter-chip-alert:focus-visible {
  border-color: var(--hw-alert-ink);
}

/* `color` is restated here and is NOT redundant. The rule above is (0,4,0) and
   base's `.filter-chip-active { color: #ffffff }` is (0,2,0), so once the chip
   is selected the blood INK would out-specify the white and render blood on a
   blood fill — measured 1.0:1, i.e. an invisible count on the one chip whose
   entire job is showing a number. White on the fill is 7.26:1 light, 6.37:1
   dark, and the count inherits it through base's own rule. */
:root[data-palette="halloween"] .filter-chip.filter-chip-alert.filter-chip-active {
  background: var(--hw-alert-fill);
  border-color: var(--hw-alert-fill);
  color: #ffffff;
}

/* The dashboard's "Assets by status" bars, which are the same vocabulary in
   another shape and sit on the same screen as the chips above — leaving them
   would have left SCP's brand teal as the last unre-pitched semantic colour in
   the theme, directly beside a moonlight `available` chip. A solid bar needs
   ~3:1 against its surface rather than text contrast (styles.css says so and
   keeps its own separate set for exactly this reason), so these are their own
   tokens and not the chips' inks reused.

   `.chart-bar-category` is NOT a status — it is the neutral series colour for
   the by-category and by-condition charts — so it is the one bar that may, and
   should, be the accent. */
:root[data-palette="halloween"] .chart-bar.chart-bar-available { background: var(--hw-bar-good); }
:root[data-palette="halloween"] .chart-bar.chart-bar-checked_out { background: var(--hw-bar-hand); }
:root[data-palette="halloween"] .chart-bar.chart-bar-in_maintenance { background: var(--hw-bar-warn); }
:root[data-palette="halloween"] .chart-bar.chart-bar-not_working { background: var(--hw-bar-bad); }
:root[data-palette="halloween"] .chart-bar.chart-bar-lost { background: var(--hw-bar-worst); }
:root[data-palette="halloween"] .chart-bar.chart-bar-disposed { background: var(--hw-bar-closed); }
:root[data-palette="halloween"] .chart-bar.chart-bar-donated { background: var(--hw-bar-buried); }
:root[data-palette="halloween"] .chart-bar.chart-bar-category { background: var(--hw-violet); }

/* Filter chips (/assets, /checkouts, report pages). styles.css hard-codes the
   SCP brand teal #00838f as both the hover border and the SELECTED fill on every
   theme. Material already re-points the hover for being a stray off-palette
   accent; here the fill matters more, because teal in this palette appears
   nowhere else EXCEPT on `.status-available` — so a selected "All 394" chip
   rendered as a teal pill directly above a row of status chips, reading as a
   status rather than as "this filter is on". Measured white on #5a2f96: 9.20:1.

   `:not(.filter-chip-alert)` is not decoration, and its REASON changed on
   2026-08-29 while the selector did not. It used to mean "the overdue chip's
   salmon is semantic and this file must not touch it". It now means "the
   overdue chip is re-pitched in the SEMANTIC section above and must not be
   overwritten with the theme's violet here" — the violet is "this filter is
   on", the blood is "there is something overdue", and a selected overdue chip
   has to say the second. Same guard, opposite direction, still load-bearing:
   the base `.filter-chip-active` rule is (0,2,0), the same as a bare
   palette-prefixed one, so excluding the alarm by hand rather than by source
   order is what keeps the two from fighting. */
:root[data-palette="halloween"] .filter-chip-active:not(.filter-chip-alert) {
  background: var(--hw-violet-deep);
  border-color: var(--hw-violet-deep);
}

:root[data-palette="halloween"] .filter-chip:not(.filter-chip-alert):hover,
:root[data-palette="halloween"] .filter-chip:not(.filter-chip-alert):focus-visible {
  border-color: var(--hw-violet);
}

/* Focus in ember — the one place orange is allowed near data, and only because
   it is transient, singular, drawn outside the box and never a property of a
   record. A violet ring would be the link colour ringing a link. */
:root[data-palette="halloween"] :focus-visible {
  outline: 2px solid var(--hw-ember);
  outline-offset: 2px;
}

/* ===========================================================================
   THE PROPS — three of them, all in chrome
   ===========================================================================

   Chosen for restraint over volume. A carved pumpkin, a corner cobweb and a
   candle glow, each in exactly one kind of place. Everything is a ::before /
   ::after or a background-image on an element that already exists, because the
   theme lives in localStorage and the server cannot know it when it renders —
   there is no per-theme markup available and none is wanted. */

/* --- 1. THE CORNER COBWEB ---------------------------------------------------
   Top-left corner of the sidebar: the corner of the whole application frame,
   and the one large surface that never holds data. The sidebar is dark in BOTH
   variants, so the web is drawn once.

   An SVG rather than the radial-gradient version, and the reason is visible at a
   glance: concentric circles read as a radar screen. Five spokes with three
   SAGGING strands between them (quadratic curves whose control points sit at
   0.78r along the bisector) read as a web. Geometry is generated, not eyeballed.

   position: absolute inside .sidebar is safe and was checked against the two
   documented hazards in themes/README.md — .sidebar is `overflow: visible` (the
   scrolling lives on the inner .sidebar-scroll, for a WebKit reason spelled out
   in styles.css), and nothing here introduces a transform/filter/contain, so the
   fixed Appearance panel keeps the viewport as its containing block.
   `pointer-events: none` so it can never eat a click on the brand link under it,
   and z-index 0 keeps it behind the nav. No `position` is set on .sidebar here:
   styles.css already makes it `position: sticky` (it has to be, to pin to the
   viewport height), which is what this absolute box resolves against.

   A DECORATION SITTING BEHIND TEXT STILL HAS TO BE MEASURED. The brand subtitle
   and the top nav links fall inside this 132px box, so the worst case is a
   letter landing exactly on a strand: #a9a0b6 at 0.3 over the #130d1d sidebar
   composites to #40394b, against which the subtitle (#9fb0a6) is 4.85:1 and a
   nav link (#cfc4dd) is 6.61:1. Both clear AA with the web at its densest. */
:root[data-palette="halloween"] .sidebar::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 132px;
  height: 132px;
  z-index: 0;
  pointer-events: none;
  opacity: 0.3;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 120'%3E%3Cg fill='none' stroke='%23a9a0b6' stroke-width='1.1' stroke-linecap='round'%3E%3Cpath d='M0 0L118 0M0 0L109 45.2M0 0L83.4 83.4M0 0L45.2 109M0 0L0 118'/%3E%3Cpath d='M34 0Q26 5.2 31.4 13Q22.1 14.7 24 24Q14.7 22.1 13 31.4Q5.2 26 0 34M66 0Q50.5 10 61 25.3Q42.8 28.6 46.7 46.7Q28.6 42.8 25.3 61Q10 50.5 0 66M100 0Q76.5 15.2 92.4 38.3Q64.9 43.3 70.7 70.7Q43.3 64.9 38.3 92.4Q15.2 76.5 0 100'/%3E%3C/g%3E%3C/svg%3E");
}

/* The brand and nav sit above the web rather than under it. .sidebar-scroll is
   already the sidebar's other child; giving it a stacking position is enough. */
:root[data-palette="halloween"] .sidebar-scroll {
  position: relative;
  z-index: 1;
}

/* --- 2. THE CANDLE GLOW -----------------------------------------------------
   A warm ember wash at the foot of the sidebar, under the Appearance row. This
   is the theme's biggest Halloween gesture and it is deliberately spent on the
   surface that carries no data at all.

   The gradient is layered OVER --sidebar-bg rather than replacing it: the
   footer's background is load-bearing in styles.css ("it guarantees nothing
   shows through behind the footer's own padding"), so the opaque colour has to
   stay underneath.

   ONE DECLARATION, AND DELIBERATELY NOTHING ELSE. The fixed Appearance panel
   opens from inside this element, so a transform / filter / contain here would
   make this box its containing block and strand it inside a 224px column — the
   exact failure apple.css records measuring. A background needs none of those,
   and no `position` either, so none is set.

   Measured, because the Appearance trigger's label sits on this: the gradient
   peaks at 0.3 alpha well BELOW the text row, but even taking that maximum,
   #ff8a3d at 0.3 over #130d1d composites to #5a331f and the trigger's #cfc4dd
   is 6.53:1 against it (9.97:1 at the ~0.1 the label actually sits in). */
:root[data-palette="halloween"] .sidebar-footer {
  background: radial-gradient(120% 150% at 22% 128%, rgba(255, 138, 61, 0.3) 0%, rgba(255, 138, 61, 0.1) 42%, transparent 70%) var(--sidebar-bg);
}

/* --- 3. THE JACK-O'-LANTERN -------------------------------------------------
   Two homes, both of them places where there is by definition nothing to
   obscure: an EMPTY STATE, and the signed-out sign-in page.

   THE EMPTY-STATE HOOK IS A SIBLING SELECTOR BECAUSE THE DOM HAS NO EMPTY-STATE
   CLASS, and adding one would be a shared-markup change, which themes/README.md
   puts outside a theme's remit. The three selectors were checked against the
   rendered HTML of every signed-in page rather than guessed: `main h2 + p`
   matches seven paragraphs across the app and all seven are empty states
   ("Nothing overdue.", "Nothing due.", "Nothing out for repair.", "Nothing past
   its expected useful life.", "Nothing due back in the next 7 days.",
   "No maintenance scheduled." on an asset, "No imports yet."); `.list-toolbar +
   p` is the filtered-to-nothing state on /assets and /checkouts;
   `.report-range-form + p` is "No results in this date range." on a report.
   The known limitation, stated rather than hidden: a future <h2> followed by an
   INTRO paragraph would also get a pumpkin. It would be charming rather than
   wrong, and the fix would be one class in the shared markup.

   The pumpkin is a lit carving, not an emoji: the eyes/nose/mouth are cut out in
   near-black so the shape survives at 28px on either ground, which a glowing
   yellow face does not (measured 1.7:1 against the body). */
:root[data-palette="halloween"] main h2 + p,
:root[data-palette="halloween"] .list-toolbar + p,
:root[data-palette="halloween"] .report-range-form + p {
  min-height: 1.85rem;
  padding-left: 2.5rem;
  background-repeat: no-repeat;
  background-position: left top -0.05rem;
  background-size: 1.85rem 1.85rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath d='M14.9 9.8c-.5-3.4.6-5.7 3.5-6.8.8-.3 1.3.8.6 1.2-1.9 1.2-2.5 3-2.2 5.6z' fill='%234f6b33'/%3E%3Cellipse cx='16' cy='19.6' rx='13.4' ry='10.4' fill='%23c25c11'/%3E%3Cellipse cx='16' cy='19.6' rx='9.2' ry='10.4' fill='%23e2711d'/%3E%3Cpath d='M8.9 14.6l5 1.8-2.4 3.6z' fill='%232a1004'/%3E%3Cpath d='M23.1 14.6l-5 1.8 2.4 3.6z' fill='%232a1004'/%3E%3Cpath d='M16 17.6l1.8 3.4h-3.6z' fill='%232a1004'/%3E%3Cpath d='M8.4 22.3Q16 21.2 23.6 22.3L22 25.9 19.9 23.9 17.8 26.5 16.1 24.1 14.3 26.5 12.2 23.9 10.4 25.9Z' fill='%232a1004'/%3E%3C/svg%3E");
}

/* The sign-in page. .login-brand-subtitle is the "Asset Manager" wordmark under
   the logo on views/auth/login-dev.ejs; a block ::after under it is a pumpkin
   sitting on the page, with a candle halo behind it.

   THIS IS THE ONLY THING IN THE THEME THAT MOVES, and the placement is the
   argument: a flicker in the sidebar would sit in peripheral vision for the
   whole working day, which is exactly the vestibular problem the brief names.
   /login is on screen for two seconds. The animation is a slow opacity breath
   with no motion and no transform, and it is switched off entirely under
   prefers-reduced-motion at the foot of this file. */
:root[data-palette="halloween"] .login-brand-subtitle::after {
  content: "";
  display: block;
  width: 6rem;
  height: 6rem;
  margin: 0.8rem auto 0;
  background-repeat: no-repeat;
  background-position: center, center;
  background-size: 3.8rem 3.8rem, 100% 100%;
  /* THE HALO IS A RADIAL GRADIENT AND NOT A box-shadow, which is what this was
     first written as. A box-shadow is clipped OUT from under its own border
     box, so a glow drawn that way leaves a hard-edged disc of untinted page in
     the middle of itself — measured on both variants: a black disc behind the
     pumpkin in dark, a white one in light. A gradient paints the whole box and
     falls off smoothly through it. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath d='M14.9 9.8c-.5-3.4.6-5.7 3.5-6.8.8-.3 1.3.8.6 1.2-1.9 1.2-2.5 3-2.2 5.6z' fill='%234f6b33'/%3E%3Cellipse cx='16' cy='19.6' rx='13.4' ry='10.4' fill='%23c25c11'/%3E%3Cellipse cx='16' cy='19.6' rx='9.2' ry='10.4' fill='%23e2711d'/%3E%3Cpath d='M8.9 14.6l5 1.8-2.4 3.6z' fill='%232a1004'/%3E%3Cpath d='M23.1 14.6l-5 1.8 2.4 3.6z' fill='%232a1004'/%3E%3Cpath d='M16 17.6l1.8 3.4h-3.6z' fill='%232a1004'/%3E%3Cpath d='M8.4 22.3Q16 21.2 23.6 22.3L22 25.9 19.9 23.9 17.8 26.5 16.1 24.1 14.3 26.5 12.2 23.9 10.4 25.9Z' fill='%232a1004'/%3E%3C/svg%3E"),
    radial-gradient(circle at 50% 50%, rgba(255, 138, 61, 0.34) 0%, rgba(255, 138, 61, 0.16) 38%, rgba(255, 138, 61, 0) 68%);
  animation: hw-candle 5.5s ease-in-out infinite;
}

@keyframes hw-candle {
  0%, 100% { opacity: 1; }
  38% { opacity: 0.9; }
  52% { opacity: 0.97; }
  70% { opacity: 0.86; }
}

/* ===========================================================================
   MOTION
   ===========================================================================
   One rule, because there is one animation. A page that flickers at someone
   with vestibular sensitivity is not charming. */
:root[data-palette="halloween"][data-motion="off"] .login-brand-subtitle::after {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  :root[data-palette="halloween"] .login-brand-subtitle::after {
    animation: none;
  }
}

/* ===========================================================================
   NARROW
   ===========================================================================
   The app's one breakpoint (768px), where the sidebar becomes a slide-in
   drawer. The layout is unchanged by this theme, so only the props need a word:
   the cobweb shrinks with the drawer so it stays a corner ornament rather than a
   veil over a 260px panel, and the sign-in pumpkin comes down a size. */
@media (max-width: 768px) {
  :root[data-palette="halloween"] .sidebar::before {
    width: 88px;
    height: 88px;
  }

  :root[data-palette="halloween"] .login-brand-subtitle::after {
    width: 3.4rem;
    height: 3.4rem;
    background-size: 2.7rem 2.7rem;
  }
}
