/* Fredoka (self-hosted, variable weight): the rounded geometric face standing
   in for the original's wordmark/UI font. */
@font-face {
  font-family: "Fredoka";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("assets/fonts/fredoka-latin.woff2") format("woff2");
}

:root {
  /* Tokens sampled from the Domino Grove original — see
     assets/original-art-reference.md and the BOS-16 reference screenshots */
  --bg: #36895a;          /* background green after the multiply grade (original base) */
  --ink: #2b2b2c;         /* charcoal: body text + grid lines */
  --accent: #00a187;      /* teal-green brand accent ("GROVE") */
  --play: #fa446c;        /* pink START/DAILY menu buttons */
  --line: #2b2b2c;        /* thick dark grid lines */
  --cell: #eaebe9;        /* off-white board cells / card (post-fx it reads as the original's #f1f1ef) */
  --card: #eaebe9;        /* off-white board card */
  --date: #797b7a;        /* medium gray daily date in the card header */
  --menu-green: #00d46d;  /* bright green of the title-screen GROVE wordmark */
  /* Tree parts. The original's shadow color lives in Unity sprite art /
     serialized prefab fields, not the extracted C# source, so this is chosen
     to read as a soft ground shadow on the off-white card. The trunk takes the
     leaf (canopy) color. */
  --tree-shadow: rgba(43, 43, 44, 0.18); /* soft oval ground shadow */
  /* Tile palette (BOS-18): theme.js re-writes --tc1..--tc5 inline on <html>
     when a theme applies; these defaults are the grove palette so the page
     renders correctly even before the module runs. Tiles reference these via
     --tile-color: var(--tcN) set by WorldView._paintTileEl. */
  --tc1: #d94a63;  /* salmon-red */
  --tc2: #00a687;  /* teal */
  --tc3: #fea202;  /* amber */
  --tc4: #61a53c;  /* leaf green */
  --tc5: #c9c9c5;  /* wild — light gray */
  --tc6: #e8420e;  /* fire (BOS-22, Infinite mode) — flame red-orange */
  /* Structure tokens themes may vary (BOS-18): grid line weight, corner
     rounding, typeface, and the card's shadow treatment. */
  --grid-gap: 8px;
  --radius-card: 24px;
  --radius-board: 10px;
  --radius-tile: 4px;
  --font-body: "Fredoka", system-ui, "Segoe UI", Helvetica, Arial, sans-serif;
  --card-shadow: 5px 8px 0px rgba(0, 0, 0, 0.5);
  --home-border: #b9beba;
  /* Post-processing (applied when <body> has the .fx class; toggle it from
     the console for a before/after: document.body.classList.toggle("fx")).
     The bloom pass itself is the inline SVG filter #fx-bloom in index.html. */
  --fx-grade: saturate(1.07) contrast(1.03) brightness(1.02) hue-rotate(-2deg);
  --fx-vignette-color: rgba(18, 26, 15, 0.32);  /* near-black green at edges */
  --fx-vignette-start: 52%;                     /* radius where darkening begins */
  font-size: 16px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
}

body {
  /* Seamless leaf-brick tile; --bg matches its average green as a fallback
     while the image loads. Sized so a brick is 74px — the pitch measured in
     the BOS-16 fullscreen reference (the 300px tile holds 2×2 bricks). The
     multiply layer pulls the tile's too-light average (#6fac5a) down to the
     reference's #36895a (per-channel ratio ≈ 124/203/255). */
  background-color: var(--bg);
  background-image: linear-gradient(#7ccbff, #7ccbff), url("assets/bg-tile.png");
  background-blend-mode: multiply, normal;
  background-size: auto, 148px;
  background-repeat: repeat;
  color: var(--ink);
  font-family: var(--font-body);
  display: grid;
  place-items: center;
}

/* Post-processing chain: full-screen color grade (cheap CSS filter functions)
   on body, with the expensive SVG bloom pass scoped to the card — the green
   background never crosses the bloom threshold, so restricting the filter
   region to #app looks identical and repaints far less area per frame.
   Applying filter here makes body a containing block for fixed descendants,
   but body already spans the viewport (html, body { height: 100% }) so the
   fixed vignette still covers the full screen. */
body.fx {
  filter: var(--fx-grade);
}
body.fx #app {
  filter: url(#fx-bloom);
}

/* Vignette: last pass, drawn over everything, transparent in the middle and
   darkening toward the corners. Hidden entirely unless fx is on. */
.fx-vignette {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 100;
  background: radial-gradient(
    ellipse at center,
    transparent var(--fx-vignette-start),
    var(--fx-vignette-color) 100%
  );
  display: none;
}
body.fx .fx-vignette { display: block; }

/* Single off-white rounded card holds header + board + footer, as in the
   original. Sits on the green background with a hard-edged drop shadow. */
#app {
  position: relative;
  /* The original's card spans ~44% of a fullscreen 1919px capture (849px). */
  width: min(94vw, clamp(560px, 44.2vw, 880px));
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  padding: clamp(1.25rem, 2.3vw, 2.75rem);
  background: var(--card);
  border-radius: var(--radius-card);
  box-shadow: var(--card-shadow);
}

/* Title on top, score on its own line beneath it — both left-justified, as in
   the original, where the big bold score sits between the title and the board. */
.header {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
}

/* Wordmark on the left, daily date on the right, sharing the top line. */
.title-row {
  width: 100%;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.title-text {
  margin: 0;
  font-size: clamp(1.4rem, 3.3vw, 4rem);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.title-text .grove { color: var(--accent); }

/* The daily date, medium gray at the card's top-right (original daily mode). */
.date {
  color: var(--date);
  font-weight: 600;
  font-size: clamp(0.9rem, 1.5vw, 1.8rem);
  font-variant-numeric: tabular-nums;
}

/* The score outranks the title, so it renders larger and heavier — an ~80px
   digit on the fullscreen reference. */
.score {
  font-size: clamp(2.4rem, 6vw, 7rem);
  font-weight: 650;
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.stage { position: relative; }

/* Board is a grid sized from --cols/--rows set by WorldView.createBoard().
   The dark --line color is the board's own background showing through the
   gaps and padding, so the board reads as one continuous grid of lines —
   per-tile borders would leave holes where four rounded corners meet. */
.board {
  display: grid;
  grid-template-columns: repeat(var(--cols, 7), 1fr);
  grid-template-rows: repeat(var(--rows, 5), 1fr);
  gap: var(--grid-gap);      /* original: ~8px line between 72px cells */
  padding: var(--grid-gap);  /* outer line, same thickness as the interior lines */
  background: var(--line);
  border-radius: var(--radius-board);
  aspect-ratio: var(--cols, 7) / var(--rows, 5);
}

.tile {
  position: relative;
  background: var(--cell);
  border: none;
  border-radius: var(--radius-tile);  /* slight rounding on cell interiors, as in the original */
  padding: 0;
  cursor: pointer;
  user-select: none;
  will-change: transform;
}

.tile.empty { background: var(--cell); cursor: pointer; }
.tile.empty .tree { display: none; }

/* Placement preview: the two empty cells the held domino would fill are tinted
   with the grid-line color so the move reads before the click. Set by
   WorldView._updateHoverHighlight only when the placement is legal. */
.tile.preview-fill { background: var(--line); }

/* Scoring flash: a full-tile overlay that snaps to the group color (or dark
   gray for shape groups) and fades to clear. Sits under the tree (first child)
   so the canopy stays visible on top, matching the Unity flashSprite layering. */
.tile .flash {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-tile);  /* match the tile interior rounding */
  opacity: 0;                    /* GSAP drives this during a score blink */
  pointer-events: none;
  z-index: 1;
}

/* The point "dot" launched when a tile scores: a circle a little smaller than
   the round canopy (66% of the tile). Centered on the tile by GSAP
   (xPercent/yPercent -50) and animated up, then shrunk away. */
.tile .score-dot {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 52%;
  aspect-ratio: 1;
  border-radius: 50%;
  pointer-events: none;
  z-index: 5;
  will-change: transform, opacity;
}

/* A "tree": the colored canopy (the game shape) sits on a little trunk, with a
   soft oval shadow on the ground beneath it. Mirrors the Unity TileView, which
   stacks a shapeSprite (canopy) over a shadowSprite and pivots at the trunk
   base for the wind sway. The whole tree lives inside the tile so GSAP can
   scale the tile box during scoring and carry the tree with it. */
.tree {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  /* Bottom padding sized so the whole composition (canopy + trunk + ground
     shadow) reads as vertically centered in the tile, per the original art. */
  padding-bottom: 13%;
  pointer-events: none;
}
/* Canopy + trunk wrapper: the part of the tree that rotates during the wind
   sway (WorldView._swayDominoTrees). Fills the tree box so the canopy/trunk
   percentage sizes keep resolving against the tile, and pivots where the trunk
   meets the ground. The shadow sits outside it so it slides instead of tilting. */
.tree .sway {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  transform-origin: bottom center;
  will-change: transform;
}
.tree .shadow {
  position: absolute;
  bottom: 9%;
  width: 38%;
  height: 8%;
  background: var(--tree-shadow);
  border-radius: 50%;
  filter: blur(0.5px);
}
.tree .trunk {
  width: 6%;
  height: 26%;
  background: var(--tile-color, var(--accent));   /* trunk matches the leaves */
  border-radius: 1.5px 1.5px 0 0;
}
.tree .canopy {
  /* Heights resolve against the padded .tree box (87% of the tile), so they
     run a bit larger than widths to keep circles round and squares square. */
  width: 48%;
  height: 55%;
  margin-bottom: -2%;                /* tuck the trunk under the canopy */
  background: var(--tile-color, var(--accent));
}
.tree .canopy.circle   { border-radius: 50%; }
.tree .canopy.square   { border-radius: 10%; }
.tree .canopy.triangle {
  width: 44%; height: 62%;           /* taller than wide */
  clip-path: polygon(50% 0%, 98% 100%, 2% 100%);
}

/* Fire tile (BOS-22, Infinite mode): the tree keeps its shape for shape-group
   matching but smolders — a warm glow plus a slow brightness flicker so it
   reads as burning in every theme (Ink included, where color can't carry it). */
[data-ct="6"] .canopy,
[data-ct="6"] .trunk {
  animation: fire-flicker 0.9s ease-in-out infinite alternate;
}
[data-ct="6"] .canopy { filter: drop-shadow(0 0 6px var(--tc6)); }
@keyframes fire-flicker {
  from { filter: brightness(0.92) drop-shadow(0 0 4px var(--tc6)); }
  to   { filter: brightness(1.25) drop-shadow(0 0 9px var(--tc6)); }
}

/* Held-domino preview: a zero-size anchor that tracks the cursor (positioned by
   WorldView._positionDomino). The two pips are absolutely placed relative to the
   anchor — origin pip on it, hanging pip one cell away along the direction.
   pointer-events:none so the floating preview never blocks tile hover/clicks. */
.domino {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 2;
}
.domino .pip {
  position: absolute;
  width: 2.4rem;
  height: 2.4rem;
  margin: 0;
  /* center each pip on its own (left, top) anchor point */
  transform: translate(-50%, -50%);
}
/* The charcoal body behind the two pips: one connecting border wraps them into
   a single solid domino (sized/positioned by WorldView._layoutDominoBody).
   Corners only slightly rounded, and a small hard-edged drop shadow — same
   flat-shadow language as the card, but lighter. (BOS-14) */
.domino .body {
  position: absolute;
  background: var(--line);
  border-radius: 7px;
  box-shadow: 3px 4px 0 rgba(0, 0, 0, 0.3);
}

.tutorial {
  position: absolute;
  inset: auto 0 -2.25rem 0;
  text-align: center;
  margin: 0;
  font-style: italic;
  opacity: 0.8;
}

button {
  font: inherit;
  border: none;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}

/* Home button: an off-white rounded square pinned to the screen's top-left
   with the same hard offset shadow language as the board card. */
.home-button {
  position: fixed;
  top: clamp(20px, 4.7vh, 51px);
  left: clamp(20px, 3.2vw, 61px);
  z-index: 10;
  width: clamp(56px, 4.2vw, 80px);
  height: clamp(56px, 4.2vw, 80px);
  display: grid;
  place-items: center;
  background: var(--card);
  border: 3px solid var(--home-border);
  border-radius: 16px;
  box-shadow: 3px 4px 0 rgba(0, 0, 0, 0.35);
  color: var(--ink);
}
.home-button svg { width: 55%; height: 55%; }
.home-button:hover { transform: translateY(-1px); }
.home-button:active { transform: translateY(1px); box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.35); }

/* ── Title screen ─────────────────────────────────────────────────────────────
   Full-viewport menu over a dark forest green: the same leaf-brick tile as the
   game background, graded way down with a heavy center-out vignette, autumnal
   tree blobs flanking the edges, and the logo/menu stack in the middle. */
.title-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  overflow: hidden;
  background-image:
    radial-gradient(ellipse 90% 90% at 50% 42%,
      rgba(23, 112, 71, 0.86) 0%,
      rgba(16, 84, 60, 0.90) 45%,
      rgba(10, 48, 48, 0.96) 100%),
    url("assets/bg-tile.png");
  background-size: auto, 148px;
  background-color: #0d4a3a;
}

/* Decorative trees: irregular canopy blobs on thin trunks, rooted off the
   bottom edge, standing in for the original's painterly forest. */
.title-trees { position: absolute; inset: 0; }
.ttree {
  position: absolute;
  left: var(--x);
  bottom: -4%;
  width: var(--w);
  height: var(--h);
  filter: blur(1.5px) brightness(0.9);
  opacity: 0.85;
}
.ttree::before {           /* canopy */
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  aspect-ratio: 0.95;
  background: var(--c);
  border-radius: var(--r);
}
.ttree::after {            /* trunk: pale gray, as in the original's paintings */
  content: "";
  position: absolute;
  left: 50%;
  top: 30%;
  bottom: 0;
  width: 7px;
  transform: translateX(-50%);
  background: color-mix(in srgb, var(--c) 22%, #cfcfc6);
  border-radius: 3px;
}

.title-content {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.25rem;
  text-align: center;
}

.logo {
  margin: 0 0 0.25rem;
  font-size: clamp(2.6rem, 8.5vw, 6.5rem);
  font-weight: 600;
  letter-spacing: 0.03em;
  line-height: 1.1;
  color: #fcfcfa;
  text-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
  white-space: nowrap;
}
/* The domino outline around MIN — the wordmark's namesake tile. */
.logo .boxed {
  border: 0.045em solid currentColor;
  border-radius: 0.28em;
  padding: 0 0.08em;
  margin: 0 0.06em;
}
.logo-grove { color: var(--menu-green); }
/* The tree standing in for GROVE's O: a round canopy over a short trunk. */
.tree-o {
  display: inline-block;
  position: relative;
  width: 0.72em;
  height: 0.72em;
  margin: 0 0.05em;
}
.tree-o .tcanopy {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 82%;
  background: currentColor;
  border-radius: 50%;
}
.tree-o .ttrunk {
  position: absolute;
  left: 50%;
  bottom: -0.02em;
  width: 0.09em;
  height: 0.28em;
  transform: translateX(-50%);
  background: currentColor;
  border-radius: 0.03em;
}

.collab {
  margin: 0;
  color: #e4e4e1;
  font-weight: 600;
  font-size: clamp(1rem, 2.6vw, 1.9rem);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
}

.menu-buttons {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.4rem;
  margin-top: 2rem;
}
.menu-buttons button {
  background: var(--play);
  color: #fff;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-radius: 12px;
  box-shadow: 0 5px 0 rgba(0, 0, 0, 0.25);
}
.menu-buttons button:hover { filter: brightness(1.07); transform: translateY(-1px); }
.menu-buttons button:active { transform: translateY(2px); box-shadow: 0 2px 0 rgba(0, 0, 0, 0.25); }
.menu-start { font-size: clamp(1.4rem, 3vw, 2.2rem); padding: 0.55em 1.9em; }
.menu-daily { font-size: clamp(1.05rem, 2.2vw, 1.6rem); padding: 0.5em 1.6em; }
/* Infinite (BOS-22, experimental): same shape as DAILY, tinted fire-orange.
   Scoped under .menu-buttons to out-rank its shared background rule. */
.menu-buttons .menu-infinite { background: var(--tc6, #e8420e); }

/* Board-size picker (BOS-21): a row of small pills under START/DAILY. Inactive
   sizes sit ghosted in translucent dark; the selected one fills with the same
   pink as the start buttons. */
.size-picker {
  display: flex;
  gap: 0.6rem;
  margin-top: 0.2rem;
}
.menu-buttons .size-picker button {
  font-size: clamp(0.75rem, 1.4vw, 1.05rem);
  padding: 0.45em 1.1em;
  background: rgba(0, 0, 0, 0.3);
  box-shadow: 0 3px 0 rgba(0, 0, 0, 0.25);
  opacity: 0.85;
}
.menu-buttons .size-picker button.active {
  background: var(--play);
  opacity: 1;
}

/* Credits, bottom-left, with the pink "By" / "&" accents. */
.credits {
  position: absolute;
  left: 2rem;
  bottom: 1.75rem;
  color: #f1f1ef;
  font-weight: 600;
  font-size: clamp(0.95rem, 1.9vw, 1.5rem);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1.35;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
}
.credits .by { color: var(--play); }

/* ── Theme picker (BOS-18) ────────────────────────────────────────────────────
   Title-screen chrome, fixed to the bottom-right above the overlay (z 50) but
   outside it, so the per-theme overlay tints below never recolor the swatches.
   Each button carries its theme's card/ink colors via --opt-bg/--opt-ink set
   inline by theme.js. */
.theme-picker {
  position: fixed;
  right: 1.75rem;
  bottom: 1.75rem;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.5rem;
}
.theme-picker .theme-label {
  color: #e4e4e1;
  font-weight: 600;
  font-size: clamp(0.85rem, 1.6vw, 1.1rem);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
}
.theme-picker .theme-options {
  display: flex;
  gap: 0.6rem;
}
.theme-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: 0.55rem 0.65rem 0.45rem;
  background: var(--opt-bg, var(--card));
  color: var(--opt-ink, var(--ink));
  border: 3px solid transparent;
  border-radius: 12px;
  box-shadow: 0 4px 0 rgba(0, 0, 0, 0.3);
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.theme-option:hover { transform: translateY(-1px); }
.theme-option[aria-pressed="true"] {
  border-color: var(--menu-green);
  transform: translateY(-2px);
}
.theme-option .dots {
  display: flex;
  gap: 3px;
}
.theme-option .dots i {
  display: block;
  width: 11px;
  height: 11px;
  border-radius: 50%;
}

/* ── Themes (BOS-18) ──────────────────────────────────────────────────────────
   Each theme is a token override block keyed off html[data-theme=…] (theme.js
   sets the attribute + the --tc palette vars). The title overlay gets a cheap
   full-screen tint per theme for instant feedback while picking. */

/* Newsprint — "minimal and appropriate for a newspaper": flat paper page,
   serif type, hairline rules, square corners, print-muted palette, no post-fx. */
html[data-theme="newsprint"] {
  --bg: #e7e3d7;
  --card: #f7f4ea;
  --cell: #f7f4ea;
  --line: #22211d;
  --ink: #22211d;
  --accent: #22211d;
  --date: #6e6c63;
  --tree-shadow: rgba(34, 33, 29, 0.12);
  --grid-gap: 2px;
  --radius-card: 2px;
  --radius-board: 0;
  --radius-tile: 0;
  --font-body: Georgia, "Times New Roman", Times, serif;
  --card-shadow: 0 0 0 1px var(--line);
  --home-border: #22211d;
}
html[data-theme="newsprint"] .title-overlay {
  filter: grayscale(0.75) sepia(0.3) brightness(1.03);
}

/* Ink — black and white only; the four colors read by canopy pattern instead:
   amber=solid, green=outline, salmon=dots, teal=parallel lines, wild=crosshatch
   (the issue's orange/yellow/pink/blue, mapped to our palette). No post-fx. */
html[data-theme="ink"] {
  --bg: #ffffff;
  --card: #ffffff;
  --cell: #ffffff;
  --line: #141414;
  --ink: #141414;
  --accent: #141414;
  --date: #141414;
  --menu-green: #ffffff;  /* selected-swatch ring + logo, on the grayscale overlay */
  --tree-shadow: rgba(20, 20, 20, 0.2);
  --grid-gap: 6px;
  --radius-card: 12px;
  --radius-board: 6px;
  --radius-tile: 2px;
  --card-shadow: 0 0 0 3px var(--line);
  --home-border: #141414;
}
html[data-theme="ink"] .title-overlay {
  filter: grayscale(1) contrast(1.12);
}
/* Every ink canopy is a black silhouette; an inset inner shape carries the
   per-color pattern, so the silhouette always outlines the tree shape. */
html[data-theme="ink"] .tree .canopy { position: relative; }
html[data-theme="ink"] .tree .canopy::after {
  content: "";
  position: absolute;
  inset: 16%;
  border-radius: inherit;      /* follows circle/square rounding */
  background-color: var(--cell);
}
/* Triangles clip the inner shape to a smaller similar triangle; the uneven
   inset keeps the visible black rim roughly even along the slanted sides. */
html[data-theme="ink"] .tree .canopy.triangle::after {
  clip-path: polygon(50% 0%, 98% 100%, 2% 100%);
  inset: 26% 19% 10% 19%;
}
/* amber (ct3) -> solid black: no inner shape at all */
html[data-theme="ink"] [data-ct="3"] .canopy::after { display: none; }
/* leaf green (ct4) -> outline: the plain inner shape (the default ::after) */
/* salmon (ct1) -> dot pattern */
html[data-theme="ink"] [data-ct="1"] .canopy::after {
  background-image: radial-gradient(#141414 27%, transparent 34%);
  background-size: 7px 7px;
}
/* teal (ct2) -> parallel lines */
html[data-theme="ink"] [data-ct="2"] .canopy::after {
  background-image: repeating-linear-gradient(0deg, #141414 0 2px, transparent 2px 6px);
}
/* wild (ct5) -> crosshatch */
html[data-theme="ink"] [data-ct="5"] .canopy::after {
  background-image:
    repeating-linear-gradient(45deg, #141414 0 1.5px, transparent 1.5px 6px),
    repeating-linear-gradient(-45deg, #141414 0 1.5px, transparent 1.5px 6px);
}
/* fire (ct6, BOS-22) -> steep hatching, plus the flicker it shares with every
   theme, since ink can't tell it apart by color */
html[data-theme="ink"] [data-ct="6"] .canopy::after {
  background-image: repeating-linear-gradient(60deg, #141414 0 2px, transparent 2px 5px);
}

/* Neon — the grove after dark: deep-violet night, trees as glowing neon signs.
   Keeps the fx chain: the bloom threshold only catches the bright glow. */
html[data-theme="neon"] {
  --bg: #14112a;
  --card: #211c40;
  --cell: #2a2454;
  --line: #0e0b1f;
  --ink: #f0eeff;
  --accent: #29e0f0;
  --play: #ff4fa3;
  --date: #9a95c9;
  --menu-green: #8aff5a;
  --tree-shadow: rgba(0, 0, 0, 0.5);
  --card-shadow: 5px 8px 0px rgba(0, 0, 0, 0.6);
  --home-border: #3b3470;
}
html[data-theme="neon"] .title-overlay {
  filter: hue-rotate(195deg) saturate(1.3) brightness(0.92);
}
html[data-theme="neon"] .tree .sway {
  filter: drop-shadow(0 0 6px var(--tile-color, var(--accent)));
}

/* Themed page backdrop: everything but grove drops the leaf-brick tile for a
   flat color; the paper-like themes also drop the whole post-fx chain (grade,
   bloom, vignette) — print doesn't glow. */
html[data-theme="newsprint"] body,
html[data-theme="ink"] body,
html[data-theme="neon"] body {
  background-image: none;
}
html[data-theme="newsprint"] body.fx,
html[data-theme="ink"] body.fx {
  filter: none;
}
html[data-theme="newsprint"] body.fx #app,
html[data-theme="ink"] body.fx #app {
  filter: none;
}
html[data-theme="newsprint"] .fx-vignette,
html[data-theme="ink"] .fx-vignette {
  display: none;
}
