/* Color Bus: a bright top-down parking puzzle in the Parking Jam / Bus Jam look.
   Three things carry the art: lots cut to a silhouette, vehicles moulded with a solid side wall
   of their own deep shade, and a painted park the queue waits in. Taps are never queued, so every
   piece animates on its own timeline and nothing blocks input.

   The stage is themed with variables rather than two sets of rules: the day values live on
   .bus-layout and html[data-scene="night"] swaps them, so one board markup serves both. */
.bus-layout {
  --bus-stage-top: #f4f7fb;
  --bus-stage-low: #e6ecf4;
  --bus-stage-ink: #2b2a26;
  --bus-stage-edge: #ffffffc0;
  --bus-quiet: #4a596f;
  --bus-label: #6c7b94;
  --bus-sky-top: #bfe6ff;
  --bus-sky-low: #eaf8ff;
  --bus-turf-top: #7fd16a;
  --bus-turf-low: #48a850;
  --bus-hill: #9adf8a;
  --bus-stone: #e0cfa6;
  --bus-wall: #f2e9d2;
  --bus-door: #d8caa8;
  --bus-trunk: #8b6239;
  --bus-leaf: #3fa84c;
  --bus-leaf-lit: #5fc962;
  --bus-leaf-deep: #38a047;
  --bus-path: #cdbb93;
  --bus-mini-floor: #cfc9bb;
  --bus-slab: #b7b1a4;
}
html[data-scene="night"] .bus-layout {
  --bus-stage-top: #1b2440;
  --bus-stage-low: #121a2f;
  --bus-stage-ink: #e8eefc;
  --bus-stage-edge: #ffffff1f;
  --bus-quiet: #a8b6d4;
  --bus-label: #8f9dbd;
  --bus-sky-top: #1d2b55;
  --bus-sky-low: #38406e;
  --bus-turf-top: #2c6b48;
  --bus-turf-low: #1b4a35;
  --bus-hill: #2f6e4c;
  --bus-stone: #6d6a86;
  --bus-wall: #75719a;
  --bus-door: #4f4c70;
  --bus-trunk: #4a3726;
  --bus-leaf: #236b3c;
  --bus-leaf-lit: #2f8b4c;
  --bus-leaf-deep: #1d5b33;
  --bus-path: #514a62;
  --bus-mini-floor: #3a3f55;
}

/* The heading, stat strip and switches match Flowly's HUD so the games read as one app. */

.bus-nudge { position: absolute; z-index: 80; top: var(--s2); left: 50%; translate: -50% 0; width: max-content; max-width: calc(100% - var(--s6));
  margin: 0; padding: var(--s1) var(--s3); border-radius: 999px; background: var(--surface); color: var(--ink); box-shadow: 0 .2rem .6rem var(--shadow-deep);
  font-size: var(--text-sm); text-align: center; pointer-events: none; animation: fade-in 160ms var(--ease); }
/* The coin chip hugs the HUD so a coin lot keeps the same board room as any other on a 640px phone. */

/* Tools use the app's own button vocabulary, like every other game's control row. */
.bus-controls { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s2); min-width: 0; }
.bus-controls button { padding: var(--s2) var(--s3); font-size: var(--text-sm); }

/* The win card is the shared .win-* dialog the other games use. */
/* At least the lot's height; a taller card (pet reward, Daily actions) grows down over the finished round's
   controls instead of spilling text off both ends of the lot. */
.bus-win { position: absolute; inset: 0 0 auto; min-height: 100%; z-index: 90; display: grid; align-content: center; justify-items: center;
  gap: var(--s2); padding: var(--s4); border-radius: var(--radius-lg); text-align: center;
  background: color-mix(in srgb, var(--surface) 88%, transparent); backdrop-filter: blur(6px);
  animation: trophy-fade var(--normal) var(--ease); }
.bus-win h2, .bus-win p { margin: 0; }
.bus-win .button, .bus-win button { width: min(100%, 19rem); justify-content: center; }
.bus-win > * { min-width: 0; max-width: 100%; }
@media (max-height: 760px) {
  .bus-win { gap: var(--s1); padding: var(--s3); }
  .bus-win .button, .bus-win button { min-height: 2.5rem; padding-block: var(--s1); }
  .bus-win .win-pet { margin: 0; }
}

/* Home tiles and the pack list, built on the same shapes as Flowly's. */
.bus-mini { flex: none; width: 5.5rem; height: 5.5rem; filter: drop-shadow(0 .2rem .35rem #2a1f0a26); }

.bus-pack-list { display: grid; gap: var(--s1); margin: 0; padding: 0; list-style: none; }
.bus-pack-list li { display: flex; justify-content: space-between; gap: var(--s3); padding: var(--s1) 0;
  border-bottom: 1px solid var(--line); }
/* One hero scene on a lit band, the shape every other game's card uses. The glow is the brand
   orange over night navy, so the card is a sibling of the others rather than a copy of one. */
.hub-art.bus-art { background: radial-gradient(ellipse at 50% 112%, #ffa23d85, transparent 60%),
    linear-gradient(160deg, #3d3029, #181012); padding: var(--s4); }
.hub-art.bus-art .bus-mini { width: auto; height: 100%; aspect-ratio: 1; }

/* The board is one tall scene: park, then the road the bays sit on, then the cut lot. */
.bus-game { position: relative; min-width: 0; width: 100%; max-width: 34rem; margin-inline: auto; display: grid; gap: var(--s3);
  padding: var(--s3); border-radius: var(--radius-lg); color: var(--bus-stage-ink);
  background: linear-gradient(var(--bus-stage-top), var(--bus-stage-low));
  box-shadow: inset 0 0 0 1px var(--bus-stage-edge), 0 .6rem 1.4rem #0f1c331a; }

/* The park: a slim painted strip with the queue standing on the path across its foot. It stays
   short so the lot, where the play happens, gets the height. */
.bus-park { position: relative; display: flex; align-items: flex-end; gap: var(--s2); min-width: 0; min-height: 4.75rem;
  padding: var(--s1) var(--s2) .45rem var(--s3); border-radius: var(--radius-sm) var(--radius-sm) 0 0; overflow: hidden;
  box-shadow: inset 0 0 0 1px #0f1c3314; }
.bus-scene { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.bus-sign { position: absolute; right: var(--s2); top: var(--s2); z-index: 1; display: flex; align-items: baseline; gap: .3rem;
  padding: .1rem var(--s2) .15rem;
  border-radius: 999px; background: linear-gradient(#2f7ff0, #1f63d6); box-shadow: 0 .14rem 0 #164a9e, inset 0 .1rem 0 #fff6; }
.bus-sign strong { color: #fff; font-size: var(--text-md); line-height: 1.2; font-variant-numeric: tabular-nums; text-shadow: 0 .1rem 0 #0003; }
.bus-sign span { color: #fffc; font-size: var(--text-xs); letter-spacing: var(--tracking-label); text-transform: uppercase; }
.bus-platform { position: relative; flex: 1; display: flex; align-items: flex-end; min-width: 0; }
/* Clipped at the sides so a long queue runs off the path, but open above: the next rider is
   scaled up and must keep their head. A plain overflow:hidden would behead them. The queue starts
   at the shelter, so the rider about to board is the one standing at the stop. */
.bus-queue { display: flex; align-items: flex-end; gap: 1px; min-width: 0; margin: 0; padding: 0;
  clip-path: inset(-150% 0 -20% 0); list-style: none; }
.bus-queue li { flex: none; width: 1.1rem; transform-origin: 50% 100%;
  transition: width 160ms var(--ease), opacity 160ms linear, transform 160ms var(--ease-move); }
.bus-queue li .bus-rider { animation: bus-bob 2.6s ease-in-out infinite; animation-delay: calc(var(--i, 0) * -170ms); }
.bus-queue li.fog, .bus-bench li.fog .bus-rider { filter: saturate(0) brightness(1.08); }
.bus-queue .bus-more { width: auto; padding: 0 0 .2rem var(--s2); color: var(--bus-stage-ink); font-size: var(--text-sm); font-weight: var(--weight-label); }
@keyframes bus-bob { 50% { transform: translateY(-2px); } }
.bus-rider { display: block; width: 100%; height: auto; }
/* The stop itself: a glass shelter with a slate roof on two posts, a bench under it and a bus
   flag on a pole beside it, marking the head of the queue. The roof overhangs so it reads as shelter. */
.bus-shelter { position: relative; flex: none; margin-right: 1.6rem; align-self: flex-end;
  padding: 2.1rem .5rem .1rem; }
.bus-shelter-roof { position: absolute; left: -.7rem; right: -.7rem; top: .35rem; height: .78rem;
  border-radius: .36rem; background: linear-gradient(#67748a, #3b4454);
  box-shadow: 0 .1rem 0 #2a313c, 0 .5rem .7rem #0f1c3326, inset 0 .12rem 0 #ffffff4d; }
/* The fascia: a bright lip under the roof edge, the way a real shelter catches light. */
.bus-shelter-roof::before { content: ""; position: absolute; left: .14rem; right: .14rem; top: .72rem;
  height: .2rem; border-radius: 0 0 .16rem .16rem; background: linear-gradient(#2f6fd0, #2457a8); }
/* Two posts holding it up, set in from the overhang. */
.bus-shelter-roof::after { content: ""; position: absolute; left: .85rem; right: .85rem; top: .92rem; height: 1.6rem;
  background: linear-gradient(90deg, #7b8799 0 .3rem, #0000 .3rem calc(100% - .3rem), #5d6775 calc(100% - .3rem)); }
.bus-shelter-flag { position: absolute; right: -1.6rem; bottom: .25rem; z-index: 2; display: grid; place-items: center;
  width: 1.25rem; height: 1.25rem; border-radius: .36rem; color: #fff;
  background: linear-gradient(#4f9bff, #2f7ff0); box-shadow: 0 .18rem 0 #1b55ad, inset 0 .12rem 0 #ffffff59; }
.bus-shelter-flag::after { content: ""; position: absolute; top: 100%; width: .24rem; height: 1.9rem;
  border-radius: .12rem; background: linear-gradient(90deg, #97a3b4, #5d6775); }
.bus-shelter-flag svg { width: .85rem; height: .85rem; }
/* Glass back panel, with a timetable pinned to it. */
.bus-shelter-glass { position: absolute; left: .2rem; right: .2rem; top: 1.25rem; bottom: .1rem;
  border-radius: .22rem .22rem 0 0; background: linear-gradient(#ffffff6b, #cfe6f2ab);
  box-shadow: inset 0 0 0 1px #ffffff96, inset 0 -.1rem 0 #0f1c331a; }
.bus-shelter-glass::after { content: ""; position: absolute; left: .3rem; top: .2rem; width: .6rem; height: .55rem;
  border-radius: .1rem; background: repeating-linear-gradient(#fff 0 .16rem, #bcd0e4 .16rem .26rem);
  box-shadow: 0 .06rem .12rem #0f1c3326; }
.bus-bench { position: relative; z-index: 1; display: flex; gap: var(--s1); margin: 0; padding: 0; list-style: none; }
.bus-bench li { position: relative; width: 1.05rem; height: .7rem;
  background: linear-gradient(#e0a964, #a9743d); border-radius: .22rem .22rem 0 0;
  box-shadow: inset 0 .1rem 0 #ffffff5c; }
/* A seated rider sits behind the seat front, so only their head and shoulders show above it.
   The one boarding next sits a little larger. */
.bus-bench li .bus-rider { position: absolute; z-index: -1; left: 0; bottom: .2rem; width: 100%; }
.bus-bench li.next .bus-rider { transform: scale(1.15); transform-origin: 50% 100%; filter: drop-shadow(0 .15rem .2rem #0f1c3347); }
/* The bench legs, so the seats do not float on the pavement. */
.bus-bench li::after { content: ""; position: absolute; left: 50%; bottom: -.22rem; width: .22rem; height: .24rem;
  translate: -50% 0; border-radius: 0 0 .08rem .08rem; background: #6f7a8a; }

/* The road: asphalt with lane dashes, the bays marked out on it in dashed white. */
.bus-bays { position: relative; display: flex; justify-content: center; gap: var(--s2); min-width: 0; margin: 0;
  padding: var(--s2); list-style: none; background: linear-gradient(#636974, #4d525c);
  box-shadow: inset 0 .1rem .4rem #00000047; }
.bus-bays::before { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 3px; translate: 0 -50%;
  background: repeating-linear-gradient(90deg, #ffffff54 0 1.1rem, #fff0 1.1rem 2.4rem); }
/* A fixed height: a bay that locks, unlocks or parks a bus never resizes the row, so the lot keeps its size. */
.bus-bays li { position: relative; z-index: 1; flex: 1 1 0; min-width: 0; max-width: 4.5rem; display: grid; place-items: center;
  height: 2.75rem; padding: 3px; border-radius: .45rem; background: #4b505a;
  outline: 2px dashed #ffffff8c; outline-offset: -4px; }
.bus-bay.shut { background: #22252c; outline: none; box-shadow: inset 0 0 0 2px #00000045, 0 .15rem 0 #00000038; }
.bus-shut { display: flex; align-items: center; justify-content: center; gap: .2rem; color: #e8ebf2;
  font-size: var(--text-sm); font-weight: var(--weight-label); line-height: 1; white-space: nowrap; }
.bus-shut svg { width: 1em; height: 1em; flex: none; }
.bus-parked { position: relative; display: grid; place-items: center; gap: var(--s1); width: 100%; height: 100%; padding: var(--s1);
  border-radius: .5rem; background: linear-gradient(176deg, var(--light) -30%, var(--tint) 48%, var(--tint) 100%);
  box-shadow: inset 0 .16rem 0 #ffffff7a, inset 0 0 0 1px #00000024, 0 .2rem 0 var(--deep), 0 .34rem .5rem #0f1c3347;
  animation: bus-pull-in 280ms var(--ease-enter); will-change: transform; }
.bus-parked::after { content: ""; position: absolute; inset: 7% 12% auto; height: 22%; border-radius: .25rem;
  background: linear-gradient(#ffffff54, #ffffff14); }
@keyframes bus-pull-in { from { transform: translateY(70%) scale(.9); opacity: 0; } }
.bus-bay.away .bus-parked { transform: translateY(-180%) scale(.92); opacity: 0; transition: transform 280ms var(--ease-move), opacity 280ms linear; }
.bus-seats { position: relative; z-index: 1; display: flex; flex-wrap: wrap; justify-content: center; gap: 3px; }
.bus-seats i { width: .55rem; height: .55rem; border-radius: 50%; background: #0000002e; box-shadow: inset 0 0 0 1px #ffffff5c; }
.bus-seats i.taken { background: #fff; box-shadow: 0 0 0 1px var(--bus-edge), 0 0 .2rem #ffffffa0; }
.bus-seats i.landing { animation: bus-seat 260ms var(--ease-enter) backwards; animation-delay: calc(var(--i, 0) * 55ms); }
@keyframes bus-seat { from { transform: scale(.2) translateY(-.5rem); } 60% { transform: scale(1.3); } to { transform: scale(1); } }

/* The lot. Only the cells the outline kept get tarmac, so the silhouette is the level's face.
   Sized from a height budget rather than an aspect ratio, so the whole board fits any viewport:
   --bus-chrome is everything stacked above and below it, so no row hides under the tab bar.
   app.js measures the real budget into --fit-h; the rem guess is only the first-paint fallback. */
.bus-lot { --lot-h: min(var(--fit-h, clamp(12rem, calc(100dvh - var(--bus-chrome, 24rem)), 38rem)), 38rem);
  position: relative; container-type: inline-size; contain: layout;
  aspect-ratio: var(--cols) / var(--rows); width: min(100%, calc(var(--lot-h) * var(--cols) / var(--rows)));
  margin-inline: auto; }
:root[data-focus] .bus-lot { --lot-h: var(--fit-h, calc(100dvh - 16rem)); }
/* Shadowing the wrapper rather than each slab outlines the silhouette once, as one solid pad. */
.bus-floor { position: absolute; inset: 0;
  filter: drop-shadow(0 .18rem 0 #8e8679) drop-shadow(0 .5rem .55rem #0f1c3330); }
.bus-slab { --cell: calc(100cqw / var(--cols));
  position: absolute; left: calc(var(--c) / var(--cols) * 100%); top: calc(var(--r) / var(--rows) * 100%);
  width: calc(100% / var(--cols) + .5px); height: calc(100% / var(--rows) + .5px);
  background: var(--bus-slab); box-shadow: inset 0 0 0 .5px #0000001a; }
.bus-car { --cell: calc(100cqw / var(--cols)); --lift: calc(var(--cell) * .07);
  position: absolute; left: calc(var(--c) / var(--cols) * 100%); top: calc(var(--r) / var(--rows) * 100%);
  z-index: calc(var(--r) + 2);
  width: calc(var(--w) / var(--cols) * 100%); height: calc(var(--h) / var(--rows) * 100%);
  padding: calc(var(--cell) * .055) calc(var(--cell) * .055) calc(var(--cell) * .115);
  /* A vehicle is a button, and the app gives every button a thumb-sized minimum. In a lot whose
     cells are a fraction of that, the minimum wins and every vehicle swells over its neighbours. */
  min-width: 0; min-height: 0;
  border: 0; background: none; cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
.bus-car.wreck { cursor: default; }

/* Outlines: white, or near-black on light paint. Arrows, seats and shape marks are always white,
   edged in the bus's own deep shade so they still read on yellow. */
.bus-body, .bus-parked { --bus-ink: #fff; --bus-edge: color-mix(in srgb, var(--deep) 75%, #000); }
@supports (color: oklch(from red calc(l - .1) c h)) { .bus-body, .bus-parked { --bus-ink: oklch(from var(--tint) clamp(.2, (.66 - l) * 1e3, 1) 0 0); } }
/* One vehicle, seen from above: a moulded block of colour standing on a solid wall of its own
   deep shade, which is what gives the lot its height without a single gradient trick. The wall and
   the shadow under it both fit inside the cell's bottom padding, so nothing touches its neighbour. */
.bus-car .bus-body { position: relative; display: grid; place-items: center; width: 100%; height: 100%;
  border-radius: calc(var(--cell) * .3);
  background: linear-gradient(176deg, var(--light) -45%, var(--tint) 42%, var(--tint) 100%);
  box-shadow: inset 0 calc(var(--cell) * .06) 0 #ffffff8f, inset 0 calc(var(--cell) * -.05) 0 #0000001f,
    inset 0 0 0 max(1.5px, calc(var(--cell) * .035)) color-mix(in srgb, var(--deep) 50%, #000),
    0 var(--lift) 0 var(--deep), 0 calc(var(--lift) + .02rem) calc(var(--cell) * .07) #2a1f0a40;
  transition: transform 110ms var(--ease), filter var(--fast) var(--ease); }
/* A moulded roof panel, inset from the rim, so a vehicle reads as a solid body and not a flat tile. */
.bus-car .bus-body::after { content: ""; position: absolute; inset: calc(var(--cell) * .085);
  border-radius: calc(var(--cell) * .22); pointer-events: none;
  box-shadow: inset 0 0 0 1px #ffffff2e, 0 0 0 1px #00000012; }
.bus-car:active .bus-body { transform: translateY(calc(var(--lift) * .55)) scale(.97); }
.bus-car:focus-visible { outline: none; }
.bus-car:focus-visible .bus-body { outline: 3px solid var(--bus-ink); outline-offset: -3px; }
/* Tinted screen at the nose and a smaller one at the tail: the way it faces, readable without the arrow. */
.bus-cab, .bus-tail { position: absolute; z-index: 1; border-radius: calc(var(--cell) * .11); }
/* Glass, not a hole: a dark pane with a lit edge, so the nose reads as a windshield. */
.bus-cab { background: linear-gradient(150deg, #6d7f9c, #27344a 62%);
  box-shadow: inset 0 0 0 1px #0000002e, 0 0 0 1px #ffffff2b; }
.bus-tail { background: #2b3a52; opacity: .34; }
.flat .bus-cab { top: 15%; bottom: 15%; width: calc(var(--cell) * .26); }
.flat .bus-tail { top: 27%; bottom: 27%; width: calc(var(--cell) * .1); }
.tall .bus-cab { left: 15%; right: 15%; height: calc(var(--cell) * .26); }
.tall .bus-tail { left: 27%; right: 27%; height: calc(var(--cell) * .1); }
.to-right .bus-cab { right: 7%; } .to-right .bus-tail { left: 7%; }
.to-left  .bus-cab { left: 7%; }  .to-left  .bus-tail { right: 7%; }
.to-down  .bus-cab { bottom: 7%; } .to-down .bus-tail { top: 7%; }
.to-up    .bus-cab { top: 7%; }    .to-up   .bus-tail { bottom: 7%; }
/* The one direction this vehicle can go, painted fat across the roof. */
/* The edge is a drop-shadow on the box, since a clip-path would cut away a shadow on the shape itself. */
.bus-arrow { position: absolute; top: 50%; left: 50%; translate: -50% -50%;
  filter: drop-shadow(0 0 max(.6px, calc(var(--cell) * .012)) var(--bus-edge)) drop-shadow(0 calc(var(--cell) * .02) calc(var(--cell) * .03) #0004); }
.bus-arrow::before { content: ""; position: absolute; inset: 0; background: #fff; }
.flat .bus-arrow { height: 52%; width: min(52%, calc(var(--cell) * .54)); }
.tall .bus-arrow { width: 52%; height: min(52%, calc(var(--cell) * .54)); }
.to-right .bus-arrow::before { clip-path: polygon(0 30%, 52% 30%, 52% 0, 100% 50%, 52% 100%, 52% 70%, 0 70%); }
.to-left .bus-arrow::before { clip-path: polygon(100% 30%, 48% 30%, 48% 0, 0 50%, 48% 100%, 48% 70%, 100% 70%); }
.to-down .bus-arrow::before { clip-path: polygon(30% 0, 30% 52%, 0 52%, 50% 100%, 100% 52%, 70% 52%, 70% 0); }
.to-up .bus-arrow::before { clip-path: polygon(30% 100%, 30% 48%, 0 48%, 50% 0, 100% 48%, 70% 48%, 70% 100%); }
.bus-shine { position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: linear-gradient(158deg, #ffffff47 0 14%, #fff0 34% 72%, #00000017 100%); }
.bus-car.stuck .bus-body { filter: saturate(.86) brightness(.95); }
.bus-car.covered .bus-body { --tint: #9aa6bb; --light: #cdd5e2; --deep: #596579;
  background: linear-gradient(176deg, #ffffff40 -45%, #fff0 42%),
    repeating-linear-gradient(135deg, #ff4fa8 0 12%, #ffc400 12% 24%, #2fc44f 24% 36%, #2f7ff0 36% 48%, #9340f0 48% 60%); }
/* A covered bus wears its ? big and centered, so it never reads as just another color. */
.bus-car.covered .bus-mark { inset: 0; display: grid; place-items: center; font-weight: 800; color: #fff;
  font-size: calc(46cqw / var(--cols)); text-shadow: 0 1px 0 #0009, 0 0 4px #000a; }
/* Wrecks: scenery that fills cells, never moves and is never worth a tap. */
.bus-car.wreck .bus-body { --tint: #3f444d; --deep: #1d2026;
  background: linear-gradient(176deg, #666d79 -45%, #3f444d 42%, #3f444d 100%); }
.bus-hazard { position: absolute; inset: 14%; border-radius: calc(var(--cell) * .1); opacity: .85;
  background: repeating-linear-gradient(45deg, #f0b429 0 calc(var(--cell) * .07), #2a2d33 calc(var(--cell) * .07) calc(var(--cell) * .14)); }
.bus-clamp { position: absolute; width: calc(var(--cell) * .26); height: calc(var(--cell) * .26); border-radius: 50%;
  background: #d9480f; box-shadow: inset 0 0 0 calc(var(--cell) * .06) #fffc; }
.bus-dots { position: absolute; z-index: 2; display: grid; gap: calc(var(--cell) * .06); --dot: max(3px, calc(var(--cell) * .11)); pointer-events: none; }
.bus-dots i { width: var(--dot); height: var(--dot); border-radius: 50%; background: #fff; opacity: .9; box-shadow: 0 0 0 1px var(--bus-edge); }
/* Seats sit in pairs between the rear window and the arrow, one row per pair. */
.flat .bus-dots { top: 50%; translate: 0 -50%; grid-template-rows: repeat(2, var(--dot)); grid-auto-flow: column; }
.tall .bus-dots { left: 50%; translate: -50% 0; grid-template-columns: repeat(2, var(--dot)); }
.to-right .bus-dots { left: calc(var(--cell) * .3); }
.to-left .bus-dots { right: calc(var(--cell) * .3); }
.to-down .bus-dots { top: calc(var(--cell) * .3); }
.to-up .bus-dots { bottom: calc(var(--cell) * .3); }
.bus-mark { position: absolute; z-index: 2; right: 7%; bottom: 5%; color: #fff; font-size: calc(24cqw / var(--cols)); line-height: 1;
  text-shadow: 0 0 1px var(--bus-edge), 0 1px 2px #0005; }
.bus-bay .bus-mark { position: static; font-size: var(--text-sm); }
.bus-car.hinted { z-index: 60; }
.bus-car.hinted .bus-body { outline: 3px solid var(--bus-ink); outline-offset: -3px; animation: bus-hint 900ms ease-in-out infinite; }
.bus-car.bump { z-index: 61; }
/* A blocked tap only highlights: the bus and its blocker light up in place, with no movement. */
.bus-car.bump .bus-body { outline: 3px solid #ffffffd0; outline-offset: -3px; animation: bus-glow 600ms var(--ease); }
.bus-car.shoved .bus-body { outline: 3px solid #ff8a7a; outline-offset: -3px; animation: bus-glow 600ms var(--ease); }
@keyframes bus-glow { 0%, 70% { filter: brightness(1.35); } }
@keyframes bus-hint { 50% { outline-color: transparent; } }

/* A departed vehicle is already out of the model; this copy just drives off so taps never wait. */
.bus-ghost { z-index: 70; pointer-events: none; will-change: transform, opacity;
  animation: fade-out var(--drive) cubic-bezier(.34, .02, .2, 1) forwards; }
.bus-ghost.to-right { animation-name: bus-away-right; }
.bus-ghost.to-left { animation-name: bus-away-left; }
.bus-ghost.to-down { animation-name: bus-away-down; }
.bus-ghost.to-up { animation-name: bus-away-up; }
@keyframes bus-away-right { 15% { transform: translateX(-6%); } to { transform: translateX(calc(var(--away) * 100%)); opacity: .1; } }
@keyframes bus-away-left { 15% { transform: translateX(6%); } to { transform: translateX(calc(var(--away) * -100%)); opacity: .1; } }
@keyframes bus-away-down { 15% { transform: translateY(-6%); } to { transform: translateY(calc(var(--away) * 100%)); opacity: .1; } }
@keyframes bus-away-up { 15% { transform: translateY(6%); } to { transform: translateY(calc(var(--away) * -100%)); opacity: .1; } }

@media (max-width: 640px) {
  .bus-game { gap: var(--s2); padding: var(--s2); }
  .bus-queue li, .bus-bench li { width: 1rem; }
  .bus-bays { gap: var(--s1); padding: var(--s2) var(--s1); }
  .bus-bays li { height: 2.5rem; max-width: 4.2rem; }
  .bus-seats i { width: .42rem; height: .42rem; }
  /* The bottom tab bar eats the last rows, so the phone budget leaves room for it. */
  .bus-lot { --bus-chrome: 32rem; }
}
@media (prefers-reduced-motion: reduce) {
  .bus-ghost { display: none; }
}

@media (max-width: 640px) {
  /* One row of tools, so the lot keeps the height a second row would take. */
  .bus-controls { flex-wrap: nowrap; }
  .bus-controls button { flex: 1 1 0; min-width: 0; justify-content: center; padding-inline: var(--s2); }
  .bus-mini { width: 4.6rem; height: 4.6rem; }
}
/* Short phones, like an iPhone with Safari's bars showing: the park keeps just the stop and its queue. */
@media (max-width: 640px) and (max-height: 760px) {
  .bus-game { gap: var(--s1); }
  .bus-park { min-height: 4.25rem; padding-top: 0; }
  .bus-bays { padding-block: var(--s1); }
  .bus-bays li { height: 2.3rem; }
  .bus-sign { top: var(--s1); }
}
/* The shortest phones (an SE with Safari's bars): the park shrinks to its queue... */
@media (max-width: 640px) and (max-height: 600px) {
  .bus-park { min-height: 3.5rem; }
  .bus-bays li { height: 2.1rem; }
}
/* ...and if a big lot still runs under the tab bar, the tools ride just above it (the icon-only bar is 3.5rem) until
   scrolled into place. Any higher and they cover the bottom of a lot already at its smallest. */
@media (max-width: 767px) and (not ((orientation: landscape) and (max-height: 520px))) {
  .bus-controls { position: sticky; z-index: 30; bottom: calc(max(var(--s2), env(safe-area-inset-bottom)) + 3.5rem + var(--s2)); }
}
/* Full screen hides the tab bar, so the tools have nothing to ride above. */
:root[data-focus] .bus-controls { bottom: max(var(--s2), env(safe-area-inset-bottom)); }

/* Rush: every bus that pulls away flashes the seconds it bought next to the clock. */
.rush-gain { align-self: center; font-weight: 700; font-size: var(--text-sm); color: var(--accent); animation: rush-gain 900ms ease-out forwards; pointer-events: none; }
@keyframes rush-gain { 0% { opacity: 0; transform: translateY(4px); } 20% { opacity: 1; } 100% { opacity: 0; transform: translateY(-6px); } }
@media (prefers-reduced-motion: reduce) { .rush-gain { animation-name: rush-gain-fade; } @keyframes rush-gain-fade { 0%, 60% { opacity: 1; } 100% { opacity: 0; } } }