/* shadcn/ui tokens (orange theme on stone, with the accents muted a little): canvas fills the top, one card-style bar holds everything at the bottom. */
:root {
  --background: 0 0% 100%;
  --foreground: 20 14.3% 4.1%;
  --card: 0 0% 100%;
  --muted: 60 4.8% 95.9%;
  --muted-foreground: 25 5.3% 44.7%;
  --secondary: 60 4.8% 95.9%;
  --secondary-foreground: 24 9.8% 10%;
  --accent: 60 4.8% 95.9%;
  --border: 20 5.9% 90%;
  --input: 20 5.9% 90%;
  --primary: 22 74% 50%;
  --primary-foreground: 60 9.1% 97.8%;
  --destructive: 0 62% 50%;
  --ring: 22 74% 50%;
  --success: 142 52% 38%;
  --radius: 0.75rem;
  /* the HUD over the scene (stat chips, corner buttons, settings menu): see-through so the ride stays in view */
  --glass: 0.55;
  --glass-hover: 0.75;
  --font: "Rubik", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  /* the one ease for UI motion: a strong ease-out */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --mono: ui-monospace, "SFMono-Regular", Menlo, monospace;
  --tap: 44px;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --background: 20 14.3% 4.1%; --foreground: 60 9.1% 97.8%; --card: 20 14.3% 6%;
    --muted: 12 6.5% 15.1%; --muted-foreground: 24 5.4% 63.9%; --secondary: 12 6.5% 15.1%; --secondary-foreground: 60 9.1% 97.8%;
    --accent: 12 6.5% 15.1%; --border: 12 6.5% 15.1%; --input: 12 6.5% 18%;
    --primary: 20 64% 46%; --primary-foreground: 60 9.1% 97.8%; --destructive: 0 58% 60%; --ring: 20 64% 46%; --success: 142 44% 48%;
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --background: 20 14.3% 4.1%; --foreground: 60 9.1% 97.8%; --card: 20 14.3% 6%;
  --muted: 12 6.5% 15.1%; --muted-foreground: 24 5.4% 63.9%; --secondary: 12 6.5% 15.1%; --secondary-foreground: 60 9.1% 97.8%;
  --accent: 12 6.5% 15.1%; --border: 12 6.5% 15.1%; --input: 12 6.5% 18%;
  --primary: 20 64% 46%; --primary-foreground: 60 9.1% 97.8%; --destructive: 0 58% 60%; --ring: 20 64% 46%; --success: 142 44% 48%;
  color-scheme: dark;
}
*, *::before, *::after { box-sizing: border-box; border-color: hsl(var(--border)); }
html, body { height: 100%; }
body { background: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font); font-size: 14px; overflow: hidden; -webkit-font-smoothing: antialiased; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* Hugeicons (free, stroke rounded): outline icons drawn in the text color */
svg { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.75; }

.app { position: fixed; inset: 0; display: flex; flex-direction: column; }
/* without JavaScript the game can't run, so the page says what it is instead */
.nojs { position: fixed; inset: 0; z-index: 10; overflow: auto; padding: 48px 16px; background: hsl(var(--background)); font-size: 16px; line-height: 1.55; }
.nojs p { max-width: 576px; margin: 0 auto 1em; }
.nojs .nojs-name { font-size: 28px; font-weight: 700; }
#stage { position: relative; flex: 1 1 auto; min-height: 0; }
#scene { display: block; width: 100%; height: 100%; touch-action: none; }

/* bar = shadcn Card */
.bar {
  flex: none; background: hsl(var(--card)); border-top: 1px solid hsl(var(--border));
  box-shadow: 0 -6px 24px -12px rgb(0 0 0 / 0.18);
  padding-top: 12px; padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);
  padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(16px, env(safe-area-inset-right, 0px));
}
.inner { max-width: 760px; margin: 0 auto; display: grid; grid-template-columns: minmax(0, 1fr); }
.inner > * { min-width: 0; }
.inner > * + * { margin-top: 8px; } /* margins, not a grid gap, so the trace can collapse to nothing and take its gap with it */
.head { display: flex; align-items: center; gap: 12px; min-width: 0; }
.title { flex: 1 1 auto; min-width: 0; display: grid; gap: 1px; }
.name { margin: 0; font-size: 22px; line-height: 1.15; font-weight: 700; letter-spacing: -0.015em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* inline rename (shadcn Input in place of the title): the rider's own ride name lifts on hover and turns into a field
   on click. Both overhang the text by 6px a side so the words never move, and the field is the title's exact height */
.name.edit { justify-self: start; max-width: calc(100% + 12px); margin: 0 -6px; padding: 0 6px; border-radius: calc(var(--radius) - 4px); cursor: text; transition: background-color 0.15s; }
.name.edit:hover { background: hsl(var(--muted)); }
.name.edit:focus-visible { outline: none; box-shadow: 0 0 0 2px hsl(var(--background)), 0 0 0 4px hsl(var(--ring)); }
.name-edit { display: block; width: 100%; height: 1.15em; margin: 0 -6px; padding: 0 6px; box-sizing: content-box; border: 0; outline: none; font-family: inherit; color: hsl(var(--foreground)); background: hsl(var(--background)); border-radius: calc(var(--radius) - 4px); box-shadow: inset 0 0 0 1px hsl(var(--ring)), 0 0 0 3px hsl(var(--ring) / 0.18); text-overflow: clip; }
.name-edit[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .name.edit { transition: none; } }
.sub { font-size: 12.5px; color: hsl(var(--muted-foreground)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tools { display: flex; gap: 6px; flex: none; }

/* stats: one scrolling text strip */
.strip { display: flex; align-items: center; gap: 4px; overflow-x: auto; white-space: nowrap; scrollbar-width: none; font-size: 13px; font-variant-numeric: tabular-nums; color: hsl(var(--muted-foreground)); -webkit-mask-image: linear-gradient(90deg, #000 92%, transparent); mask-image: linear-gradient(90deg, #000 92%, transparent); padding-right: 16px; }
.strip::-webkit-scrollbar { display: none; }
.strip .st { flex: none; }
.strip .st + .st::before { content: "·"; margin-right: 4px; opacity: 0.6; }
.strip b { font-weight: 400; margin-right: 3px; }

/* shadcn Button */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px; white-space: nowrap; flex: none;
  height: 40px; padding: 0 16px; border-radius: calc(var(--radius) - 2px); font: 500 14px var(--font);
  border: 1px solid transparent; cursor: pointer; transition: background-color 0.15s, color 0.15s, opacity 0.15s, transform 0.16s var(--ease);
  background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none; user-select: none; -webkit-user-select: none;
}
.btn:focus-visible, .menu button:focus-visible, .menu a:focus-visible { outline: none; box-shadow: 0 0 0 2px hsl(var(--background)), 0 0 0 4px hsl(var(--ring)); }
.btn:disabled { opacity: 0.5; pointer-events: none; }
.btn:hover { background: hsl(var(--primary) / 0.9); }
/* press feedback, so a tap answers on touch screens too */
.btn:active { transform: scale(0.97); }
@media (prefers-reduced-motion: reduce) { .btn:active { transform: none; } }
.btn.outline { background: hsl(var(--background)); color: hsl(var(--foreground)); border-color: hsl(var(--input)); box-shadow: 0 1px 2px rgb(0 0 0 / 0.05); }
.btn.outline:hover, .btn.ghost:hover { background: hsl(var(--accent)); }
.btn.ghost { background: transparent; color: hsl(var(--muted-foreground)); }
.btn.icon { width: 40px; padding: 0; }
.btn.sm.icon { width: 34px; height: 34px; border-radius: calc(var(--radius) - 4px); }
#soundBtn .off { display: none; }
#soundBtn[aria-pressed="false"] .on { display: none; }
#soundBtn[aria-pressed="false"] .off { display: block; }
#soundBtn[aria-pressed="false"] { color: hsl(var(--muted-foreground)); }

/* shadcn AI PromptInput: one row, the textarea on the left and its actions on the right. As the text wraps the
   field grows upward and the actions stay on the bottom line */
.prompt { position: relative; display: flex; align-items: flex-end; gap: 4px; border: 1px solid hsl(var(--input)); background: hsl(var(--background)); border-radius: calc(var(--radius) + 2px); padding: 6px; box-shadow: 0 1px 2px rgb(0 0 0 / 0.05); transition: box-shadow 0.15s, border-color 0.15s, opacity 0.24s; }
.prompt:focus-within { border-color: hsl(var(--ring)); box-shadow: 0 0 0 3px hsl(var(--ring) / 0.18); }
.prompt textarea { flex: 1 1 auto; min-width: 0; resize: none; border: 0; outline: none; background: transparent; color: hsl(var(--foreground)); font: 16px/22px var(--font); padding: 6px 4px 6px 8px; height: 34px; min-height: 34px; max-height: 78px; overflow-y: hidden; width: 100%; scrollbar-width: thin; }
.prompt[hidden], .own[hidden] { display: none; }
/* a real coaster from the gallery is read-only: this stands in for the prompt and brings it back */
.own { width: 100%; height: var(--tap); }
.prompt textarea::placeholder { color: hsl(var(--muted-foreground)); }
.prompt .acts { display: flex; gap: 4px; flex: none; }
.send .stop { display: none; }
.send.busy { background: hsl(var(--foreground)); color: hsl(var(--background)); }
.send.busy .go { display: none; }
.send.busy .stop { display: block; }
/* the live mic's ring pulses on its own layer, as opacity, so it doesn't repaint the button every frame */
.mic { position: relative; }
.mic.live { background: hsl(var(--destructive)); color: #fff; }
.mic.live::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: 0 0 0 5px hsl(var(--destructive) / 0.25); opacity: 0; pointer-events: none; animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 50% { opacity: 1; } }
.menu { position: absolute; right: 6px; bottom: calc(100% + 8px); z-index: 5; min-width: 200px; padding: 4px; background: hsl(var(--card)); border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 2px); box-shadow: 0 10px 30px -10px rgb(0 0 0 / 0.35); display: grid; }
.menu[hidden] { display: none; }
/* menus grow out of their trigger's corner as they open, and close at once */
.menu { transform-origin: bottom right; transition: opacity 0.15s var(--ease), transform 0.15s var(--ease); }
@starting-style { .menu { opacity: 0; transform: translateY(4px) scale(0.97); } }
@media (prefers-reduced-motion: reduce) { .menu { transition-property: opacity; } }
#setMenu { background: hsl(var(--card) / var(--glass-hover)); -webkit-backdrop-filter: blur(16px); backdrop-filter: blur(16px); }
.menu button, .menu a { text-decoration: none; display: flex; align-items: center; gap: 10px; text-align: left; border: 0; background: transparent; color: hsl(var(--foreground)); font: 14px var(--font); padding: 9px 10px; border-radius: calc(var(--radius) - 4px); cursor: pointer; }
.menu button:hover, .menu a:hover { background: hsl(var(--accent)); }
.menu button:disabled { opacity: 0.45; pointer-events: none; }
.menu svg { width: 16px; height: 16px; color: hsl(var(--muted-foreground)); }

/* shadcn Tooltip */
.tip { position: fixed; z-index: 60; left: 0; top: 0; pointer-events: none; background: hsl(var(--foreground)); color: hsl(var(--background)); font: 500 12px/1.3 var(--font); padding: 6px 9px; border-radius: calc(var(--radius) - 4px); white-space: nowrap; box-shadow: 0 4px 14px rgb(0 0 0 / 0.18); opacity: 0; transform: translateY(2px); transition: opacity 0.15s var(--ease), transform 0.15s var(--ease); }
.tip.on { opacity: 1; transform: none; }
/* the next tooltip, moments after the last one closed, shows at once */
.tip.warm { transition: none; }
.tip[hidden] { display: none; }
.tip .sub { display: block; font-weight: 400; opacity: 0.68; margin-top: 1px; }
.tip kbd { font: 500 11px var(--font); padding: 0 4px; border-radius: 3px; background: hsl(var(--background) / 0.16); }
@media (prefers-reduced-motion: reduce) { .tip { transition: none; } }

/* shadcn AI ChainOfThought: one line that expands into the step history.
   Everything that enters, leaves or shifts animates. The block grows in from nothing, the current step swaps in place,
   steps slide in under the rest, and the drawer opens on a 0fr to 1fr grid row (the one trick that animates to auto height). */
.trace { --ease: cubic-bezier(0.22, 1, 0.36, 1); display: grid; grid-template-rows: 0fr; margin-top: 0; opacity: 0; visibility: hidden; text-align: left; transition: grid-template-rows 0.36s var(--ease), margin-top 0.36s var(--ease), opacity 0.24s var(--ease), visibility 0s linear 0.36s; }
.trace.on { grid-template-rows: 1fr; margin-top: 6px; opacity: 1; visibility: visible; transition-delay: 0s; }
.trace .clip { min-height: 0; overflow: hidden; }
.trace > .clip { padding: 0 2px 0 12px; }
/* first paint of a state, and tests: no motion at all for one frame */
.trace.snap, .trace.snap * { transition: none !important; animation-duration: 0s !important; }

.trace-row { display: flex; align-items: center; gap: 8px; min-width: 0; width: calc(100% + 8px); margin: 0 -4px; padding: 1px 4px; border: 0; border-radius: 6px; background: transparent; color: hsl(var(--muted-foreground) / 0.85); font: 12.5px var(--font); cursor: pointer; text-align: left; }
.trace-row:focus-visible { outline: none; box-shadow: inset 0 0 0 2px hsl(var(--ring)); }
.trace-row .tm { flex: none; font-variant-numeric: tabular-nums; font-size: 12px; }
.trace-row .chev { width: 16px; height: 16px; opacity: 0; transition: transform 0.3s var(--ease), opacity 0.2s var(--ease); }
.trace-row.has-history .chev { opacity: 1; }
.trace-row[aria-expanded="true"] .chev { transform: rotate(180deg); }
.trace.err .ti { color: hsl(var(--destructive) / 0.8); }
.trace .ti { color: hsl(var(--muted-foreground) / 0.8); }

/* the current line: each change slides the new text up from below while the old one lifts away */
.tt { flex: 1 1 auto; min-width: 0; display: grid; overflow: hidden; }
.tw { grid-area: 1 / 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; animation: tw-in 0.34s var(--ease) both; }
.tw.still { animation: none; }
.tw.out { animation: tw-out 0.24s var(--ease) both; pointer-events: none; }
@keyframes tw-in { from { opacity: 0; transform: translateY(70%); } }
@keyframes tw-out { to { opacity: 0; transform: translateY(-70%); } }
.active .tw:not(.out) { background: linear-gradient(90deg, hsl(var(--muted-foreground)) 0%, hsl(var(--foreground)) 50%, hsl(var(--muted-foreground)) 100%); background-size: 200% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; animation: tw-in 0.34s var(--ease) both, shimmer 1.6s linear infinite; }
.active .tw.still:not(.out) { animation: shimmer 1.6s linear infinite; }
@keyframes shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }

/* the loop: Hugeicons grid (24, 2 padding, rounded, 1.75 stroke). Every glyph sits in one stack and the state picks which shows. */
.ti { --ease: cubic-bezier(0.22, 1, 0.36, 1); position: relative; width: 16px; height: 16px; flex: none; display: inline-grid; place-items: center; }
/* the glyphs swap by scaling about the shared baseline (y 22 of 24), so the tails stay put and the loop swells into the ring */
.ti > svg { grid-area: 1 / 1; width: 16px; height: 16px; opacity: 0; transform: scale(0.7); transform-origin: 50% 91.67%; transition: opacity 0.2s var(--ease), transform 0.3s var(--ease); }
.ti[data-k="active"] > .lp, .ti[data-k="done"] > .lp, .ti[data-k="error"] > .ic-error, .ti[data-k="info"] > .ic-info { opacity: 1; transform: none; }
.lp path, .ring path { stroke-linecap: round; stroke-linejoin: round; }
.lp-track { stroke: hsl(var(--muted-foreground) / 0.28); transition: opacity 0.3s var(--ease); }
/* the emphasis rides the whole track: up from the bottom left, round the loop, out the bottom right. Two dashes share one clock, one per piece of track, so the gap at the crossing is skipped.
   It eases in and out like a car over the top: slow up the lift, fast round the loop, slow into the station */
.lp-sweep { stroke: hsl(var(--primary)); opacity: 0; transition: opacity 0.2s var(--ease); }
.ti[data-k="active"] .lp-sweep { opacity: 1; }
.lp-sweep path { stroke-dasharray: 10 200; animation: lp-a 1.6s cubic-bezier(0.65, 0, 0.35, 1) infinite; animation-play-state: paused; }
.lp-sweep .sb { animation-name: lp-b; }
.ti[data-k="active"] .lp-sweep path { animation-play-state: running; }
/* A = 51.68 long, B = 8.06, the gap between them 6.2 along the track. The dash starts 12 back from A's start and ends 2 past B's end:
   A runs 22 to -(51.68 + 6.2 + 8.06 + 2), B runs (22 + 51.68 + 6.2) to -(8.06 + 2), so both move 89.94 on the same clock */
@keyframes lp-a { 0% { stroke-dashoffset: 22; } 88%, 100% { stroke-dashoffset: -67.94; } }
@keyframes lp-b { 0% { stroke-dashoffset: 79.88; } 88%, 100% { stroke-dashoffset: -10.06; } }
/* done: the track stays and the two legs at the crossing draw over it in green, so the check is part of the track (with the crossing's gap left in it).
   Each leg has pathLength 1, drawn like a pen: the short leg down into the crossing, then the long one up and out */
.lp-chk { stroke: hsl(var(--success)); }
.lp-chk path { stroke-dasharray: 1 2; stroke-dashoffset: 1; transition: stroke-dashoffset 0.14s cubic-bezier(0.5, 0, 1, 1); }
.lp-chk path + path { transition: stroke-dashoffset 0.34s var(--ease) 0.16s; }
.ti[data-k="done"] .lp-chk path { stroke-dashoffset: 0; }
.ti[data-k]:not([data-k="done"]) .lp-chk path { transition-duration: 0.12s; transition-delay: 0s; }

/* the step history */
.trace-drawer { display: grid; grid-template-rows: 0fr; opacity: 0; margin-top: 0; transition: grid-template-rows 0.36s var(--ease), opacity 0.24s var(--ease), margin-top 0.36s var(--ease); }
.trace-drawer.open { grid-template-rows: 1fr; opacity: 1; margin-top: 2px; }
.trace-steps { list-style: none; margin: 0; padding: 0; display: grid; max-height: 150px; overflow-y: auto; scrollbar-width: thin; }
.trace-steps li { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows 0.34s var(--ease), opacity 0.26s var(--ease); }
.trace-steps li.in { grid-template-rows: 1fr; opacity: 1; }
.trace-steps li > .clip { position: relative; }
.trace-steps li > .clip::before { content: ""; position: absolute; left: 7.5px; top: 0; bottom: 0; width: 1px; background: hsl(var(--border)); }
.trace-steps li:first-child > .clip::before { top: 50%; }
.trace-steps li:last-child > .clip::before { bottom: 50%; }
.step { position: relative; display: flex; align-items: center; gap: 8px; min-width: 0; padding: 3px 0; font-size: 12px; color: hsl(var(--muted-foreground) / 0.85); }
.step .ti { background: hsl(var(--card)); border-radius: 50%; }
.step[data-s="error"] .ti { color: hsl(var(--destructive) / 0.8); }
.step .lt { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.step time { flex: none; font-size: 11.5px; font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  .trace, .trace-drawer, .trace-steps li, .ti > svg, .lp-track, .lp-sweep, .chev { transition-duration: 0.01s; transition-delay: 0s; }
  .tw, .tw.out { animation: none; }
  .tw.out { display: none; }
  .active .tw:not(.out) { animation: none; color: hsl(var(--foreground)); background: none; }
  .lp-sweep path { animation: none; stroke-dasharray: none; }
  .lp-chk path, .lp-chk path + path { transition-duration: 0.01s; transition-delay: 0s; }
  .ti[data-k="active"] .lp-sweep { opacity: 0.6; }
}

.hud { position: absolute; left: 10px; bottom: 10px; z-index: 2; pointer-events: none; display: flex; gap: 5px; }
.hud[hidden] { display: none !important; }
.hud.orbit .chip { display: none; }
#pauseBtn .play { display: none; }
#pauseBtn[aria-pressed="true"] .play { display: block; }
#pauseBtn[aria-pressed="true"] .pause { display: none; }
#pauseBtn { position: relative; }
#pauseBtn[aria-pressed="true"]::after { content: ""; position: absolute; inset: 4px; border: 1px solid hsl(var(--foreground) / 0.22); border-radius: 50%; opacity: 0.15; pointer-events: none; animation: pulse 2.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { #pauseBtn[aria-pressed="true"]::after { animation: none; opacity: 0.7; } }
.hud .chip { display: inline-flex; align-items: center; gap: 4px; background: hsl(var(--card) / var(--glass)); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); border: 1px solid hsl(var(--border) / 0.8); border-radius: calc(var(--radius) - 3px); font-size: 13px; height: 32px; box-sizing: border-box; padding: 0 7px; box-shadow: 0 2px 8px rgb(0 0 0 / 0.12); color: hsl(var(--foreground) / 0.72); font-variant-numeric: tabular-nums; }
/* value and unit share one size; the value stands out by weight and color */
.hud b { font-weight: 500; color: hsl(var(--foreground)); min-width: 2ch; text-align: right; }
.hud .dir { color: hsl(var(--muted-foreground)); }
/* chips take the mouse so they can show a tooltip; the HUD around them still lets clicks through to the scene */
.hud .chip { pointer-events: auto; cursor: default; transition: background-color 0.12s, color 0.12s; }
@media (hover: hover) { .hud .chip:hover { background: hsl(var(--card)); color: hsl(var(--foreground)); } }
@media (prefers-reduced-motion: reduce) { .hud .chip { transition-duration: 0.01s; } }
/* the blueprint: a sheet over whatever view is showing, inset evenly so the park shows round it, under the buttons. app.js
   unrolls it (Web Animations, skipped for reduced motion); clicking the scrim round it closes it. Over a view that moves
   (front seat, chase...) the scrim is deeper, so the motion round the edges doesn't pull the eye off the drawing */
.bp { position: absolute; inset: 0; z-index: 1; --bp-pad: clamp(14px, 5vmin, 48px); }
.bp[hidden] { display: none; }
.bp.closing { pointer-events: none; }
.bp-scrim { position: absolute; inset: 0; background: rgb(0 0 0 / 0.18); }
.bp.moving .bp-scrim { background: rgb(0 0 0 / 0.34); }
.bp-sheet { position: absolute; inset: var(--bp-pad); overflow: hidden; border-radius: calc(var(--radius) - 4px); box-shadow: 0 18px 48px -16px rgb(0 0 0 / 0.5), 0 2px 8px rgb(0 0 0 / 0.12); }
.bp-sheet svg { display: block; width: 100%; height: 100%; }
/* the sheet reads out the curve under the pointer, so a finger dragging across it moves the readout, not the page */
.bp-sheet { touch-action: none; }
/* the sheet's title block, names and readout are written in sentence case and set in capitals here */
.bp-sheet .bp-caps { text-transform: uppercase; }
@media (hover: hover) and (pointer: fine) { .bp-sheet svg { cursor: crosshair; } }
.ctrl { position: absolute; right: 10px; bottom: 10px; z-index: 3; display: flex; gap: 5px; }
.btn.glass { width: 32px; height: 32px; padding: 0; border-radius: calc(var(--radius) - 3px); background: hsl(var(--card) / var(--glass)); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); border: 1px solid hsl(var(--border) / 0.8); color: hsl(var(--foreground)); box-shadow: 0 2px 8px rgb(0 0 0 / 0.12); }
.btn.glass:hover { background: hsl(var(--card) / var(--glass-hover)); }
.btn.glass svg { width: 16px; height: 16px; }
#setMenu { right: 0; }
.menu .tg .sw { margin-left: auto; }
.sw { flex: none; position: relative; width: 30px; height: 18px; border-radius: 9px; background: hsl(var(--muted-foreground) / 0.35); transition: background-color 0.15s var(--ease); }
.sw::after { content: ""; position: absolute; left: 2px; top: 2px; width: 14px; height: 14px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / 0.2); transition: transform 0.15s var(--ease); }
.tg[aria-checked="true"] .sw { background: hsl(var(--primary)); }
.tg[aria-checked="true"] .sw::after { transform: translateX(12px); }
@media (prefers-reduced-motion: reduce) { .sw, .sw::after { transition: none; } }
/* shadcn Tabs as a segmented control: the theme row in the settings menu */
.seg-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 5px 5px 5px 10px; font-size: 14px; }
.seg-row .lbl { display: flex; align-items: center; gap: 10px; }
.seg { display: inline-flex; padding: 2px; gap: 2px; border-radius: calc(var(--radius) - 4px); background: hsl(var(--muted)); }
.menu .seg button { padding: 4px 8px; font-size: 12.5px; font-weight: 500; border-radius: calc(var(--radius) - 6px); color: hsl(var(--muted-foreground)); transition: background-color 0.15s, color 0.15s; }
.menu .seg button:hover { background: transparent; color: hsl(var(--foreground)); }
.menu .seg button[aria-checked="true"] { background: hsl(var(--background)); color: hsl(var(--foreground)); box-shadow: 0 1px 2px rgb(0 0 0 / 0.12); }
@media (prefers-reduced-motion: reduce) { .menu .seg button { transition: none; } }
/* phones: sound moves into settings; the stat chips wrap clear of the four right-side buttons */
.menu .narrow { display: none; }
@media (max-width: 600px) {
  #soundBtn { display: none; }
  .menu .narrow { display: flex; }
  .hud { right: 161px; flex-wrap: wrap; }
}
@media (prefers-reduced-motion: reduce) { .mic.live::after { animation: none; } }
@media (max-width: 600px) { .name { font-size: 19px; } }

/* first load, until the scene draws its first frame (app.js drops .booting): the loop rides over a plain stage, the bar shows
   shadcn Skeleton placeholders where the name and stats go, and the prompt waits (inert, at disabled opacity) */
.boot { position: absolute; inset: 0; z-index: 4; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 16px; text-align: center; background: hsl(var(--background)); color: hsl(var(--muted-foreground)); font-size: 13px; opacity: 0; visibility: hidden; transition: opacity 0.36s cubic-bezier(0.22, 1, 0.36, 1), visibility 0s linear 0.36s; }
.booting .boot { opacity: 1; visibility: visible; transition: none; }
.boot .ti, .boot .ti > svg { width: 32px; height: 32px; }
/* the sweep runs as a filmstrip (the script after #boot draws it), a steps() transform the compositor plays while scripts
   hold the main thread. It replaces the glyph's own sweep, which comes back when motion is reduced. Once the stage
   starts to fade it holds still, so nothing keeps animating under the hidden stage */
.lp-reel { grid-area: 1 / 1; width: 32px; height: 32px; overflow: hidden; }
.lp-film { display: block; width: calc(var(--frames) * 32px); height: 32px; animation: lp-film 1.6s steps(var(--frames)) infinite; }
.lp-film > svg { width: 100%; height: 100%; stroke: hsl(var(--primary)); stroke-dasharray: 10 200; stroke-linecap: round; stroke-linejoin: round; }
.app:not(.booting) .lp-film { animation-play-state: paused; }
@keyframes lp-film { to { transform: translateX(-100%); } }
@media (prefers-reduced-motion: no-preference) { .boot .ti.reel .lp-sweep { display: none; } }
@media (prefers-reduced-motion: reduce) { .lp-reel { display: none; } }
.booting .name { width: fit-content; max-width: 60%; color: transparent; border-radius: calc(var(--radius) - 6px); background: hsl(var(--muted)); animation: skeleton 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
.booting .strip::before { content: ""; width: 240px; max-width: 70%; height: 1lh; border-radius: calc(var(--radius) - 8px); background: hsl(var(--muted)); animation: skeleton 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
.booting .prompt { opacity: 0.5; }
@keyframes skeleton { 50% { opacity: 0.5; } }
@media (prefers-reduced-motion: reduce) { .boot { transition-duration: 0.01s; } .booting .name, .booting .strip::before { animation: none; } }

/* accounts: save and the rider's Gravatar to the right of the ride name, a byline under it */
.acct { position: relative; align-items: center; gap: 2px; }
.acct[hidden], .acct .btn[hidden], .g-head .btn[hidden] { display: none; }
.byline { color: hsl(var(--muted-foreground)); text-decoration: none; cursor: pointer; justify-self: start; max-width: 100%; }
.byline:hover { color: hsl(var(--foreground)); text-decoration: underline; }
.byline[hidden] { display: none; }
#saveBtn .on { display: none; }
#saveBtn[data-state="mine"] .off { display: none; }
#saveBtn[data-state="mine"] .on { display: block; color: hsl(var(--primary)); }
#saveBtn.busy { opacity: 0.55; pointer-events: none; }
.me .av { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; background: hsl(var(--muted)); box-shadow: 0 0 0 1px hsl(var(--border)); }
.me.in svg { display: none; }
#saveMenu { right: 0; left: auto; }
.menu button.danger, .menu button.danger svg { color: hsl(var(--destructive)); }

/* the gallery: a rider's coasters, two to a row, over the scene; the bar stays below it */
.gallery { position: absolute; inset: 0; z-index: 6; overflow-y: auto; overscroll-behavior: contain; background: hsl(var(--background) / 0.9); -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px); }
.gallery[hidden] { display: none; }
.gallery:focus { outline: none; }
/* sized to the panel's height so the first four, two by two, fit without scrolling (rows of 4:3 pictures plus captions) */
.gallery { container-type: size; }
.g-in { max-width: min(760px, max(320px, calc((100cqh - 220px) * 1.333 + 44px))); margin: 0 auto; padding: 14px max(16px, env(safe-area-inset-right, 0px)) 20px max(16px, env(safe-area-inset-left, 0px)); }
.g-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.g-av { width: 44px; height: 44px; border-radius: 50%; flex: none; background: hsl(var(--muted)); box-shadow: 0 0 0 1px hsl(var(--border)); object-fit: cover; }
.g-who { flex: 1 1 auto; min-width: 0; display: grid; gap: 1px; }
.g-who h2 { margin: 0; font-size: 19px; font-weight: 700; letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.g-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.g-tile { display: grid; gap: 6px; text-align: left; padding: 0; border: 0; background: transparent; color: inherit; font: inherit; cursor: pointer; min-width: 0; -webkit-tap-highlight-color: transparent; }
.g-tile .pic { position: relative; aspect-ratio: 4 / 3; border-radius: calc(var(--radius) - 2px); overflow: hidden; background: hsl(var(--muted)); border: 1px solid hsl(var(--border)); transition: transform 0.15s, box-shadow 0.15s; }
.g-tile img { display: block; width: 100%; height: 100%; object-fit: cover; }
.g-tile .pic svg { position: absolute; inset: 0; margin: auto; width: 32px; height: 32px; color: hsl(var(--muted-foreground)); opacity: 0.6; }
@media (hover: hover) and (pointer: fine) { .g-tile:hover .pic { box-shadow: 0 8px 24px -12px rgb(0 0 0 / 0.4); transform: translateY(-1px); } }
.g-tile:focus-visible { outline: none; }
.g-tile:focus-visible .pic { box-shadow: 0 0 0 2px hsl(var(--background)), 0 0 0 4px hsl(var(--ring)); }
.g-tile b { font-weight: 500; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 0 2px; }
.g-tile small { font-size: 12px; color: hsl(var(--muted-foreground)); padding: 0 2px; margin-top: -4px; }
/* empty gallery = shadcn Empty: the loop in a muted tile, a title, a line of help and one action */
.g-empty { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 56px 16px 32px; }
.g-empty[hidden] { display: none; }
.g-empty-ic { display: grid; place-items: center; width: 56px; height: 56px; margin-bottom: 16px; border-radius: var(--radius); background: hsl(var(--muted)); border: 1px solid hsl(var(--border)); }
.g-empty-ic svg { width: 28px; height: 28px; }
.g-empty-ic .trk { stroke: hsl(var(--muted-foreground) / 0.4); }
.g-empty-ic .swp { stroke: hsl(var(--primary)); stroke-dasharray: 16 200; stroke-dashoffset: -14; }
.g-empty h3 { margin: 0; font-size: 16px; font-weight: 500; }
.g-empty p { margin: 6px 0 0; max-width: 300px; color: hsl(var(--muted-foreground)); font-size: 14px; line-height: 1.5; }
.g-empty .btn { margin-top: 20px; }
/* the gallery is its own page: the bar under it belongs to the ride, so it steps aside until the gallery closes */
.app.in-gallery .bar { display: none; }
/* the real-coaster gallery (/gallery): the same sheet, the mark for an avatar, rides grouped under a heading per park */
.gallery.real { background: hsl(var(--background)); }
.gallery.real .g-av { border-radius: calc(var(--radius) - 2px); box-shadow: none; background: transparent; }
.g-park { grid-column: 1 / -1; margin: 16px 2px 2px; padding-top: 20px; border-top: 1px solid hsl(var(--border)); font-size: 20px; font-weight: 500; }
.g-park:first-child { margin-top: 4px; padding-top: 0; border-top: 0; }
.g-park small { display: block; margin-top: 5px; font-size: 11.5px; line-height: 1.6; font-weight: 400; color: hsl(var(--muted-foreground)); }
.g-real-tools { position: sticky; top: 0; z-index: 1; display: flex; gap: 10px; padding: 8px 0 14px; margin-bottom: 4px; background: hsl(var(--background)); }
.g-search { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; padding: 0 12px; border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 4px); color: hsl(var(--muted-foreground)); }
.g-search svg { flex: none; color: hsl(var(--muted-foreground) / 0.8); }
.g-search input { width: 100%; min-width: 0; height: 44px; border: 0; outline: none; background: transparent; color: hsl(var(--foreground)); font: 16px var(--font); text-overflow: ellipsis; }
.g-search input::placeholder { color: hsl(var(--muted-foreground) / 0.97); }
.g-search:focus-within { box-shadow: 0 0 0 2px hsl(var(--background)), 0 0 0 4px hsl(var(--ring)); }
.g-park-filter { position: relative; flex: none; min-width: 0; width: 160px; max-width: 42%; }
.g-park-filter select { width: 100%; height: 46px; padding: 0 36px 0 12px; border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 4px); background: hsl(var(--background)); color: hsl(var(--foreground)); font: 16px var(--font); text-overflow: ellipsis; -webkit-appearance: none; appearance: none; }
.g-park-filter > svg { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); pointer-events: none; color: hsl(var(--muted-foreground) / 0.8); }
.g-park-filter select.placeholder + svg { left: 62px; right: auto; }
.g-park-filter select.placeholder { color: hsl(var(--muted-foreground) / 0.97); }
.g-park-filter option { color: hsl(var(--foreground)); }
.g-park-filter option[value=""] { color: hsl(var(--muted-foreground) / 0.97); }
.g-park-filter select:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; }
.g-real-tile { min-width: 0; }
.g-real-tile .g-tile { width: 100%; }
.g-real-stats { margin: 4px 2px 0; font-size: 12px; }
.g-real-stats:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; border-radius: 2px; }
.g-real-empty { text-align: center; padding: 30px 0; color: hsl(var(--muted-foreground)); font-size: 13px; }
.g-real-empty .btn { display: block; margin: 8px auto 0; }
.g-real-tools[hidden], .g-real-empty[hidden] { display: none; }
@media (max-width: 600px) {
  .g-park-filter { width: 130px; }
}
.g-more { display: flex; margin: 16px auto 0; }
.g-more[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .g-tile .pic { transition: none; } }

/* shadcn Kbd: a key cap, in the shortcuts dialog and its settings row */
.keys kbd, .menu kbd { display: inline-grid; place-items: center; min-width: 20px; height: 20px; padding: 0 5px; font: 500 11.5px var(--font); color: hsl(var(--foreground)); background: hsl(var(--muted)); border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 7px); }
.menu kbd { flex: none; color: hsl(var(--muted-foreground)); }
@media (hover: none) and (pointer: coarse) { .menu .keys-item { display: none; } }

/* shadcn Dialog: keyboard shortcuts, from ? or the settings menu */
.keys { width: min(400px, calc(100vw - 32px)); max-height: calc(100dvh - 32px); overflow-y: auto; padding: 0 0 12px; color: hsl(var(--foreground)); background: hsl(var(--card)); border: 1px solid hsl(var(--border)); border-radius: var(--radius); box-shadow: 0 10px 30px -10px rgb(0 0 0 / 0.35); font: 14px var(--font); }
.keys::backdrop { background: rgb(0 0 0 / 0.4); }
.keys[open] { animation: keys-in 0.24s var(--ease); }
.keys[open]::backdrop { animation: keys-fade 0.24s var(--ease); }
@keyframes keys-in { from { opacity: 0; transform: translateY(8px) scale(0.98); } }
@keyframes keys-fade { from { opacity: 0; } }
.keys-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 12px 0 20px; }
.keys h2 { margin: 0; font-size: 16px; font-weight: 500; }
.keys section { padding: 0 20px; }
.keys h3 { margin: 10px 0 2px; font-size: 12px; font-weight: 500; color: hsl(var(--muted-foreground)); }
.keys dl { margin: 0; }
.keys dl div { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 7px 0; }
.keys dl div + div { border-top: 1px solid hsl(var(--border)); }
.keys dd { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 4px; margin: 0; font-size: 12px; color: hsl(var(--muted-foreground)); }
.keys dd span { padding: 0 2px; }
.keys dt { white-space: nowrap; }
.keys dd .ks { display: inline-flex; gap: 4px; padding: 0; }
@media (prefers-reduced-motion: reduce) { .keys[open], .keys[open]::backdrop { animation: keys-fade 0.2s ease; } }
