/* Greedy Ivy — the board. Copyright (c) 2026 Alejandro Núñez.
   All rights reserved. See LICENSE. Game mechanics are not claimed; see the notice there.

   Everything that draws a cell and nothing that draws a page, so a client can import
   this file and get the board the style frame shows. `playtest/styleframe.py` inlines it
   at generation time, which is what keeps art.html a single self-contained file.

   It names custom properties but defines none. The token blocks are generated from
   shared/skins.json — by styleframe.py here, by the client's build there — because a
   hand-written copy of a palette is a copy that drifts from the one the auditor scores.

   One rule runs the whole palette: the two players own the only strong chroma on the
   board, and everything else separates by lightness and temperature. That is not a
   style choice — colour blindness compresses hue but never lightness or saturation, so
   a saturated colour against a drained one survives every kind of sight. It also tells
   the truth about the game: living vine keeps its colour, a withering branch is
   bleached of it, deadwood is drained to grey.

   Weight is contrast, not darkness, so deadwood is dark on a light board and pale on a
   dark one. Every value here is checked by playtest/palette.py — see the floors there. */
.board{--sq:min(var(--sqmax), calc((100vw - 2*var(--gutter) - 2px) / 9));
  position:relative; width:calc(9*var(--sq)); flex:none; background:var(--outside);
  padding-top:calc(.75*var(--sq))}
.board.bare{padding-top:0}
/* The roof sits behind the cells, which are opaque and cut their own holes in it. */
.house{position:absolute; inset:0; width:100%; height:100%; z-index:0; pointer-events:none}
.house .pitch{fill:var(--roof); stroke:var(--roof-line); stroke-width:2;
  stroke-linejoin:round; vector-effect:non-scaling-stroke}
.grid{display:grid; grid-template-columns:repeat(9,var(--sq)); position:relative; z-index:1}
.cell{z-index:1}

/* The wall: the boundary cells' own left/right/bottom edge, thickened from the inside.
   Not a shape drawn beside, over, or around the grid — there is no separate box here to
   size, position, or reserve room for, because the wall is made of the board's own
   cells rather than something that has to fit next to them.

   Three earlier shapes all tried to give it a box of its own outside the grid, and each
   needed somewhere to put that box that wasn't already there:
   - An SVG path stroked outward from the grid's edge worked on the desktop browser that
     built it and did something very wrong on a real phone (the band came out enormous,
     eating the action bar).
   - A div positioned past the grid's own edge, sized to sit outside it, needed .stage to
     reserve room to catch it and still came out lopsided — left/top position a box's
     *outer* edge for position:absolute regardless of box-sizing, so pinning left:0 with
     only width set put the entire border on the right and none on the left.
   - Padding on .board reserved that room properly instead of guessing at it, but the
     wall was still its own box beside the grid, not the grid itself — recognisably a
     stripe from a ruler's width away, and gone entirely once looked at on the board it
     was supposed to belong to.

   --line-color and the three --wall-* properties exist so several of these can land on
   one cell at once — every corner of the house body is a side-wall cell *and* a soil
   cell — without one selector's box-shadow silently replacing another's. Each rule below
   sets only the one property it owns; .cell's own box-shadow is the only place all four
   layers are ever combined, so nothing can lose to nothing else by accident. */
.cell{position:relative; width:var(--sq); height:var(--sq); display:block; font-style:normal;
  font-size:var(--sq); --line-color:var(--line); --wall-l:0px; --wall-r:0px; --wall-b:0px;
  background:var(--cell);
  box-shadow:
    inset 0 0 0 1px var(--line-color),
    inset var(--wall-l) 0 0 0 var(--roof-line),
    inset var(--wall-r) 0 0 0 var(--roof-line),
    inset 0 var(--wall-b) 0 0 var(--roof-line)}
.cell.off{background:transparent; box-shadow:none}
.cell.skyfield{background:var(--outside); box-shadow:inset 0 0 0 1px var(--line)}
.cell.roofsw{background:var(--roof); box-shadow:inset 0 0 0 1px var(--roof-line)}
/* Column 0 and column 8 of 9 are the house's side walls. nth-child counts every cell in
   the grid's DOM order, off-board roof corners included, so :not(.off) is what keeps
   the stripe from starting above the eaves — it needs no idea of the roof's own shape,
   just that the corners it should skip are already marked. The foundation is the soil
   row and needed no new selector: it already had its own class. */
.grid > .cell:not(.off):nth-child(9n+1){--wall-l:calc(var(--sq) * .08)}
.grid > .cell:not(.off):nth-child(9n){--wall-r:calc(var(--sq) * -.08)}
.cell.soil{--wall-b:calc(var(--sq) * -.08);
  background-color:var(--soil);
  background-image:
    radial-gradient(circle at 30% 35%,color-mix(in srgb,var(--soil-line) 55%,transparent) 0 .9px,transparent 1px),
    radial-gradient(circle at 72% 68%,color-mix(in srgb,var(--soil-line) 38%,transparent) 0 .7px,transparent .8px);
  background-size:6px 6px,9px 9px}
.cell.soil::before{content:""; position:absolute; left:0; right:0; top:-1.5px; height:3px;
  background:var(--soil-line); z-index:0}
/* The income tiers are the house's floors. Rows 9-11 pay 4 and, with the crown over them,
   are exactly the roof — so the eaves already draws that boundary and only the 4/5 line was
   ever missing. The crown pays nothing, which makes the 11/12 line a tier boundary in
   arithmetic and deliberately not one on the board: a floor drawn under e12 would announce
   a zero that neither manual nor the tutorial says out loud. `cell_html`'s `r > 1` is where
   that is decided, in both renderers. */
.cell.floor::before{content:""; position:absolute; left:0; right:0; top:-1.5px; height:3px;
  background:var(--floor-line); z-index:0}
.cell.glass{background:var(--stone-soft); --line-color:var(--stone)}
.cell.glass::before{content:""; position:absolute; inset:14%; border:1px solid var(--stone);
  background:linear-gradient(140deg,transparent 42%,color-mix(in srgb,var(--card) 70%,transparent) 50%,transparent 58%)}
.cell.wood{background:var(--wood); --line-color:var(--wood)}
.cell.sky{background:var(--sun-soft); --line-color:var(--sun)}
.cell.sky::before{content:""; position:absolute; left:50%; top:50%; width:34%; height:34%;
  transform:translate(-50%,-50%); border-radius:50%; border:1.5px solid var(--sun); opacity:.8}
/* The tunnel: a5 and i5 share an edge. It is the one connection on this board
   with no direction to draw it in — the two cells are eight columns apart — so it is not a
   .link, it is marked where it would leave: a seam down the outer wall of each mouth, and
   an arrowhead through it, pointing out of the board the way that edge goes. Both mouths
   point outward, because the edge is walked in both directions and one arrow in and one
   arrow out would read as a one-way street.

   An element of its own, and neither ::before nor the cell's background. ::before is free
   again since the mouths moved off row 4 — where they carried .floor, and where a fifth
   claimant on that pseudo-element inherited whatever it did not name, the bug the .offer
   halo comment below records — but taking it back would only reinstate that trap the next
   time the tunnel moves, and the other reason never depended on the row at all:
   background-image is where the client paints the Grow hint (.cell.can in app.css) — a
   terrain rule carrying .grid and an nth-child outspecifies it, so the two cells a creep
   most needs dotted were the two it silently went missing on. A child of the cell competes
   with nothing, and it clears the side wall's own inset box-shadow as well, which had been
   painting over the outer half of the seam it was drawn in.

   Which side to draw comes from the same nth-child the side walls use, since one class
   cannot know left from right. */
.mouth{position:absolute; top:0; bottom:0; width:9%; z-index:1; background:var(--sun)}
.mouth::before{content:""; position:absolute; top:50%; width:300%; height:44%;
  transform:translateY(-50%); background:var(--sun)}
.grid > .cell.tunnel:nth-child(9n+1) .mouth{left:0}
.grid > .cell.tunnel:nth-child(9n+1) .mouth::before{left:0;
  clip-path:polygon(100% 0,100% 100%,0 50%)}
.grid > .cell.tunnel:nth-child(9n) .mouth{right:0}
.grid > .cell.tunnel:nth-child(9n) .mouth::before{right:0;
  clip-path:polygon(0 0,0 100%,100% 50%)}

.count{position:absolute; inset:0; display:grid; place-items:center; z-index:2;
  font:700 .42em/1 "IBM Plex Mono",monospace; color:var(--wood-ink)}

/* vine: a node plus a stem to every same-team neighbour, so connectivity is drawn,
   not inferred. Shape carries the team too — rounded square alive, circle withering,
   large ringed square for a root — because colour alone must never be the only cue. */
.cell.vine{--c:var(--vine)}
.cell.vine.p{--c:var(--rival)}
.cell.vine.n{--c:var(--wither)}
.node{position:absolute; left:27%; top:27%; width:46%; height:46%; border-radius:32%;
  background:var(--c); z-index:2}
.link{position:absolute; background:var(--c); z-index:1}
.link.u{left:35%; width:30%; top:-1px; height:58%}
.link.d{left:35%; width:30%; bottom:-1px; height:58%}
.link.l{top:35%; height:30%; left:-1px; width:58%}
.link.r{top:35%; height:30%; right:-1px; width:58%}
.cell.p .node{left:30%; top:30%; width:40%; height:40%; border-radius:18%;
  transform:rotate(45deg)}
.cell.n .node{border-radius:50%; background:var(--wither-soft);
  box-shadow:inset 0 0 0 2px var(--wither)}
.cell.n .link{background:repeating-linear-gradient(var(--dir),
  var(--c) 0 5px, transparent 5px 10px)}
.cell.n .link.u,.cell.n .link.d{left:41%; width:18%; --dir:to bottom}
.cell.n .link.l,.cell.n .link.r{top:41%; height:18%; --dir:to right}
.cell.root .node{left:16%; top:16%; width:68%; height:68%; border-radius:26%;
  box-shadow:0 0 0 2px var(--card),0 0 0 3.5px var(--c)}

.cell.leaf .node{left:36%; top:36%; width:28%; height:28%; border-radius:50%}
.lf{position:absolute; left:50%; top:50%; width:84%; height:84%; z-index:4;
  transform:translate(-50%,-54%) rotate(var(--rot,-16deg)) scale(var(--ls,1));
  overflow:visible}
.grown .lf{width:86%; height:86%}
.grown .lf path{stroke-width:1.15; stroke-linejoin:round}
.grown .lf .rib{stroke-width:.9; opacity:.5}
.lf path{fill:var(--card); stroke:var(--c); stroke-width:1.6; stroke-linejoin:round}
.lf .rib{fill:none; stroke:var(--c); stroke-width:1.2; opacity:.55}
.cell.lit .lf path{fill:var(--sun-soft)}
.cell.lit .lf{filter:drop-shadow(0 0 3px color-mix(in srgb,var(--sun) 70%,transparent))}
.cell.dim .lf path{stroke:var(--faint); fill:var(--card)}
.cell.dim .lf .rib{stroke:var(--faint)}
.cell.n .lf path{stroke:var(--wither); fill:var(--wither-soft)}
.cell.n .lf .rib{stroke:var(--wither)}
.pay{position:absolute; right:2%; bottom:2%; z-index:6; background:var(--sun);
  color:var(--sun-ink); border-radius:1em; min-width:1.3em; height:1.3em; padding:0 .18em;
  display:grid; place-items:center; font:700 .22em/1 "IBM Plex Mono",monospace;
  box-shadow:0 0 0 1.5px var(--cell)}
.cell.n .pay{background:var(--wither); color:var(--wither-ink)}
/* The crowned leaf carries a trophy where every other leaf carries its income — same
   badge, same corner, so nothing on the board shifts at the moment a game is won. Filled
   in the badge's own ink rather than outlined the way a leaf is: at this size an outlined
   cup is a smudge. Only ever drawn on a *lit* e12, which is the cell that wins. */
.pay.crown{padding:0}
.pay.crown .cup{display:block; width:.95em; height:.95em}
.pay.crown .cup path{fill:var(--sun-ink)}

/* Shade marks the column, not the vine. It sits under the stems: dimming a vine would
   blur the one read that matters most - whose is it - to say something about the light. */
.cell.shaded::after{content:""; position:absolute; inset:0; background:var(--shade); z-index:0}

/* overlays: the loud layer. Nothing in the base art is allowed to compete with these. */
.cell.target{box-shadow:inset 0 0 0 2.5px var(--ink)}
.cell.doomed .node,.cell.doomed .link,.cell.doomed .lf{filter:grayscale(1); opacity:.28}
.cell.doomed{background:color-mix(in srgb,var(--ink) 5%,var(--card))}
/* The halo has to undo the other ::before rules, because a cell has one ::before and four
   rules want it: the floor line, the soil line, the glass pane and the sky's own ring.
   This rule wins every property it names and inherits every property it does not — so
   `.floor`'s `height:3px` survived `inset:8%` and collapsed the halo into a gold sliver
   along the top of the cell. Any cell on a tier boundary showed it; the bottom cell of a
   branch a creep was about to graft home showed it most. Reset the geometry the other
   four set and the halo is a halo wherever it lands. */
.cell.offer::before{content:""; position:absolute; inset:8%; z-index:6; border-radius:18%;
  border:2px dashed var(--sun); background:color-mix(in srgb,var(--sun) 12%,transparent);
  width:auto; height:auto; transform:none; opacity:1}
