/*
 * Thai Numerals Zip  —  games/zip/index.css
 *
 * Everything is scoped under #tlz so nothing leaks into the rest of the site.
 * The board itself keeps an explicit palette (it is a game board, not page
 * furniture); .tlz-dark swaps that palette when the site theme is dark.
 *
 * The path and the walls are drawn in SVG (see index.js) — the classes below
 * only set their colour and thickness. Walls live on the EDGE between two
 * squares; there is deliberately no "wall square" style.
 */

#tlz,
/* The win dialogue is moved onto <body> at start-up (see index.js): the
   page callout carries backdrop-filter, which makes it the containing block for
   position:fixed and then clips it. Out on the body it inherits nothing from the
   board, so it needs the palette in its own right too. */
.tlz-modal,
/* The how-to dialogue is moved into the site-wide modal, OUTSIDE #tlz, so it
   cannot inherit anything from the board — it needs the palette in its own
   right. Same declarations, two scopes. */
.tlz-howto {
	--tlz-board:      #fdfaf6;   /* board face                       */
	--tlz-rule:       #e0d6cb;   /* thin grid rules                  */
	--tlz-frame:      #a3907c;   /* outer frame, and the walls with it */
	--tlz-path:       #f2842b;   /* the drawn line (gradient fallback) */
	--tlz-path-light: #f89f54;   /* top-left end of the line's gradient  */
	--tlz-path-dark:  #dc6d14;   /* bottom-right end                     */
	/* Walls and the board frame are deliberately one colour. A single
	   declaration covers both themes: the var() resolves against whichever
	   --tlz-frame is in play on #tlz. Deep enough in the taupe family to read
	   as an obstacle (2.9:1 on the board) rather than as another grid rule. */
	--tlz-wall:       var(--tlz-frame);
	/* A numeral is bare until the line reaches it, then a green disc appears
	   behind it. The disc always lands on the orange path — never on the board
	   — so it keeps one colour in both themes; only the bare numeral flips. */
	--tlz-chip-text:  #14181d;   /* numeral still to be reached, on the board */
	--tlz-disc:       #157f6d;   /* disc that appears once it is reached      */
	--tlz-disc-text:  #ffffff;
	--tlz-chip-done:  #157f6d;   /* "finished" green, for the goal line text */
	--tlz-penalty:    #c9372c;   /* the -1 for a numeral out of turn         */
	--tlz-ring-edge:  rgba(0, 0, 0, .17);   /* rims on the level ring        */
	/* The site's own .pagesubtitle colour, in both themes. Copied rather than
	   inherited: taking the class itself would drag its font stack, size ladder,
	   padding and glow along with the colour. */
	--tlz-subtitle:   rgb(34, 70, 32);
	--tlz-ink:        inherit;
	--tlz-surface:    rgba(127,127,127,.10);
	--tlz-line:       rgba(127,127,127,.32);
	--tlz-thai: 'Prompt', 'Leelawadee UI', Leelawadee, Tahoma, 'Arial Unicode MS', sans-serif;
}

/* The board's own box. Kept OUT of the palette rule above because the win
   dialogue shares that rule for its colours, and it is a full-window overlay:
   max-width and auto margins there stopped it covering the screen, leaving
   uncovered strips down both sides that clicks fell straight through. */
#tlz,
.tlz-howto {
	max-width: 520px;
	margin: 0 auto;
	padding: 2px 4px 10px;
	text-align: center;
	color: var(--tlz-ink);
}

#tlz.tlz-dark,
.tlz-modal.tlz-dark,
.tlz-howto.tlz-dark {
	--tlz-board:     #23262c;
	--tlz-rule:      #3a3f47;
	--tlz-frame:     #838d9c;   /* frame + walls */
	--tlz-path:      #f08a2e;
	--tlz-path-light: #f6a660;
	--tlz-path-dark:  #d97517;
	/* Only the unreached numeral flips: white on the dark board. The disc keeps
	   its light-theme colours — it sits on the orange line either way. */
	--tlz-chip-text: #ffffff;
	--tlz-chip-done: #1ea88f;
	--tlz-penalty:   #ff7367;   /* lifted off the dark board */
	--tlz-ring-edge: rgba(255, 255, 255, .2);
	--tlz-subtitle:  rgb(0, 172, 193);
}

#tlz .tlz-lead {
	margin: 0 0 12px;
	font-size: .95em;
	line-height: 1.5;
	opacity: .85;
	text-align: center;
}

/* ---- heads-up display ------------------------------------------------- */

#tlz .tlz-hud {
	position: relative;          /* anchor for the centred clock */
	display: flex;
	align-items: center;
	justify-content: space-between;
	clear: both;                 /* stay below the floated help icon */
	/* The ROW is pinned, not the goal box: that keeps the board from hopping
	   when the message changes, while letting the outlined box shrink to fit a
	   short message instead of leaving a blank line under it. Tall case is the
	   "Find ๓" variant (2.05em of this font) plus the goal's fixed 6px padding
	   and 2px border, which do not scale with the site's font ladder. */
	min-height: calc(2.06em + 8px);
	gap: 10px;
	max-width: 460px;
	margin: 0 auto 8px;
	padding: 0 2px;
}
/* Three across: goal left, clock centred, points right. The two outer cells
   flex equally so the clock sits in the true centre regardless of how long the
   goal message is. */
#tlz .tlz-goal {
	flex: 1 1 auto;
	min-width: 0;
	box-sizing: border-box;
	/* Hard stop short of the centred clock. Messages are written to fit well
	   inside this; the cap only exists so an unexpectedly long translation
	   wraps rather than sliding underneath the time. */
	max-width: calc(50% - 28px);
	font-size: 1.05em;
	font-weight: 700;
	text-align: left;
	line-height: 1.3;
	/* The border is reserved transparent and the padding is always there, so
	   .tlz-goal-fill only changes a colour — the HUD never changes size and the
	   board never jumps when the outline appears mid-game. */
	padding: 3px 7px;
	border: 1px solid transparent;
	border-radius: 6px;
}
/* All digits collected, empty squares left: box the message so it gets read. */
#tlz .tlz-goal.tlz-goal-fill { border-color: var(--tlz-path); }
/* index.js measures both runs' ink against the shared baseline and sets
   --tlz-goal-digit-align. The -.12em is only the fallback for before that
   lands (or if the canvas measurement is unavailable). */
#tlz .tlz-goal .tlz-goal-digit {
	font-family: var(--tlz-thai);
	font-size: 1.5em;
	vertical-align: var(--tlz-goal-digit-align, -.12em);
	/* Zero line-height keeps this numeral OUT of the line box. It is set 1.5x
	   and then shifted by a per-glyph amount, and an inline box that tall grows
	   the line box -- so every digit with a different shift gave the row a
	   different height and nudged the whole board up or down as the target
	   changed. With no height of its own it still draws, and still moves with
	   the shift, but the row is left to .tlz-hud's min-height. The glyph paints
	   outside its box, which is fine: nothing here clips. */
	line-height: 0;
	color: var(--tlz-path);
}
#tlz .tlz-goal.tlz-won { color: var(--tlz-chip-done); }
/* Centred out of flow. Sharing the row as an equal third would squeeze the goal
   until the longest message wrapped, which changes the row height and moves the
   board; this way the goal keeps all the width it needs and the clock is still
   dead centre. */
#tlz .tlz-clock {
	position: absolute;
	left: 50%;
	transform: translateX(-50%);
	display: inline-flex;
	align-items: center;
	gap: .4em;
	font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
	font-size: .95em;
	opacity: .7;
	white-space: nowrap;
}
#tlz .tlz-score {
	flex: 0 0 auto;          /* only as wide as "Points: N" needs */
	text-align: right;
	font-size: .95em;
	font-weight: 600;
	white-space: nowrap;
	opacity: .8;
}
#tlz .tlz-clock-icon {
	width: 1.15em;
	height: 1.15em;
	flex: none;
	display: block;
}

/* ---- board ------------------------------------------------------------ */

#tlz .tlz-board {
	position: relative;
	width: min(100%, 460px);
	margin: 0 auto;
	aspect-ratio: 1 / 1;
	background: var(--tlz-board);
	border: 2px solid var(--tlz-frame);
	border-radius: 14px;
	box-shadow: 0 2px 10px rgba(0,0,0,.10);
	overflow: hidden;
	touch-action: none;          /* let us own drag gestures on mobile */
	user-select: none;
	-webkit-user-select: none;
	-webkit-tap-highlight-color: transparent;
}

#tlz .tlz-svg {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	display: block;
	pointer-events: none;
}

#tlz .tlz-rule-line { stroke: var(--tlz-rule); stroke-width: 1.5; }

/* The line is painted with a gradient pinned to the whole grid (index.js builds
   it with gradientUnits="userSpaceOnUse", corner to corner), so a square's
   colour depends on where it sits on the board, not on the shape of the path
   so far. It stays put as the line grows. var(--tlz-path) is the paint
   fallback if the gradient ever fails to resolve. */
#tlz .tlz-path-line {
	fill: none;
	stroke: url(#tlz-path-grad) var(--tlz-path);
	stroke-width: 42;
	stroke-linecap: round;
	stroke-linejoin: round;
}
#tlz .tlz-grad-from { stop-color: var(--tlz-path-light); }
#tlz .tlz-grad-to   { stop-color: var(--tlz-path-dark); }

/* the head of the line, so a one-square path is still visible */
#tlz .tlz-path-head { fill: url(#tlz-path-grad) var(--tlz-path); }

#tlz .tlz-wall-line {
	stroke: var(--tlz-wall);
	stroke-width: 11;
	/* Square caps, and index.js draws each wall corner to corner with no inset.
	   The caps then overhang each end by exactly half the stroke, which is what
	   fills the outer quadrant where two walls meet in an L. Keep them square:
	   round caps leave a notch there. */
	stroke-linecap: square;
}

/* ---- interactive layer ------------------------------------------------ */

#tlz .tlz-cells {
	position: absolute;
	inset: 0;
	display: grid;
	touch-action: none;
	outline: none;
	cursor: pointer;
}

#tlz .tlz-cell {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
}

/* Bare numeral until reached; the round box is already the right size and
   shape to become the disc, and it anchors the hint ring and corner numeral. */
#tlz .tlz-chip {
	position: relative;
	width: 74%;
	height: 74%;
	border-radius: 50%;
	background: transparent;
	color: var(--tlz-chip-text);
	display: flex;
	align-items: center;
	justify-content: center;
	font-family: var(--tlz-thai);
	font-size: clamp(17px, 6vw, 34px);
	font-weight: 700;
	line-height: 1;
	transition: background-color .18s ease, color .18s ease, transform .2s ease;
}
/* Optical centring: index.js measures the resolved font's glyph ink and sets
   --tlz-digit-nudge. On its own span so the shake animation's transform on the
   chip does not cancel it. Defaults to 0, so it degrades to plain centring. */
#tlz .tlz-digit {
	display: block;
	transform: translateY(var(--tlz-digit-nudge, 0));
}

/* Reached: the disc fades in behind the numeral. */
#tlz .tlz-chip.tlz-chip-done {
	background: var(--tlz-disc);
	color: var(--tlz-disc-text);
	box-shadow: 0 1px 3px rgba(0,0,0,.28);
}
/* Hints on: an open ring round the numeral you are looking for. */
#tlz .tlz-chip.tlz-chip-next { box-shadow: 0 0 0 3px var(--tlz-path); }

/* rejected move: a short shake on the offending numeral */
#tlz .tlz-chip.tlz-chip-nope { animation: tlz-shake .32s ease; }
@keyframes tlz-shake {
	0%,100% { transform: translateX(0); }
	20%     { transform: translateX(-5px); }
	45%     { transform: translateX(4px); }
	70%     { transform: translateX(-3px); }
}

/* ...and what it cost, over the square that refused. index.js sets left/top to
   the middle of that cell, so every transform below has to carry the -50%,-50%
   that centres the label on that point. Pointer-events off: it sits on the
   grid's own hit targets and must never eat the next tap. */
#tlz .tlz-penalty {
	position: absolute;
	z-index: 3;
	font-weight: 800;
	/* Sized off the viewport the way the numerals themselves are, not in em:
	   this lives in the cells layer, whose inherited font is about 11px, so an
	   em here buys a fraction of the size it looks like it should. Set a shade
	   larger than a chip, since it has to carry from wherever the eye is. */
	font-size: clamp(20px, 7vw, 38px);
	line-height: 1;
	color: var(--tlz-penalty);
	text-shadow: 0 1px 3px rgba(0, 0, 0, .45);
	pointer-events: none;
	animation: tlz-penalty .8s ease-out forwards;
}
@keyframes tlz-penalty {
	0%   { opacity: 0; transform: translate(-50%, -50%) scale(.7); }
	15%  { opacity: 1; transform: translate(-50%, -62%) scale(1.1); }
	45%  { opacity: 1; transform: translate(-50%, -90%) scale(1); }
	100% { opacity: 0; transform: translate(-50%, -145%) scale(.9); }
}
/* Still says what it cost, just without the flight. */
@media (prefers-reduced-motion: reduce) {
	#tlz .tlz-penalty { animation-name: tlz-penalty-still; }
}
@keyframes tlz-penalty-still {
	0%, 100% { opacity: 0; transform: translate(-50%, -50%); }
	15%, 60% { opacity: 1; transform: translate(-50%, -50%); }
}

#tlz .tlz-arabic {
	position: absolute;
	right: 6%;
	bottom: 2%;
	font-size: clamp(8px, 1.8vw, 11px);
	font-weight: 600;
	opacity: .45;
	font-family: "SFMono-Regular", Consolas, monospace;
}

/* ---- controls --------------------------------------------------------- */

/* One row of three: Undo | Clear | New game. Equal columns, capped to the
   board's width so the row lines up with the grid above it. */
#tlz .tlz-controls {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	align-items: center;
	gap: 10px;
	/* 464 = the board's 460 content box plus its 2px border either side, so the
	   outer edges line up with the grid rather than sitting 2px inside it. */
	max-width: 464px;
	margin: 14px auto 6px;
}
/* Fill the cell, and drop the site's 1px button margin, which would otherwise
   push each button past its column. */
#tlz .tlz-controls > button {
	width: 100%;
	margin: 0;
}
/* The site gives selects a solid #ccc edge and an inset #ddd highlight, both of
   which read heavier than the .gray_button border beside them. Match the
   buttons. Full shorthand rather than border-color alone, so this still paints
   if the site ever wipes select borders the way it does button ones. */
#tlz select {
	border: 1px solid rgba(127,127,127,.32);
	box-shadow: none;
}

/* The buttons and the size select are plain site controls now — no styling of
   their own beyond killing the focus outline, which page_style.css puts on
   every button:focus and which you asked to stay out of this game. */
#tlz .tlz-controls button:focus,
.tlz-modal-actions button:focus { outline: none; }

/* Icon + label button. The site gives buttons line-height: 0 and a fixed 2rem
   height, so the flex row is what lines the glyph up with the text. */
#tlz .tlz-icon-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: .4em;
	line-height: normal;
}
#tlz .tlz-btn-icon {
	width: 1.4em;
	height: 1.4em;
	flex: none;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* One moving part each, both on the same springy overshoot so the two buttons
   read as a pair. transform-origin on an SVG child is in USER units, not
   percentages — 4px 6px here means the lid's left hinge in the 24-unit box. */
#tlz .tlz-trash-lid,
#tlz .tlz-undo-head {
	transition: transform .3s cubic-bezier(.34, 1.56, .64, 1);
}
#tlz .tlz-trash-lid { transform-origin: 4px 6px; }

#tlz .tlz-icon-btn:hover .tlz-trash-lid { transform: rotate(-25deg); }
/* the arrowhead hops back along the arc; user units again, so ~2.6/24 of the box */
#tlz .tlz-icon-btn:hover .tlz-undo-head { transform: translateX(-2.6px); }

/* New game: the reset button from user_profile_buttons.php, mirrored. The flip
   lives on the <svg> so the rotation below stays exactly as the original — the
   arrow spins in the mirrored space and needs no adjusting. transform-box:
   fill-box is what makes transform-origin: center mean the arrow's own centre
   rather than the SVG viewport's. */
#tlz .tlz-reset-icon {
	overflow: visible;
	transform: scaleX(-1);
}
#tlz .tlz-reset-arrow {
	transform-box: fill-box;
	transform-origin: center;
	transition: transform .4s cubic-bezier(.34, 1.56, .64, 1);
}
#tlz .tlz-icon-btn:hover .tlz-reset-arrow { transform: rotate(-180deg); }

@media (prefers-reduced-motion: reduce) {
	#tlz .tlz-trash-lid,
	#tlz .tlz-undo-head,
	#tlz .tlz-reset-arrow { transition: none; }
}

/* Under 300px the three buttons share the row with no space to spare, so the
   icons go and the labels get the width. The toggles below keep theirs — those
   icons ARE the control, not decoration on a label. */
@media (max-width: 299.98px) {
	#tlz .tlz-btn-icon { display: none; }
	#tlz .tlz-icon-btn { gap: 0; }
	#tlz .tlz-controls { gap: 5px; }
}

#tlz .tlz-toggles {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: 6px 20px;
	margin: 6px auto 12px;
}
#tlz .tlz-toggle {
	display: inline-flex;
	align-items: center;
	padding: 2px;
	line-height: 0;
	cursor: pointer;
	color: inherit;
	opacity: .6;
	transition: opacity .15s ease, color .15s ease;
}
#tlz .tlz-toggle:hover { opacity: 1; color: var(--tlz-path); }
/* Keyboard focus only. :focus-within would keep the accent after a mouse click,
   because the hidden checkbox holds focus — so a clicked toggle stayed orange.
   Kept as its own rule: if :has() is unsupported the selector is dropped, and
   the :hover rule above must survive that. */
#tlz .tlz-toggle:has(.tlz-toggle-input:focus-visible) {
	opacity: 1;
	color: var(--tlz-path);
}
/* Hidden from view but NOT from the tab order or the accessibility tree —
   display:none would make both toggles unreachable by keyboard. */
#tlz .tlz-toggle-input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}
/* 1.4em, the same as the icons on the buttons above, so the two rows agree */
#tlz .tlz-toggle-ico {
	width: 1.4em;
	height: 1.4em;
	display: block;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}
/* the checkbox IS the state: show the matching glyph for it */
#tlz .tlz-toggle .tlz-ico-on { display: none; }
#tlz .tlz-toggle-input:checked ~ .tlz-ico-on { display: block; }
#tlz .tlz-toggle-input:checked ~ .tlz-ico-off { display: none; }

/* The bulb is one icon in two states. Rays on when hints are on; when they are
   off the bulb slides left and an X appears on the right -- the same X, at the
   same coordinates, as the muted speaker beside it, so the two "off" icons
   read as a pair.

   Which element carries which shift matters. The ON shift sits on the <svg>,
   so it moves rays and bulb together. The OFF shift sits on .tlz-bulb-body, so
   the X is left exactly where it was authored and stays aligned with the
   speaker's. That body is a bare wrapper: a CSS transform on the bulb group
   itself would replace its transform attribute and undo its scaling.

   The offsets are measured, not guessed. The speaker's ink centres on 12.0 in
   the 24-unit box; the bulb's lands on 13.24 with the rays off and 10.72 with
   them on. On an inner <g>, transform px ARE user units, so the off shift can
   name 1.24 directly; on the <svg> it has to be em -- 24 units render at
   1.4em, so one unit is 1.4/24 = 0.0583em. The 5 units across mirror the
   speaker's layout, leaving the bulb on x 2.7-11.4 against the cone's 2-11. */
#tlz .tlz-bulb { transition: transform .2s ease; }
#tlz .tlz-toggle-input:checked ~ .tlz-bulb {
	transform: translateY(0.075em);       /* rays on: whole icon 1.28 units high */
}
#tlz .tlz-bulb-body {
	transform: translate(-5px, -1.24px);  /* rays off: aside for the X, up 1.24 */
	transition: transform .2s ease;
}
#tlz .tlz-toggle-input:checked ~ .tlz-bulb .tlz-bulb-body { transform: none; }

#tlz .tlz-bulb-rays { display: none; }
#tlz .tlz-toggle-input:checked ~ .tlz-bulb .tlz-bulb-rays { display: inline; }
#tlz .tlz-toggle-input:checked ~ .tlz-bulb .tlz-bulb-x { display: none; }

@media (prefers-reduced-motion: reduce) {
	#tlz .tlz-bulb,
	#tlz .tlz-bulb-body { transition: none; }
}

/* ---- digit key -------------------------------------------------------- */

/* Five to a row, so the ten digits read as two even rows of five rather than
   however many happen to fit. A grid rather than wrapped flex: with flex the
   count changes with the width, and a short last row ends up centred and
   ragged. minmax(0, 1fr) lets the columns shrink below the items' min-width on
   a narrow screen instead of overflowing. */
#tlz .tlz-key {
	display: grid;
	grid-template-columns: repeat(5, minmax(0, 1fr));
	gap: 6px;
	margin: 0 auto 12px;
	max-width: 320px;
}
#tlz .tlz-key.tlz-hidden { display: none; }

#tlz .tlz-key-item {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 1px;
	/* border-box, or min-width plus the padding and border make a 62px tile in a
	   59px column and every one of them overhangs. */
	box-sizing: border-box;
	min-width: 48px;
	padding: 5px 6px;
	border: 1px solid var(--tlz-line);
	border-radius: 8px;
	background: var(--tlz-surface);
	line-height: 1.15;
}
#tlz .tlz-key-item.tlz-key-now { border-color: var(--tlz-path); background: rgba(242,132,43,.14); }
#tlz .tlz-key-item.tlz-key-got { opacity: .42; }

#tlz .tlz-key-thai { font-family: var(--tlz-thai); font-size: 1.35em; font-weight: 700; }
#tlz .tlz-key-word { font-family: var(--tlz-thai); font-size: .82em; }
#tlz .tlz-key-fone { font-size: .68em; opacity: .75; }
#tlz .tlz-key-num  { font-size: .68em; opacity: .55; font-family: "SFMono-Regular", Consolas, monospace; }

#tlz .tlz-tips {
	font-size: .82em;
	line-height: 1.5;
	opacity: .7;
	max-width: 460px;
	margin: 0 auto;
	text-align: left;
}

@media (max-width: 420px) {
	#tlz .tlz-key-item { min-width: 42px; padding: 4px; }
	#tlz .tlz-tips { text-align: center; }
}

/* ---- win: sweep + dialog ---------------------------------------------- */

/* Diagonal sheen crossing the board once, top-left to bottom-right. The band
   is inset well beyond the board so it starts and finishes out of sight, and
   the board's overflow: hidden does the clipping. */
#tlz .tlz-sweep {
	position: absolute;
	inset: -60%;
	pointer-events: none;
	opacity: 0;
	background: linear-gradient(135deg,
		transparent 44%,
		var(--tlz-path) 50%,
		transparent 56%);
}
#tlz .tlz-board.tlz-solved .tlz-sweep { animation: tlz-sweep .8s ease-out; }
@keyframes tlz-sweep {
	0%   { opacity: 0;   transform: translate(-55%, -55%); }
	18%  { opacity: .75; }
	82%  { opacity: .75; }
	100% { opacity: 0;   transform: translate(55%, 55%); }
}

.tlz-modal {
	position: fixed;
	inset: 0;
	z-index: 9000;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 20px;
}
.tlz-modal[hidden] { display: none; }

/* Dims the page and swallows every click outside the card. Now that the
   dialogue is a real page-level overlay this covers the whole window rather
   than just the callout it used to be trapped in. */
.tlz-modal-backdrop {
	position: fixed;
	inset: 0;
	background: rgba(10, 12, 16, .55);
	animation: tlz-fade .2s ease-out;
}
@keyframes tlz-fade { from { opacity: 0; } to { opacity: 1; } }

/* Level, directly under the trophy. Deliberately larger than the stats beneath
   it: it is the number that carries across games, where the rest is about this
   board alone. */
/* The frame round the coin and its figures. Overflow is left visible on
   purpose — the burst rays and the coin's reflection both reach outside the
   coin's box, and clipping here would cut them off.
   The background has to stay opaque whatever colour it is: the scattered
   numerals lie behind this panel, and anything translucent lets them through
   onto the figures. */
.tlz-modal-frame {
	position: relative;
	margin: 0 0 16px;
	padding: 14px 12px 10px;
	border: 1px solid var(--tlz-line);
	border-radius: 14px;
	background: var(--tlz-board);
}
.tlz-frame-deco {
	position: absolute;
	inset: 0;
	z-index: -1;
	overflow: hidden;
	border-radius: inherit;
	pointer-events: none;
	color: rgb(128, 128, 188);
	opacity: .07;
}
.tlz-frame-deco span {
	position: absolute;
	font-family: var(--tlz-thai);
	font-weight: 700;
	line-height: 1;
}

.tlz-modal-level {
	/* inline-block so the rings below hug the words rather than the whole card;
	   the card centres its text, so it stays centred either way. */
	display: inline-block;
	position: relative;
	font-size: 1.25em;
	font-weight: 700;
	line-height: 1.2;
	margin: 0 0 6px;
	color: var(--tlz-chip-done);
}
.tlz-lvl-ch {
	display: inline-block;
	position: relative;
}

/* Levelled up: each letter hops in turn, left to right, and its own outline
   swells out of it and fades. --i is the letter's place in the line, set by
   index.js, and is the whole of what staggers the wave. */
.tlz-level-up .tlz-lvl-ch {
	animation: tlz-lvl-hop .42s cubic-bezier(.28, 1.4, .5, 1) calc(var(--i) * 45ms) both;
}
@keyframes tlz-lvl-hop {
	0%, 100% { transform: translateY(0); }
	38%      { transform: translateY(-8px); }
	72%      { transform: translateY(0); }
}

/* The ghost: the same glyph again, drawn as outline only and grown out of the
   letter it sits on. content comes from data-ch rather than being hard-coded,
   so it follows whatever the caption says in any of the seven languages. */
.tlz-lvl-ch::before {
	content: attr(data-ch);
	position: absolute;
	left: 0;
	top: 0;
	white-space: pre;
	color: transparent;
	-webkit-text-stroke: 1.4px var(--tlz-chip-done);
	text-stroke: 1.4px var(--tlz-chip-done);
	opacity: 0;
	pointer-events: none;
}
.tlz-level-up .tlz-lvl-ch::before {
	animation: tlz-lvl-echo .55s ease-out calc(var(--i) * 45ms) both;
}
@keyframes tlz-lvl-echo {
	0%   { opacity: .8; transform: scale(1); }
	100% { opacity: 0;  transform: scale(2.2); }
}
@media (prefers-reduced-motion: reduce) {
	.tlz-level-up .tlz-lvl-ch,
	.tlz-level-up .tlz-lvl-ch::before { animation: none; }
}
/* Figures and buttons are two grids with the SAME two-column split, rather than
   one grid holding both. Sharing a grid did line the gap up beautifully, but it
   also welded the buttons to the figures: the buttons could not then stack on a
   narrow screen without dragging the figures into one column with them.

   Kept as equal halves in both, so the boundary still lands on the centre of the
   card and the colons still sit on that line. The labels right-align just short
   of it and the values start just past it, in every language. */
.tlz-modal-figures {
	margin: 8px 0 0;
	font-size: .95em;
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	column-gap: 8px;
	row-gap: 3px;
	line-height: 1.5;
}
.tlz-fig-label { text-align: right; opacity: .85; }
.tlz-fig-value { text-align: left; opacity: .85; }
.tlz-modal-figures b { color: var(--tlz-path); }

.tlz-modal-actions {
	margin-top: 20px;
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 8px;
}
/* Below 350px two buttons side by side get too cramped for their labels, so
   they stack and each takes the full width instead. */
@media (max-width: 349.98px) {
	.tlz-modal-actions { grid-template-columns: minmax(0, 1fr); }
}

/* The balls sit between the backdrop and the card. The card comes later in the
   DOM and is positioned, so it paints on top and they read as coming from
   behind it. pointer-events: none so a tap still reaches the backdrop. */
.tlz-balls {
	position: absolute;
	inset: 0;
	overflow: hidden;
	pointer-events: none;
}
.tlz-ball {
	position: absolute;
	top: 0;
	left: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	font-family: var(--tlz-thai);
	font-weight: 700;
	color: #fff;
	box-shadow: 0 2px 6px rgba(0,0,0,.3);
	will-change: transform;
}

.tlz-modal-card {
	position: relative;
	/* a stacking context of its own, so the deco layer's z-index -1 lands above
	   this background rather than sinking behind it */
	isolation: isolate;
	width: min(100%, 340px);
	padding: 26px 24px 20px;
	border-radius: 16px;
	/* The tint sits out here now and the frame within is left plain, so the
	   framed half reads as the lit panel and the margin around it as the mount. */
	background: linear-gradient(var(--tlz-surface), var(--tlz-surface)), var(--tlz-board);
	border: 2px solid var(--tlz-frame);
	box-shadow: 0 18px 50px rgba(0,0,0,.35);
	text-align: center;
	animation: tlz-rise .28s cubic-bezier(.2,.9,.3,1.3);
}

.tlz-trophy-wrap {
	position: relative;
	width: 96px;
	height: 96px;
	/* The bottom margin carries the reflection as well as the usual gap. */
	margin: 0 auto 30px;
	color: var(--tlz-path);
}

/* Sat directly under the coin and faded out before it gets far. Masked rather
   than given a plain opacity, so it dies away instead of stopping. */
.tlz-coin-echo {
	position: absolute;
	left: 0;
	top: 100%;
	width: 100%;
	height: 100%;
	pointer-events: none;
	opacity: .34;
	-webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 45%);
	mask-image: linear-gradient(to bottom, #000 0%, transparent 45%);
}
.tlz-echo-face { fill: url(#tlz-gold); }
.tlz-echo-rim {
	fill: none;
	stroke: #a0a7b1;
	stroke-width: 12;
}

/* The legend sits on the band, so it is cut from the band's metal rather than
   the face's: a mid silver body between a shadow above and a catch of light
   below, the same three-copy trick as the numeral on the cup. */
.tlz-coin-legend {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	overflow: visible;
}
.tlz-legend {
	font-family: var(--tlz-thai);
	font-size: 9px;
	font-weight: 700;
	letter-spacing: .6px;
	text-anchor: middle;
	fill: #c8ced6;
}
.tlz-legend-shade { fill: #4b525c; opacity: .55; }
.tlz-legend-lip   { fill: #ffffff; opacity: .5; }

/* rays firing outward, once, as the dialog opens */
.tlz-burst {
	position: absolute;
	inset: -18%;
	width: 136%;
	height: 136%;
	opacity: 0;
}
.tlz-burst line {
	stroke: url(#tlz-burst-gold);
	stroke-width: 5;
	stroke-linecap: round;
}
.tlz-modal.tlz-open .tlz-burst { animation: tlz-burst .55s ease-out .06s; }
@keyframes tlz-burst {
	0%   { opacity: 0;   transform: scale(.3)  rotate(-12deg); }
	30%  { opacity: .9; }
	100% { opacity: 0;   transform: scale(1.2) rotate(10deg); }
}

.tlz-trophy {
	/* Inset so the ring has room to sit around it. Absolute rather than a
	   width percentage, so shrinking it does not move it off centre. */
	position: absolute;
	inset: 20%;
	width: 60%;
	height: 60%;
	fill: url(#tlz-relief);
	/* Struck, not printed: a shadow cast down onto the face and a catchlight
	   along the top edge. Two drop-shadows rather than a bevel filter, because
	   these follow the outline of every piece — including the gaps inside the
	   handles — where a bevel would only work on a solid block. */
	filter: drop-shadow(0 1px .5px rgba(84, 54, 6, .42))
	        drop-shadow(0 -.8px 0 rgba(255, 250, 226, .6));
}
/* font-size is in the trophy's own units, so 17 is 17/64 of it however large
   the coin is drawn. Its own fill beats the gradient the svg hands down. */
.tlz-trophy-num {
	font-family: var(--tlz-thai);
	font-size: 21px;
	font-weight: 700;
	text-anchor: middle;
	dominant-baseline: central;
	/* The mid stop of #tlz-gold: the colour the coin's face already is at this
	   height, so the numeral is the field showing through rather than a mark
	   painted on the cup. */
	fill: #e3ba55;
}
.tlz-trophy-num-shade {
	fill: #6d4d0f;
	opacity: .5;
}
.tlz-trophy-num-lip {
	fill: #fff2cd;
	opacity: .55;
}
.tlz-trophy-handle {
	fill: none;
	stroke: url(#tlz-relief);
	stroke-width: 4.5;
	stroke-linecap: round;
}
.tlz-modal.tlz-open .tlz-trophy { animation: tlz-pop .42s cubic-bezier(.2,.9,.3,1.4); }
@keyframes tlz-pop {
	0%   { transform: scale(.4); opacity: 0; }
	65%  { transform: scale(1.1); opacity: 1; }
	100% { transform: scale(1); opacity: 1; }
}

.tlz-modal-title {
	/* Leads the card now, above the trophy, so it carries the gap below it. */
	margin: 0 0 12px;
	font-size: 1.5em;
	font-weight: 800;
	color: var(--tlz-subtitle);
}

@keyframes tlz-rise {
	from { opacity: 0; transform: translateY(14px) scale(.96); }
	to   { opacity: 1; transform: none; }
}

/* Motion is decoration here — the dialog and its content still appear. */
@media (prefers-reduced-motion: reduce) {
	#tlz .tlz-board.tlz-solved .tlz-sweep,
	.tlz-modal.tlz-open .tlz-burst,
	.tlz-modal.tlz-open .tlz-trophy,
	.tlz-modal-card,
	.tlz-modal-backdrop { animation: none; }
	.tlz-burst { opacity: .55; }
}

/* ---- how to play ------------------------------------------------------ */

/* Floated, so the lead paragraph wraps to its left instead of being pushed
   down. .tlz-hud clears it so the board can never ride up beside it.
   position/z-index matter: a float paints BELOW the inline content of later
   in-flow blocks, so the lead paragraph would otherwise sit on top of the icon
   and swallow every click. Positioning it lifts it into the layer above. */
#tlz .tlz-topbar {
	float: right;
	margin: 0 0 2px 10px;
	position: relative;
	z-index: 1;
}
#tlz .tlz-help {
	display: block;
	line-height: 0;
	text-decoration: none;
	cursor: pointer;
}
/* At rest the icon should read as body text, not as a link. The :not() is what
   makes that possible: colouring it unconditionally would outrank the theme's
   own a:hover (0,1,1) and kill the highlight, so the override is switched off
   the moment it is hovered or focused, letting each theme's link-hover colour
   through. stroke="currentColor" carries it into the SVG. */
#tlz .tlz-help:not(:hover):not(:focus) { color: inherit; }
#tlz .tlz-help svg { display: block; width: 20px; height: 20px; }

/* the stepped panel injected into the site-wide modal */
.tlz-howto {
	width: min(80vw, 320px);
	padding: 2px 4px 4px;
}
.tlz-howto-title {
	margin: 0 0 8px;
	font-size: 1.1em;
	font-weight: 800;
	line-height: 1.25;
}
/* Every example box is the same square, so the dialogue does not change height
   as you step through it. The grids fill it exactly; step 2's short, wide
   sequence sits at the top with the slack below it. */
.tlz-howto-art {
	box-sizing: border-box;
	aspect-ratio: 1 / 1;
	display: flex;
	align-items: flex-start;
	justify-content: center;
	background: var(--tlz-board);
	border: 2px solid var(--tlz-frame);
	border-radius: 12px;
	padding: 12px;
	margin: 0 0 10px;
}
.tlz-howto-art .tlz-mini { width: 100%; }
.tlz-howto-body {
	margin: 0;
	font-size: .92em;
	line-height: 1.5;
	opacity: .85;
	min-height: 4.5em;      /* keeps the footer from jumping between steps */
}

/* the little worked examples */
.tlz-mini { display: block; width: 100%; height: auto; overflow: visible; }
.tlz-mini-rule { stroke: var(--tlz-rule); stroke-width: 2; }
.tlz-mini-path {
	fill: none;
	stroke: var(--tlz-path);
	stroke-width: 40;
	stroke-linecap: round;
	stroke-linejoin: round;
}
.tlz-mini-wall { stroke: var(--tlz-wall); stroke-width: 12; stroke-linecap: square; }
.tlz-mini-disc { fill: var(--tlz-disc); }
.tlz-mini-digit {
	fill: var(--tlz-disc-text);
	font-family: var(--tlz-thai);
	font-size: 46px;
	font-weight: 700;
	text-anchor: middle;
	dominant-baseline: central;
}
.tlz-mini-box { fill: none; stroke: var(--tlz-rule); stroke-width: 3; }
.tlz-mini-arrow,
.tlz-mini-arrowhead {
	fill: none;
	stroke: var(--tlz-path);
	stroke-width: 8;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* footer: counter, step indicator, navigation */
.tlz-howto-foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
	margin-top: 14px;
}
.tlz-howto-count {
	font-size: .78em;
	opacity: .55;
	white-space: nowrap;
	font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
}
.tlz-howto-dots { display: inline-flex; align-items: center; gap: 5px; }
.tlz-dot {
	width: 8px;
	height: 8px;
	padding: 0;
	border: 0;
	border-radius: 5px;
	background: var(--tlz-line);
	cursor: pointer;
	transition: width .2s ease, background-color .2s ease;
}
.tlz-dot:hover { background: rgba(127,127,127,.55); }
.tlz-dot-on,
.tlz-dot-on:hover { width: 20px; background: var(--tlz-path); }
.tlz-howto-nav { display: inline-flex; gap: 6px; }
/* These are plain site buttons, so page_style.css gives them a focus ring
   (button:focus, 0-1-1). The :focus selector here is 0-2-1, so it outranks that
   whatever the sheet order turns out to be. */
.tlz-howto-nav button,
.tlz-howto-nav button:focus { outline: none; }

@media (prefers-reduced-motion: reduce) {
	.tlz-dot { transition: none; }
}

/* ---- the win dialogue's own buttons ------------------------------------ */

/* The dialogue is reparented to <body> at start-up, so the "#tlz ..." rules
   above no longer reach the button inside it — its icon fell back to the
   browser default and rendered as a large black blob over the label.
   These are the same declarations under a scope that does reach it. The
   originals are left alone because the board's buttons still use them, and
   dropping "#tlz" from those would cost the specificity that keeps the site
   stylesheet from overriding them. */
.tlz-modal .tlz-icon-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: .4em;
	line-height: normal;
}
.tlz-modal .tlz-btn-icon {
	width: 1.4em;
	height: 1.4em;
	flex: none;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/* This glyph is drawn as filled outlines - each square is one path whose inner
   contour is wound the other way, so the nonzero fill rule leaves the middle
   open. That means it needs the opposite paint from the stroked reset icon
   beside it, and its hairline frames read far lighter than any stroke-width we
   could set. The four paths are each nudged 26 units out from the centre in
   the markup, which opens the gaps between the squares from 44 units to 96;
   that widens the artwork to 69% of the 1024 box against the 75% of the icon
   it replaced, so the box is sized up slightly to match its optical size. */
.tlz-modal .tlz-grid-icon {
	width: 1.55em;
	height: 1.55em;
	fill: currentColor;
	stroke: none;
}
.tlz-modal .tlz-reset-icon {
	overflow: visible;
	transform: scaleX(-1);
}
.tlz-modal .tlz-reset-arrow {
	transform-box: fill-box;
	transform-origin: center;
	transition: transform .4s cubic-bezier(.34, 1.56, .64, 1);
}
.tlz-modal .tlz-icon-btn:hover .tlz-reset-arrow { transform: rotate(-180deg); }

/* Hovering View board folds the grid away into a cross: each square spins a
   quarter turn, shrinks and is drawn in towards the middle as it fades, and
   the two diagonals wind out of the centre to replace them.
   Every transform-origin below is written in the icon's own 1024 coordinates,
   at the drawn centre of the square it belongs to — the resting translate is
   part of the transform, so rotating happens about the square's real middle
   and not about a point 26 units off it.

   The grid and the cross cross-fade, which does draw them over each other for
   a few frames on the way back. Staggering the fades so they take turns reads
   as too brisk for the motion around it, so they overlap on purpose. */
.tlz-modal .tlz-grid-icon path {
	transition: transform .34s cubic-bezier(.34, 1.2, .64, 1), opacity .24s ease;
}
.tlz-modal .tlz-gs-tl { transform-origin: 311.95px 348.57px; transform: translate(-26px, -26px); }
.tlz-modal .tlz-gs-tr { transform-origin: 661.87px 348.57px; transform: translate(26px, -26px); }
.tlz-modal .tlz-gs-bl { transform-origin: 311.95px 698.48px; transform: translate(-26px, 26px); }
.tlz-modal .tlz-gs-br { transform-origin: 661.87px 698.48px; transform: translate(26px, 26px); }

.tlz-modal .tlz-icon-btn:hover .tlz-gs-tl { transform: translate(84px, 84px) rotate(45deg) scale(.34); opacity: 0; }
.tlz-modal .tlz-icon-btn:hover .tlz-gs-tr { transform: translate(-84px, 84px) rotate(45deg) scale(.34); opacity: 0; }
.tlz-modal .tlz-icon-btn:hover .tlz-gs-bl { transform: translate(84px, -84px) rotate(45deg) scale(.34); opacity: 0; }
.tlz-modal .tlz-icon-btn:hover .tlz-gs-br { transform: translate(-84px, -84px) rotate(45deg) scale(.34); opacity: 0; }

/* Stroked, so it keeps its weight however it is scaled, and set on the group
   so both arms inherit it past the fill/stroke the icon sets on the svg. */
.tlz-modal .tlz-grid-x {
	fill: none;
	stroke: currentColor;
	stroke-width: 24;
	stroke-linecap: round;
	opacity: 0;
	transform-origin: 486.9px 523.5px;
	transform: rotate(-45deg) scale(.45);
	transition: transform .34s cubic-bezier(.34, 1.45, .64, 1) .05s, opacity .2s ease .05s;
}
.tlz-modal .tlz-icon-btn:hover .tlz-grid-x { opacity: 1; transform: rotate(0) scale(1); }

@media (max-width: 299.98px) {
	.tlz-modal .tlz-btn-icon { display: none; }
	.tlz-modal .tlz-icon-btn { gap: 0; }
}
@media (prefers-reduced-motion: reduce) {
	.tlz-modal .tlz-reset-arrow { transition: none; }
	.tlz-modal .tlz-grid-icon path,
	.tlz-modal .tlz-grid-x { transition: none; }
}


/* ---- level progress ring ----------------------------------------------- */

/* The trophy sits inside a ring showing how far through the current level the
   total has got. Two circles: a faint track and the fill drawn over it. The
   fill is a dashed stroke one circumference long, and index.js slides its
   dash-offset from where the total stood before this board to where it stands
   now, so the score is seen going up rather than just landing. */
.tlz-ring {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	/* start the fill at twelve o'clock rather than three */
	transform: rotate(-90deg);
	overflow: visible;
}
.tlz-ring-track,
.tlz-ring-fill {
	fill: none;
	/* Wide enough to carry the legend struck into it. Together with r=43 in the
	   markup this puts the band at 37 to 49, and the blue outline just outside
	   it at 49.6 — inside the 50 the viewBox allows, which an svg clips to
	   whatever its overflow says. */
	stroke-width: 12;
	stroke-linecap: butt;
}
.tlz-coin {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
}
.tlz-coin-core { fill: url(#tlz-gold); }
.tlz-coin-dome { fill: url(#tlz-coin-dome); }
/* Same three tones as the numeral cut into the cup, so the two marks look
   struck by the same die. */
.tlz-star-shade { fill: #6d4d0f; opacity: .5; }
.tlz-star-lip   { fill: #fff2cd; opacity: .55; }
.tlz-star-face  { fill: #e3ba55; }
.tlz-coin-lip {
	fill: none;
	stroke: #5c3a0a;
	stroke-width: 2.4;
	opacity: .18;
}
/* Unstruck metal: the part of the rim the level has not reached yet. Kept at
   full strength — fading it back to half reads as a broken rim rather than as
   progress, since the rims either side of it stay put. */
.tlz-ring-track { stroke: #a0a7b1; }
/* Hairlines on the inside and outside of the band, so it reads as a channel
   with a lip rather than a painted arc. Neutral rather than tinted: they have
   to sit on the green fill and on the empty track alike. A whole unit wide is
   just under a pixel at the 96px the ring is drawn at, which is as heavy as
   they can be before the ring starts looking like a coin. */
.tlz-ring-edge {
	fill: none;
	stroke: var(--tlz-ring-edge);
	stroke-width: 1;
}
.tlz-ring-fill {
	stroke: url(#tlz-silver);
	/* dasharray is the circumference (2 * pi * 45); index.js sets the offset. */
	stroke-dasharray: 282.74;
	stroke-dashoffset: 282.74;      /* empty until the animation runs */
}
.tlz-ring-lead {
	fill: none;
	stroke: #3366cc;                /* the interface blue, off the primary button */
	stroke-width: 1.4;
	stroke-linecap: butt;
	/* A dash longer than either circumference, offset by its own length, leaves
	   the whole arc sitting in the gap — empty until paintRing() works out the
	   real figures from each radius. */
	stroke-dasharray: 400;
	stroke-dashoffset: 400;
}
@media (prefers-reduced-motion: reduce) {
	.tlz-ring-fill { transition: none; }
}
