/* Pickle Arcade: Patch, the fourth cabinet, rebuilt.
 *
 * arcade.css carries the site register and the --arc-* tokens on
 * body[data-app="minigame"]. This sheet carries everything Patch alone asks
 * for: the garden behind the glass, the glass itself, four reels that really
 * turn, the win lines, the celebrations, the command bar, the drawer and the
 * toasts. Every class here is prefixed pat- and every class the markup uses
 * must exist here or in arcade.css, because tests/design-language.test.mjs
 * walks both and a class it cannot find is a class nobody checks.
 *
 * ===================================================================
 * THE ONE PLACE ON THIS ESTATE WHERE LIGHT IS ALLOWED TO BE A SHADOW
 * ===================================================================
 *
 * Everywhere else the rule is flat: radius 2px, one hairline, and light drawn
 * as a radial gradient BEHIND an object rather than a box-shadow on it. That
 * rule is right for a block explorer and wrong for a slot machine, and it is
 * suspended INSIDE `.pat-band` only: shadows, inner shadows, glows, blur,
 * backdrop-filter and free radii are all allowed in here, and nowhere else.
 * The nav above the stage, the notes below it and every other page on this
 * origin keep the estate's language unchanged. Nothing in this file selects
 * outside `.pat-band`, `.pat-drawer` or `.pat-scrim`, which is what makes the
 * exception checkable by reading the left-hand side of every rule.
 *
 * WHY. A reel that stops has to look like it has mass, and mass on a screen is
 * made of an edge catching light and a shadow underneath. Drawn flat, the old
 * cabinet read as a form with pictures of vegetables in it. The spec called
 * for a modern online slot, and this is the price of one.
 *
 * COLOUR IS STILL A GRAMMAR, NOT A PALETTE. Terracotta is the machine. Gold is
 * a win: the lines, the counters, the celebrations. Mint is a credit that
 * landed, because mint means confirmed everywhere on this estate. Rose is the
 * machine stopped. Amber is every warning between, including "no receipt came
 * back". --arc-pending, white at 55%, is a transaction in flight. Green in the
 * scene is scenery and never a state. Nothing else is coloured.
 *
 * NO INLINE style= ANYWHERE. The CSP is style-src 'self' and
 * tests/ui-invariants.test.mjs walks the built export, so anything that varies
 * per instance is a CSS custom property written by JS through CSSOM
 * (--pat-brine on the jar) or an SVG geometry attribute. There is no third way
 * and there never has been.
 *
 * MOTION IS A LAYER, NOT THE PICTURE. Every rule with a duration lives inside
 * @media (prefers-reduced-motion: no-preference) at the end of this file.
 * Everything above it is the finished page: a reader who asked for less motion
 * sees the same grid, the same lines, the same figures, complete.
 */

/* ===================================================================== tokens
 * Declared on .pat-band so they cannot leak to another cabinet. The --arc-*
 * values come from arcade.css and are read with a fallback so this page renders
 * complete if the two sheets ever land apart; the fallback is never a second
 * source of truth. */
.pat-band {
  /* The garden, at dusk. Terracotta is the cabinet's light, so the sky is the
     light's own hue run from near-black through it and back down. */
  --pat-sky-top: #2a1614;
  --pat-sky-mid: #d2622e;
  --pat-sky-low: #1a0f0d;
  --pat-hill-far: #1f1210;
  --pat-hill-near: #150b0a;
  /* The free round: the same sky at golden hour. */
  --pat-sun-top: #3a2a10;
  --pat-sun-mid: #f2c14e;

  --pat-light: var(--arc-patch, #d2622e);
  --pat-light-hi: var(--arc-patch-hi, #f0854f);
  /* Gold means a win, and only a win. */
  --pat-gold: #f2c14e;
  --pat-gold-hi: #fff3d6;
  --pat-gold-deep: #7a3b1a;

  /* The tiers a tile is drawn in. A player who cannot yet read the paytable
     can still see that pepper and pickle are worth more than radish, because
     their tiles are hotter. */
  --pat-soil-a: #2b1b17;
  --pat-soil-b: #1c1110;
  --pat-leaf-a: #22331b;
  --pat-leaf-b: #152210;
  --pat-ember-a: #5a2a17;
  --pat-ember-b: #3b1a0f;
  --pat-royal-a: #6b4a0f;
  --pat-royal-b: #3a2807;
  --pat-wild-a: #1f3a2c;
  --pat-wild-b: #0f2018;
  --pat-wild-line: #7fe0a5;
  --pat-bonus-a: #3f1a3a;
  --pat-bonus-b: #220e20;
  --pat-bonus-line: #f27ed1;

  /* THE CELL IS THE UNIT. Everything else is a multiple of it: the gap, the
     glass padding, the button, the type. Sized by the SHORTER of the two axes
     and capped, because a cabinet that grew with a 4K screen would put the
     player's eye a metre from the grid. --pat-chrome is the head strip plus
     the command bar, and the phone block below overrides it, because down
     there the bar wraps to three rows. */
  --pat-chrome: 12rem;
  --pat-cell: min(
    (var(--arc-stage, 88dvh) - var(--pat-chrome)) / 3.6,
    (100vw - 2rem) / 4.6,
    9rem
  );
  --pat-gap: calc(var(--pat-cell) * .06);
  --pat-pad: calc(var(--pat-cell) * .18);
}

/* THE CELL IS A WHOLE PIXEL. A strip is a grid of 51 rows of --pat-cell, and
   the engine lays those rows out in its own fixed-point units, while the reel
   rests at translateY(--pat-cell * -n) computed in floating point: at a 77.83px
   cell the two parted by half a pixel by row 39, and the landed window sat a
   hairline off its tiles. A whole-pixel cell makes every multiple exact on both
   sides. Behind @supports, because a custom property holding a round() the
   browser cannot compute would be invalid wherever it is used and leave the
   reels with no size at all. */
@supports (width: round(down, 10.5px, 1px)) {
  .pat-band {
    --pat-cell: round(down, min(
      (var(--arc-stage, 88dvh) - var(--pat-chrome)) / 3.6,
      (100vw - 2rem) / 4.6,
      9rem
    ), 1px);
  }
}

/* ======================================================================= band
 * The stage is the page: the grid takes the height under the nav, the command
 * bar is the last row, and nothing scrolls below it - the notes are gone and
 * pages/patch.jsx renders <main> bare. Isolated so nothing in here can reach
 * the nav's z-index. */
.pat-band {
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  width: 100%;
  min-height: 34rem;
  /* Less one pixel, as .tre-table is: the nav stands 73px against its 72px
     token - a rule under it - and with nothing below the band that pixel
     was the whole of the page's scroll. */
  height: calc(var(--arc-stage, 88dvh) - 1px);
  background: var(--arc-glass, #050506);
  /* clip, never hidden: hidden makes a scroll container and freezes every
     view() timeline inside it at its end state. */
  overflow: clip;
  color: var(--pickle-text);
}

/* ====================================================================== scene
 * Four layers of garden, the head strip, the glass, the toasts and whatever
 * overlay is running. All of it is CSS and inline SVG: there is no raster in
 * the scene at all, so it costs one paint and no request. */
.pat-scene {
  position: relative;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-content: center;
  justify-items: center;
  gap: calc(var(--pat-cell) * .1);
  min-height: 0;
  padding: calc(var(--pat-cell) * .16) 1rem 0;
  overflow: clip;
}

.pat-sky,
.pat-hills,
.pat-rows,
.pat-haze {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.pat-sky {
  background: linear-gradient(
    180deg,
    var(--pat-sky-top) 0%,
    color-mix(in oklab, var(--pat-sky-mid) 42%, var(--pat-sky-top)) 38%,
    color-mix(in oklab, var(--pat-sky-mid) 62%, var(--pat-sky-low)) 62%,
    var(--pat-sky-low) 100%
  );
}

/* The far hills: an SVG silhouette sitting on the bottom of the scene, in two
   paths so there is a horizon behind a horizon. preserveAspectRatio="none" on
   the element, so it stretches with the stage rather than cropping. */
.pat-hills {
  inset: auto 0 0 0;
  height: 30%;
  width: 100%;
  display: block;
}

.pat-hill-far { fill: var(--pat-hill-far); }
.pat-hill-near { fill: var(--pat-hill-near); }

/* The rows of the patch, converging. Two repeating gradients rather than a
   drawn perspective: at 35% over a dark sky the eye reads furrows and not
   stripes, and it costs nothing. */
.pat-rows {
  inset: auto 0 0 0;
  height: 26%;
  opacity: .35;
  background:
    repeating-linear-gradient(
      92deg,
      rgb(0 0 0 / 55%) 0 2px,
      transparent 2px 5%
    ),
    linear-gradient(180deg, transparent 0%, rgb(0 0 0 / 70%) 100%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 45%);
}

/* The light the glass sits in. A pool of the cabinet's own terracotta behind
   the grid, which is how every other cabinet on this site is lit. */
.pat-haze {
  background: radial-gradient(
    62% 52% at 50% 46%,
    color-mix(in oklab, var(--pat-light) 22%, transparent) 0%,
    transparent 70%
  );
}

/* =================================================================== the head
 * Jar on the left, wordmark in the middle, and on the right either the proof
 * that this spin was committed to before it was pressed or, during a free
 * round, the counter. It spans the glass's width so the three sit over their
 * own columns. */
.pat-head {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 1rem;
  width: min(100%, calc(var(--pat-cell) * 4 + var(--pat-gap) * 3 + var(--pat-pad) * 2));
}

/* -------------------------------------------------------------- the jar
 * The Pickle Meter, which was a 14px bar nobody noticed. Ten notches of brine
 * in a jar: --pat-brine is a number between 0 and 1 that JS writes on the
 * element, and the fill's geometry reads it. */
.pat-jar {
  --pat-brine: 0;
  display: flex;
  align-items: center;
  gap: .5rem;
  justify-self: start;
}

.pat-jar-glass {
  display: block;
  width: 2.6rem;
  height: 3.2rem;
  overflow: visible;
}

.pat-jar-body { fill: rgb(255 255 255 / 5%); stroke: rgb(255 255 255 / 26%); stroke-width: 2; }
.pat-jar-lid { fill: #4e2e18; stroke: rgb(255 255 255 / 20%); stroke-width: 1.5; transform-origin: 50% 100%; }
.pat-jar-fill { fill: color-mix(in oklab, var(--pat-wild-line) 62%, #1f3a2c); }
.pat-jar-wave { fill: color-mix(in oklab, var(--pat-wild-line) 82%, #fff); }
.pat-jar-shine { fill: rgb(255 255 255 / 14%); }

.pat-jar-count {
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--pickle-text-xs);
  color: var(--pickle-muted);
  min-width: 3.4ch;
}

.pat-jar.pat-full .pat-jar-body { stroke: var(--pat-wild-line); }
.pat-jar.pat-full .pat-jar-count { color: var(--pat-wild-line); }

/* ------------------------------------------------------- the wordmark */
.pat-mark-wrap {
  display: grid;
  justify-items: center;
  gap: .15rem;
}

.pat-mark {
  display: block;
  width: min(13rem, 42vw);
  height: auto;
}

.pat-mark-letters {
  fill: var(--pat-gold);
  stroke: var(--pat-gold-deep);
  stroke-width: 1;
  paint-order: stroke fill;
  font-family: var(--pickle-font-display);
  font-weight: 700;
  font-size: 38px;
  letter-spacing: 2px;
}

.pat-mark-sheen { fill: #fff; opacity: .55; }

.pat-mark-sub {
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-2xs);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--pickle-subtle);
}

/* ------------------------------------------- the commitment, and the round
 * The right column carries one of two things. Out of a free round it is the
 * proof pill: the operator published the hash of its half of this spin's seed
 * before the press, and this is that hash, lit while it is unspent. It is the
 * fairness argument made visible instead of read. During a free round it is
 * the counter and the round's running total. */
.pat-right {
  justify-self: end;
  display: grid;
  justify-items: end;
  gap: .2rem;
  text-align: right;
}

.pat-armed {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .3rem .55rem;
  border: 1px solid rgb(255 255 255 / 12%);
  border-radius: 999px;
  background: rgb(0 0 0 / 30%);
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-2xs);
  color: var(--pickle-subtle);
}

.pat-armed.pat-on {
  border-color: color-mix(in oklab, var(--pat-wild-line) 45%, transparent);
  color: var(--pickle-muted);
}

.pat-armed-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--pickle-subtle);
}

.pat-armed.pat-on .pat-armed-dot { background: var(--pat-wild-line); }

.pat-feature-count {
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--pickle-text-sm);
  color: var(--pat-gold);
}

.pat-feature-total {
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--pickle-text-2xs);
  color: var(--pickle-muted);
}

/* ================================================================== the glass
 * The panel the reels stand in, and the memorable object on this page. An
 * inner top highlight, an inner shadow at the foot, a deep drop under it and a
 * reflection that sweeps across every nine seconds. This is the rule the
 * estate's flat register is suspended for. */
.pat-glass {
  position: relative;
  z-index: 1;
  align-self: center;
  padding: var(--pat-pad);
  border: 1px solid rgb(255 255 255 / 14%);
  border-radius: 1.25rem;
  background: linear-gradient(180deg, rgb(255 255 255 / 6%), rgb(0 0 0 / 22%));
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 18%),
    inset 0 -12px 30px rgb(0 0 0 / 40%),
    0 30px 60px rgb(0 0 0 / 55%);
  overflow: clip;
}

/* Two scatters are down and a reel is still turning. The border goes gold and
   pulses; the heartbeat cue is fired on the same rhythm by the page. */
.pat-glass.pat-tense {
  border-color: color-mix(in oklab, var(--pat-gold) 55%, transparent);
}

.pat-glass::after {
  content: "";
  position: absolute;
  inset: -40%;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(
    115deg,
    transparent 42%,
    rgb(255 255 255 / 7%) 50%,
    transparent 58%
  );
  opacity: 0;
}

/* ==================================================================== reels */
.pat-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(4, var(--pat-cell));
  gap: var(--pat-gap);
}

/* One reel is a three-cell window onto a strip. The strip is the only thing
   that moves, and it moves by translateY and nothing else. */
.pat-reel {
  position: relative;
  height: calc(var(--pat-cell) * 3);
  overflow: clip;
  border-radius: calc(var(--pat-cell) * .08);
  background: linear-gradient(180deg, #0c0708 0%, #14090a 50%, #0c0708 100%);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 6%);
}

/* The shade at the top and bottom of the window: a reel is a cylinder and the
   cells at its edges are turning away from the light. */
.pat-reel::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgb(0 0 0 / 62%) 0%,
    transparent 22%,
    transparent 78%,
    rgb(0 0 0 / 62%) 100%
  );
}

.pat-strip {
  position: absolute;
  inset: 0 0 auto 0;
  display: grid;
  grid-auto-rows: var(--pat-cell);
  will-change: transform;
}

/* ===================================================================== cells
 * A cell is a tile with a vegetable or a badge on it. The tile is the
 * ::before, which is why every state below can recolour the tile without
 * touching what is drawn on it. */
.pat-cell {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--pat-cell);
  height: var(--pat-cell);
  font-style: normal;
}

.pat-cell::before {
  content: "";
  position: absolute;
  inset: calc(var(--pat-cell) * .035);
  border-radius: calc(var(--pat-cell) * .1);
  background: linear-gradient(160deg, var(--pat-tile-a, #2b1b17), var(--pat-tile-b, #1c1110));
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 8%),
    inset 0 -6px 14px rgb(0 0 0 / 45%);
  border: 1px solid var(--pat-tile-line, rgb(255 255 255 / 8%));
}

/* The tiers. Eight vegetables in three temperatures, then the three specials,
   each with a hairline of its own colour. */
.pat-radish,
.pat-onion,
.pat-potato,
.pat-leek { --pat-tile-a: var(--pat-soil-a); --pat-tile-b: var(--pat-soil-b); }

.pat-salad,
.pat-tomato { --pat-tile-a: var(--pat-leaf-a); --pat-tile-b: var(--pat-leaf-b); }

.pat-pepper,
.pat-pickle { --pat-tile-a: var(--pat-ember-a); --pat-tile-b: var(--pat-ember-b); }

.pat-royal {
  --pat-tile-a: var(--pat-royal-a);
  --pat-tile-b: var(--pat-royal-b);
  --pat-tile-line: color-mix(in oklab, var(--pat-gold) 50%, transparent);
}

.pat-wild {
  --pat-tile-a: var(--pat-wild-a);
  --pat-tile-b: var(--pat-wild-b);
  --pat-tile-line: color-mix(in oklab, var(--pat-wild-line) 50%, transparent);
}

.pat-bonus {
  --pat-tile-a: var(--pat-bonus-a);
  --pat-tile-b: var(--pat-bonus-b);
  --pat-tile-line: color-mix(in oklab, var(--pat-bonus-line) 50%, transparent);
}

/* The vegetable itself, one row of eight in patch-veg.webp. background-size
   800% makes each cell a full width, and the position is i of 7 across, which
   is the only place these eight numbers are written down. */
.pat-veg {
  position: relative;
  z-index: 1;
  display: block;
  width: calc(var(--pat-cell) * .78);
  height: calc(var(--pat-cell) * .78);
  background-image: url("/assets/patch-veg.webp");
  background-repeat: no-repeat;
  background-size: 800% 100%;
  filter: drop-shadow(0 .28em .45em rgb(0 0 0 / 55%));
}

.pat-veg-radish { background-position: 0% 50%; }
.pat-veg-onion { background-position: 14.2857% 50%; }
.pat-veg-potato { background-position: 28.5714% 50%; }
.pat-veg-leek { background-position: 42.8571% 50%; }
.pat-veg-salad { background-position: 57.1428% 50%; }
.pat-veg-tomato { background-position: 71.4285% 50%; }
.pat-veg-pepper { background-position: 85.7142% 50%; }
.pat-veg-pickle { background-position: 100% 50%; }

/* The three specials, drawn rather than cropped, because they carry words and
   a word baked into a raster cell is unreadable at a third of its size. */
.pat-badge {
  position: relative;
  z-index: 1;
  display: block;
  width: calc(var(--pat-cell) * .84);
  height: calc(var(--pat-cell) * .84);
  overflow: visible;
}

.pat-badge-crown { fill: var(--pat-gold); stroke: var(--pat-gold-deep); stroke-width: 1.5; stroke-linejoin: round; }
.pat-badge-disc { fill: #8a5f12; stroke: var(--pat-gold); stroke-width: 2; }
.pat-badge-word {
  fill: var(--pat-gold-hi);
  stroke: var(--pat-gold-deep);
  stroke-width: .8;
  paint-order: stroke fill;
  font-family: var(--pickle-font-display);
  font-weight: 700;
  text-anchor: middle;
}
.pat-badge-small { font-size: 13px; letter-spacing: 1.6px; }
.pat-badge-large { font-size: 22px; letter-spacing: 1.2px; }
.pat-badge-sheen { fill: #fff; opacity: .45; }

.pat-badge-leaf { fill: color-mix(in oklab, var(--pat-wild-line) 70%, #0f2018); }
.pat-badge-wreath { transform-origin: 50% 54%; }
.pat-badge-wild-word {
  fill: #dfffe8;
  stroke: #0f2018;
  stroke-width: .9;
  paint-order: stroke fill;
  font-family: var(--pickle-font-display);
  font-weight: 700;
  font-size: 23px;
  letter-spacing: 1px;
  text-anchor: middle;
}

.pat-badge-packet { fill: #331029; stroke: var(--pat-bonus-line); stroke-width: 1.6; }
.pat-badge-rays { fill: color-mix(in oklab, var(--pat-bonus-line) 40%, transparent); transform-origin: 50% 50%; }
.pat-badge-bonus-word {
  fill: #ffe6f8;
  stroke: #220e20;
  stroke-width: .9;
  paint-order: stroke fill;
  font-family: var(--pickle-font-display);
  font-weight: 700;
  font-size: 19px;
  letter-spacing: 1.2px;
  text-anchor: middle;
}

/* ============================================================== cell states */

/* A cell a paid line crossed. Its tile goes gold at the edge and a pool of
   gold sits behind it. */
.pat-cell.pat-won::before {
  border-color: var(--pat-gold);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 16%),
    inset 0 -6px 14px rgb(0 0 0 / 35%),
    0 0 0 1px color-mix(in oklab, var(--pat-gold) 40%, transparent),
    0 0 22px color-mix(in oklab, var(--pat-gold) 35%, transparent);
}

/* Everything the win did not touch steps back, so the win is read and not
   searched for. */
.pat-cell.pat-dim { opacity: .5; }

/* A scatter that counted toward a round. */
.pat-cell.pat-scatter::before {
  border-color: var(--pat-bonus-line);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 16%),
    0 0 20px color-mix(in oklab, var(--pat-bonus-line) 40%, transparent);
}

/* A ROYAL held in place for the rest of a free round. The frame and the small
   padlock say it: the reel behind it still turns. */
.pat-cell.pat-lock::before {
  border-color: var(--pat-gold);
  box-shadow: inset 0 0 0 2px color-mix(in oklab, var(--pat-gold) 60%, transparent);
}

.pat-lock-mark {
  position: absolute;
  z-index: 2;
  top: calc(var(--pat-cell) * .08);
  right: calc(var(--pat-cell) * .08);
  width: calc(var(--pat-cell) * .16);
  height: calc(var(--pat-cell) * .16);
  fill: var(--pat-gold);
}

/* The cell the meter's WILD was put on. Its badge turns over to arrive, which
   is why the cell needs a perspective of its own. */
.pat-cell.pat-gift { perspective: 40rem; }

/* A cell lifted above its own strip: the sticky ROYAL during a free round. */
.pat-sticky {
  position: absolute;
  z-index: 2;
  left: 0;
  width: var(--pat-cell);
  height: var(--pat-cell);
  pointer-events: none;
}

/* ================================================================= win lines
 * An SVG over the grid in CELL UNITS, so a line is drawn once and stretches
 * with the cabinet. The dash reveal is measured at runtime with
 * getTotalLength(): Safari on iOS ignores pathLength for stroke-dasharray and
 * would draw the line as a repeating pattern instead of drawing it on. */
.pat-lines {
  position: absolute;
  inset: var(--pat-pad);
  z-index: 2;
  pointer-events: none;
  overflow: visible;
}

.pat-line {
  fill: none;
  stroke: var(--pat-gold);
  stroke-width: .05;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .9;
}

.pat-line.pat-solo { stroke-width: .075; opacity: 1; }

.pat-line-pay {
  fill: var(--pat-gold-hi);
  font-family: var(--pickle-font-mono);
  font-size: .2px;
  text-anchor: middle;
  paint-order: stroke fill;
  stroke: #150b0a;
  stroke-width: .05px;
}

/* ================================================================ the canvas
 * Sparks, coins and motes. It never draws a fact of the game: no figure, no
 * symbol, no text, and the page is complete with it absent. */
.pat-fx {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
}

/* ==================================================================== toasts
 * Every sentence the machine has to say, stacked above the bar: a refusal, a
 * settle that failed, a refund that is owed, a room catching up. At most
 * three, and one of them may carry the button that takes a stake back. */
.pat-toasts {
  position: absolute;
  z-index: 6;
  left: 50%;
  bottom: calc(var(--pat-cell) * .2);
  transform: translateX(-50%);
  display: grid;
  gap: .5rem;
  width: min(34rem, calc(100% - 2rem));
}

.pat-toast {
  display: grid;
  gap: .6rem;
  padding: .75rem 1rem;
  border: 1px solid rgb(255 255 255 / 14%);
  border-radius: .75rem;
  background: rgb(12 7 8 / 88%);
  backdrop-filter: blur(14px);
  box-shadow: 0 12px 30px rgb(0 0 0 / 50%);
  font-size: var(--pickle-text-sm);
  line-height: 1.5;
  color: var(--pickle-muted);
}

.pat-toast.pat-warn {
  border-color: var(--pickle-amber-line);
  color: var(--pickle-amber);
}

.pat-toast.pat-stop {
  border-color: rgb(255 92 92 / 45%);
  color: #ff8f8f;
}

.pat-toast-btn {
  justify-self: start;
  min-height: var(--pickle-tap);
  padding: .5rem 1.1rem;
  border: 1px solid var(--pat-gold);
  border-radius: .6rem;
  background: color-mix(in oklab, var(--pat-gold) 14%, transparent);
  color: var(--pat-gold-hi);
  font-family: var(--pickle-font-display);
  font-size: var(--pickle-text-sm);
  cursor: pointer;
}

.pat-toast-btn:disabled {
  border-color: rgb(255 255 255 / 16%);
  background: transparent;
  color: var(--pickle-subtle);
  cursor: default;
}

/* ================================================================== overlays
 * Three sizes of celebration and the two ends of a free round. Everything they
 * print comes from the settled frame; when one closes, the page underneath is
 * the landed grid with its lines shown and the bar's figure set. */
.pat-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: .4rem;
  padding: 1rem;
  text-align: center;
  background: rgb(6 3 4 / 70%);
  backdrop-filter: blur(3px);
  border: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.pat-overlay.pat-intro,
.pat-overlay.pat-outro { background: rgb(6 3 4 / 55%); }

/* Ten to fifty times the stake. The rays turn behind the figure and the ground
   goes darker than the ribbon's, because this one is worth looking up for. */
.pat-overlay.pat-big { background: rgb(6 3 4 / 76%); }

/* Fifty times and up, the rarest thing this cabinet does: the same overlay
   with the gold vignette closing in around it. */
.pat-overlay.pat-harvest { background: rgb(8 4 2 / 80%); }

.pat-rays {
  position: absolute;
  inset: -20%;
  z-index: -1;
  pointer-events: none;
  background: conic-gradient(
    from 0deg,
    transparent 0deg 8deg,
    color-mix(in oklab, var(--pat-gold) 12%, transparent) 8deg 16deg,
    transparent 16deg 24deg
  );
  mask-image: radial-gradient(closest-side, #000 20%, transparent 78%);
}

.pat-vignette {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  box-shadow: inset 0 0 8rem 2rem color-mix(in oklab, var(--pat-gold) 22%, transparent);
}

.pat-title {
  font-family: var(--pickle-font-display);
  font-weight: 700;
  font-size: clamp(1.8rem, 5vw, 3.2rem);
  letter-spacing: -.02em;
  line-height: 1;
  color: var(--pat-gold-hi);
}

.pat-figure {
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: clamp(2.2rem, 7vw, 4.4rem);
  letter-spacing: -.04em;
  line-height: .95;
  color: var(--pat-gold);
}

.pat-sub {
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-sm);
  color: var(--pickle-muted);
}

.pat-small {
  max-width: 32rem;
  font-size: var(--pickle-text-2xs);
  line-height: 1.6;
  color: var(--pickle-subtle);
}

/* NICE is a ribbon over the top of the glass, not a plate over the grid: the
   cells it is about are the ones the old banner covered. */
.pat-banner {
  position: absolute;
  z-index: 5;
  left: 50%;
  top: 0;
  transform: translateX(-50%);
  display: grid;
  justify-items: center;
  gap: .1rem;
  padding: .7rem 2.2rem 1rem;
  border-radius: 0 0 1rem 1rem;
  background: linear-gradient(180deg, var(--pat-gold) 0%, #d99a2c 100%);
  box-shadow: 0 10px 24px rgb(0 0 0 / 45%);
  color: #2a1a05;
  text-align: center;
}

.pat-banner-title {
  font-family: var(--pickle-font-display);
  font-weight: 700;
  font-size: clamp(1.1rem, 2.4vw, 1.6rem);
  letter-spacing: .02em;
  line-height: 1;
}

.pat-banner-sub {
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--pickle-text-2xs);
  color: #58400f;
}

/* The reduced-motion free round: one card, every grid's pay in a row. */
.pat-summary {
  display: grid;
  gap: .5rem;
  max-width: min(30rem, 100%);
  padding: 1rem 1.2rem;
  border: 1px solid rgb(255 255 255 / 14%);
  border-radius: .9rem;
  background: rgb(12 7 8 / 92%);
  text-align: left;
}

.pat-summary-row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--pickle-text-xs);
  color: var(--pickle-muted);
}

/* ================================================================= the bar
 * One row on a desktop: what you have, what you are betting, the press, and
 * the three switches. Under it, what the last spin did and the ten before it.
 * Nothing here is decorative and nothing here is a paragraph. */
.pat-bar {
  position: relative;
  z-index: 7;
  border-top: 1px solid rgb(255 255 255 / 12%);
  background: rgb(20 12 10 / 82%);
  backdrop-filter: blur(14px);
}

.pat-bar-in {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-items: center;
  gap: .35rem 1.25rem;
  max-width: var(--pickle-content);
  margin: 0 auto;
  padding: .75rem var(--pickle-gutter) .9rem;
}

.pat-left {
  grid-column: 1;
  grid-row: 1;
  display: flex;
  align-items: center;
  gap: 1.25rem;
  min-width: 0;
}

.pat-tools {
  grid-column: 3;
  grid-row: 1;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: .5rem;
}

.pat-meta {
  grid-column: 1;
  grid-row: 2;
}

.pat-hist {
  grid-column: 3;
  grid-row: 2;
  justify-self: end;
}

/* ------------------------------------------------------------- the figures */
.pat-vault,
.pat-last {
  display: grid;
  gap: .1rem;
  min-width: 0;
}

.pat-label {
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-2xs);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--pickle-subtle);
  white-space: nowrap;
}

.pat-vault-figure {
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: clamp(1.05rem, 1.7vw, 1.45rem);
  letter-spacing: -.03em;
  color: var(--pickle-text);
}

.pat-vault-figure.pat-unknown { color: var(--pickle-subtle); }

.pat-last-figure {
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: clamp(1.15rem, 2vw, 1.7rem);
  letter-spacing: -.03em;
  color: var(--pickle-muted);
}

.pat-last-figure.pat-up { color: var(--pat-gold); }
.pat-last-figure.pat-none { color: var(--pickle-subtle); }

/* ---------------------------------------------------------------- the bet */
.pat-bet {
  display: flex;
  align-items: center;
  gap: .6rem;
}

.pat-step {
  display: grid;
  place-items: center;
  width: var(--pickle-tap);
  height: var(--pickle-tap);
  border: 1px solid rgb(255 255 255 / 18%);
  border-radius: .6rem;
  background: rgb(255 255 255 / 4%);
  color: var(--pickle-text);
  font-family: var(--pickle-font-mono);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
}

.pat-step:disabled {
  border-color: rgb(255 255 255 / 10%);
  background: transparent;
  color: var(--pickle-subtle);
  cursor: default;
}

.pat-bet-read {
  display: grid;
  justify-items: center;
  gap: .05rem;
  min-width: 7.5rem;
}

.pat-bet-figure {
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: clamp(1.15rem, 2vw, 1.6rem);
  letter-spacing: -.03em;
  color: var(--pickle-text);
}

.pat-bet-sub {
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-2xs);
  color: var(--pickle-subtle);
  white-space: nowrap;
}

.pat-max {
  min-height: var(--pickle-tap);
  padding-inline: .9rem;
  border: 1px solid rgb(255 255 255 / 18%);
  border-radius: .6rem;
  background: transparent;
  color: var(--pickle-muted);
  font-family: var(--pickle-font-display);
  font-size: var(--pickle-text-xs);
  cursor: pointer;
}

.pat-max:disabled { color: var(--pickle-subtle); cursor: default; }

/* -------------------------------------------------------------- the press
 * A round terracotta button with a gold ring, as big as a cell. Four states,
 * and the dead one keeps its click: a press on a refused spin has to be able
 * to arm the audio and play the refusal. */
.pat-spin {
  grid-column: 2;
  grid-row: 1 / span 2;
  position: relative;
  display: grid;
  place-items: center;
  gap: .05rem;
  width: clamp(5rem, var(--pat-cell), 8.5rem);
  height: clamp(5rem, var(--pat-cell), 8.5rem);
  border: 0;
  border-radius: 50%;
  background:
    radial-gradient(120% 120% at 50% 22%, var(--pat-light-hi) 0%, var(--pat-light) 45%, #8f3a16 100%);
  box-shadow:
    inset 0 2px 0 rgb(255 255 255 / 28%),
    inset 0 -10px 18px rgb(0 0 0 / 35%),
    0 10px 26px rgb(0 0 0 / 45%),
    0 0 0 2px color-mix(in oklab, var(--pat-gold) 55%, transparent);
  color: #fff;
  cursor: pointer;
}

.pat-spin-label {
  font-family: var(--pickle-font-display);
  font-weight: 700;
  font-size: clamp(.95rem, 1.5vw, 1.2rem);
  letter-spacing: .06em;
  text-shadow: 0 1px 2px rgb(0 0 0 / 45%);
}

.pat-spin-hint {
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-2xs);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgb(255 255 255 / 70%);
}

/* The transaction is in flight. A conic sweep runs round the ring, in the
   estate's white-at-55% for a move that is neither confirmed nor refused. */
.pat-spin.pat-pending::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  background: conic-gradient(from 0deg, transparent 0deg 250deg, var(--arc-pending) 320deg, transparent 360deg);
  mask-image: radial-gradient(closest-side, transparent 92%, #000 93%);
}

/* The chain has it and the reels are turning. */
.pat-spin.pat-turning {
  background: radial-gradient(120% 120% at 50% 22%, #63301a 0%, #431e10 60%, #2a1208 100%);
  box-shadow:
    inset 0 2px 0 rgb(255 255 255 / 10%),
    inset 0 -10px 18px rgb(0 0 0 / 45%),
    0 0 0 2px rgb(255 255 255 / 12%);
  color: var(--pickle-muted);
  cursor: default;
}

/* A refusal the page predicted before the press. Unfilled, and the sentence is
   in a toast rather than in six words under the button. */
.pat-spin.pat-dead {
  background: transparent;
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 18%);
  color: var(--pickle-subtle);
}

.pat-spin.pat-dead .pat-spin-hint { color: var(--pickle-subtle); }

/* --------------------------------------------------------------- switches */
.pat-toggle {
  display: grid;
  place-items: center;
  min-width: var(--pickle-tap);
  min-height: var(--pickle-tap);
  padding-inline: .8rem;
  border: 1px solid rgb(255 255 255 / 16%);
  border-radius: .6rem;
  background: transparent;
  color: var(--pickle-muted);
  font-family: var(--pickle-font-display);
  font-size: var(--pickle-text-xs);
  cursor: pointer;
}

.pat-toggle.pat-on {
  border-color: color-mix(in oklab, var(--pat-gold) 55%, transparent);
  background: color-mix(in oklab, var(--pat-gold) 12%, transparent);
  color: var(--pat-gold);
}

.pat-toggle-ico {
  display: block;
  width: 1.1rem;
  height: 1.1rem;
  fill: currentColor;
}

/* ------------------------------------------------------------- the history
 * Ten dots, oldest on the left. Gold up, plain down, a wider gold dot for a
 * spin that was worth a banner, a hollow one for a slot with nothing in it.
 * The rule is the console's old one: the NET, never the gross. */
.pat-dots {
  display: flex;
  align-items: center;
  gap: .35rem;
}

.pat-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--pickle-subtle);
}

.pat-dot.pat-up { background: var(--pat-gold); }
.pat-dot.pat-down { background: rgb(255 255 255 / 28%); }
.pat-dot.pat-big { width: 11px; height: 11px; background: var(--pat-gold-hi); }
.pat-dot.pat-empty { background: transparent; box-shadow: inset 0 0 0 1px rgb(255 255 255 / 18%); }

/* ==================================================================== drawer
 * Paytable, fairness and this account's last ten spins. It is a drawer and not
 * a rail because a slot machine is read in glances and its small print is read
 * once: the old page put 24rem of grey prose beside the cabinet for ever. */
.pat-scrim {
  position: fixed;
  inset: 0;
  z-index: var(--pickle-z-overlay, 60);
  border: 0;
  padding: 0;
  background: rgb(0 0 0 / 55%);
  cursor: pointer;
}

.pat-drawer {
  position: fixed;
  z-index: calc(var(--pickle-z-overlay, 60) + 1);
  top: var(--pickle-nav-h);
  right: 0;
  bottom: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  width: min(26rem, 100vw);
  border-left: 1px solid rgb(255 255 255 / 14%);
  background: #100a09;
  box-shadow: -20px 0 50px rgb(0 0 0 / 55%);
}

.pat-drawer-head {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .75rem 1rem;
  border-bottom: 1px solid rgb(255 255 255 / 10%);
}

.pat-tabs {
  display: flex;
  gap: .35rem;
  flex: 1 1 auto;
  min-width: 0;
}

.pat-tab {
  min-height: var(--pickle-tap);
  padding-inline: .8rem;
  border: 1px solid transparent;
  border-radius: .5rem;
  background: transparent;
  color: var(--pickle-subtle);
  font-family: var(--pickle-font-display);
  font-size: var(--pickle-text-xs);
  cursor: pointer;
}

.pat-tab.pat-on {
  border-color: rgb(255 255 255 / 16%);
  background: rgb(255 255 255 / 6%);
  color: var(--pickle-text);
}

.pat-close {
  display: grid;
  place-items: center;
  width: var(--pickle-tap);
  height: var(--pickle-tap);
  border: 1px solid rgb(255 255 255 / 16%);
  border-radius: .5rem;
  background: transparent;
  color: var(--pickle-muted);
  cursor: pointer;
}

.pat-close-ico { display: block; width: 1rem; height: 1rem; stroke: currentColor; stroke-width: 1.6; fill: none; }

.pat-panel {
  overflow-y: auto;
  padding: 1rem;
}

/* ------------------------------------------------------------- the paytable
 * The figures come from lib/patch-reels.js, which is a COPY of the contract's
 * table, for drawing only. What is printed is the pay at the bet on the bar
 * right now, because a multiple of a line bet is a number a player has to do
 * arithmetic on and a figure in PKL is one they can read. */
.pat-pay {
  display: grid;
  gap: 1px;
  background: rgb(255 255 255 / 8%);
  border: 1px solid rgb(255 255 255 / 8%);
  border-radius: .6rem;
  overflow: clip;
}

.pat-pay-head,
.pat-pay-row {
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr) repeat(3, 3.2rem);
  align-items: center;
  gap: .5rem;
  padding: .45rem .6rem;
  background: #100a09;
}

.pat-pay-head {
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-2xs);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--pickle-subtle);
  text-align: right;
}

.pat-pay-chip {
  --pat-cell: 2.4rem;
  width: 2.4rem;
  height: 2.4rem;
}

.pat-pay-name {
  font-size: var(--pickle-text-xs);
  color: var(--pickle-text);
}

.pat-pay-n {
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--pickle-text-xs);
  color: var(--pickle-muted);
  text-align: right;
}

.pat-pay-n.pat-none { color: var(--pickle-subtle); }

.pat-rule {
  margin-top: .9rem;
  font-size: var(--pickle-text-xs);
  line-height: 1.6;
  color: var(--pickle-muted);
}

.pat-rule + .pat-rule { margin-top: .6rem; }

/* ---------------------------------------------------------------- history */
.pat-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: .5rem;
  padding: .55rem 0;
  border-bottom: 1px solid rgb(255 255 255 / 8%);
}

.pat-row-what {
  font-family: var(--pickle-font-mono);
  font-size: var(--pickle-text-2xs);
  color: var(--pickle-subtle);
}

.pat-row-net {
  font-family: var(--pickle-font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--pickle-text-sm);
  color: var(--pickle-muted);
}

.pat-row-net.pat-up { color: var(--pat-gold); }

.pat-empty {
  padding: 1.5rem 0;
  font-size: var(--pickle-text-xs);
  color: var(--pickle-subtle);
  text-align: center;
}

/* =================================================================== phones
 * The bar wraps to three rows with the press in the middle of the last one,
 * the drawer comes up from the bottom, and the cell is sized from the width
 * rather than the height. */
@media (max-width: 899px) {
  .pat-drawer {
    top: auto;
    left: 0;
    right: 0;
    height: 78dvh;
    width: auto;
    border-left: 0;
    border-top: 1px solid rgb(255 255 255 / 14%);
    border-radius: 1rem 1rem 0 0;
    box-shadow: 0 -20px 50px rgb(0 0 0 / 55%);
  }
}

@media (max-width: 639px) {
  .pat-band { --pat-chrome: 16rem; }

  .pat-head { gap: .5rem; }
  .pat-mark { width: min(9rem, 40vw); }
  .pat-jar-glass { width: 2rem; height: 2.5rem; }

  .pat-bar-in {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    grid-template-rows: auto auto auto;
    row-gap: .6rem;
  }

  .pat-left {
    grid-column: 1 / -1;
    grid-row: 1;
    justify-content: space-between;
    gap: .75rem;
  }

  .pat-meta { grid-column: 1; grid-row: 2; }
  .pat-hist { grid-column: 3; grid-row: 2; }

  .pat-spin {
    grid-column: 2;
    grid-row: 3;
    width: 5.5rem;
    height: 5.5rem;
  }

  .pat-tools {
    grid-column: 3;
    grid-row: 3;
    justify-self: end;
  }

  .pat-bet-read { min-width: 6rem; }
  .pat-bet { gap: .35rem; }
  .pat-toasts { bottom: .5rem; }
}

/* ================================================================ keyframes
 * Declared outside the motion gate so a rule below may name one; nothing moves
 * until a gated rule uses it. */

@keyframes pat-drift {
  from { transform: translateX(-1.5%); }
  to { transform: translateX(1.5%); }
}

@keyframes pat-reflect {
  from { transform: translateX(-60%); opacity: 0; }
  20% { opacity: 1; }
  80% { opacity: 1; }
  to { transform: translateX(60%); opacity: 0; }
}

@keyframes pat-shimmer {
  from { transform: translateX(-140%); }
  to { transform: translateX(140%); }
}

@keyframes pat-breathe {
  from { box-shadow: inset 0 2px 0 rgb(255 255 255 / 28%), inset 0 -10px 18px rgb(0 0 0 / 35%), 0 10px 26px rgb(0 0 0 / 45%), 0 0 0 2px color-mix(in oklab, var(--pat-gold) 40%, transparent); }
  to { box-shadow: inset 0 2px 0 rgb(255 255 255 / 28%), inset 0 -10px 18px rgb(0 0 0 / 35%), 0 10px 26px rgb(0 0 0 / 45%), 0 0 0 4px color-mix(in oklab, var(--pat-gold) 70%, transparent); }
}

@keyframes pat-sweep {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@keyframes pat-tense-pulse {
  from { border-color: color-mix(in oklab, var(--pat-gold) 25%, transparent); }
  to { border-color: var(--pat-gold); }
}

@keyframes pat-won-lift {
  from { transform: none; }
  to { transform: scale(1.06); }
}

@keyframes pat-ribbon-in {
  from { transform: translate(-50%, -100%); }
  to { transform: translate(-50%, 0); }
}

@keyframes pat-overlay-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes pat-title-in {
  from { opacity: 0; transform: scale(.88); }
  to { opacity: 1; transform: none; }
}

@keyframes pat-shake {
  0%, 100% { transform: translate3d(0, 0, 0); }
  20% { transform: translate3d(-3px, 1px, 0); }
  40% { transform: translate3d(3px, -1px, 0); }
  60% { transform: translate3d(-2px, -1px, 0); }
  80% { transform: translate3d(2px, 1px, 0); }
}

@keyframes pat-wave {
  from { transform: translateX(-8%); }
  to { transform: translateX(8%); }
}

@keyframes pat-lid-tilt {
  from { transform: rotate(0deg); }
  to { transform: rotate(-9deg); }
}

@keyframes pat-flip {
  from { transform: rotateY(180deg); }
  to { transform: rotateY(0deg); }
}

@keyframes pat-wreath-sway {
  from { transform: rotate(-3deg); }
  to { transform: rotate(3deg); }
}

@keyframes pat-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ===================================================================== motion
 * Everything with a duration. Above this line the page is finished; below it,
 * it is alive. A reader who asked for less motion never enters this block and
 * sees the same grid, the same lines and the same figures. */
@media (prefers-reduced-motion: no-preference) {
  /* The garden breathes. Forty seconds for a percent and a half, which is
     under the threshold at which a moving background is noticed as motion and
     over the one at which a still one is noticed as a photograph. */
  .pat-hills {
    animation-name: pat-drift;
    animation-duration: 40s;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  .pat-rows {
    animation-name: pat-drift;
    animation-duration: 56s;
    animation-direction: alternate-reverse;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  .pat-glass::after {
    animation-name: pat-reflect;
    animation-duration: 9s;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  .pat-mark-sheen {
    animation-name: pat-shimmer;
    animation-duration: 6s;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  /* The free round tints the whole scene to golden hour. The sky's own
     gradient is redeclared and the pool under it follows, because both read
     the same two tokens. */
  .pat-scene.pat-feature .pat-sky {
    background: linear-gradient(
      180deg,
      var(--pat-sun-top) 0%,
      color-mix(in oklab, var(--pat-sun-mid) 45%, var(--pat-sun-top)) 38%,
      color-mix(in oklab, var(--pat-sun-mid) 55%, var(--pat-sky-low)) 62%,
      var(--pat-sky-low) 100%
    );
    transition: background 900ms var(--pickle-ease);
  }

  .pat-scene.pat-feature .pat-haze {
    background: radial-gradient(
      62% 52% at 50% 46%,
      color-mix(in oklab, var(--pat-sun-mid) 26%, transparent) 0%,
      transparent 70%
    );
    transition: background 900ms var(--pickle-ease);
  }

  .pat-scene.pat-shake {
    animation-name: pat-shake;
    animation-duration: 1.2s;
    animation-timing-function: ease-in-out;
  }

  /* A reel at speed is a blur. The strip's own transform is driven by the Web
     Animations API from PatchReels; this class is only the blur, and it comes
     off over the same 380 ms the settle takes. */
  .pat-strip { transition: filter 380ms var(--pickle-ease); }
  .pat-reel.pat-turning .pat-strip { filter: blur(4px); }
  .pat-reel.pat-tense .pat-strip { filter: blur(2.5px); }

  .pat-glass.pat-tense {
    animation-name: pat-tense-pulse;
    animation-duration: 900ms;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  .pat-cell { transition: opacity 260ms var(--pickle-ease); }

  .pat-cell.pat-won {
    animation-name: pat-won-lift;
    animation-duration: 520ms;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  .pat-badge-rays {
    animation-name: pat-sweep;
    animation-duration: 20s;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
  }

  .pat-cell.pat-scatter .pat-badge-rays { animation-duration: 6s; }

  .pat-badge-wreath {
    animation-name: pat-wreath-sway;
    animation-duration: 3s;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  .pat-badge-sheen {
    animation-name: pat-shimmer;
    animation-duration: 4s;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  .pat-cell.pat-gift .pat-badge {
    animation-name: pat-flip;
    animation-duration: 600ms;
    animation-timing-function: var(--pickle-ease);
  }

  .pat-jar-wave {
    animation-name: pat-wave;
    animation-duration: 2.4s;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  .pat-jar-fill { transition: y 500ms var(--pickle-ease), height 500ms var(--pickle-ease); }

  .pat-jar.pat-full .pat-jar-lid {
    animation-name: pat-lid-tilt;
    animation-duration: 700ms;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
  }

  .pat-banner {
    animation-name: pat-ribbon-in;
    animation-duration: 420ms;
    animation-timing-function: var(--pickle-ease);
  }

  .pat-overlay {
    animation-name: pat-overlay-in;
    animation-duration: 300ms;
    animation-timing-function: var(--pickle-ease);
  }

  .pat-title,
  .pat-figure {
    animation-name: pat-title-in;
    animation-duration: 420ms;
    animation-timing-function: var(--pickle-ease);
  }

  .pat-rays {
    animation-name: pat-sweep;
    animation-duration: 12s;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
  }

  .pat-toast {
    animation-name: pat-fade-in;
    animation-duration: 260ms;
    animation-timing-function: var(--pickle-ease);
  }

  /* The press at rest. A ring that breathes says the machine is waiting for
     you; a still one says it is off. */
  .pat-spin {
    animation-name: pat-breathe;
    animation-duration: 2.4s;
    animation-direction: alternate;
    animation-iteration-count: infinite;
    animation-timing-function: ease-in-out;
    transition: transform 120ms var(--pickle-ease);
  }

  .pat-spin.pat-pending,
  .pat-spin.pat-turning,
  .pat-spin.pat-dead { animation-name: none; }

  .pat-spin.pat-pending::after {
    animation-name: pat-sweep;
    animation-duration: 1.1s;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
  }

  .pat-spin:active { transform: scale(.96); }

  .pat-step,
  .pat-max,
  .pat-toggle,
  .pat-tab,
  .pat-close,
  .pat-toast-btn { transition: border-color 160ms var(--pickle-ease), background 160ms var(--pickle-ease), color 160ms var(--pickle-ease); }

  .pat-step:hover:enabled,
  .pat-max:hover:enabled,
  .pat-toggle:hover,
  .pat-tab:hover,
  .pat-close:hover { border-color: rgb(255 255 255 / 34%); color: var(--pickle-text); }

  .pat-toast-btn:hover:enabled { background: color-mix(in oklab, var(--pat-gold) 24%, transparent); }

  .pat-scrim { animation-name: pat-fade-in; animation-duration: 200ms; }
}
