:root {
  color-scheme: light dark;
  --bg: #0b0d12;
  --fg: #f5f5f5;
  --muted: #9aa0a6;
  --accent: #ffd166;

  /* Native resolution of the scene artwork. */
  --scene-w: 1920;
  --scene-h: 1080;
  --scene-aspect: calc(var(--scene-w) / var(--scene-h));
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  background: var(--bg);
  color: var(--fg);
  overflow: hidden;
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}

#app {
  position: relative;
  width: 100%;
  height: 100vh;
}

/* Scene: a fixed-aspect stage that fits inside the viewport
   ("contain"-style) and stacks all layers on top of each other.
   All scenes are absolutely positioned at the same spot so we can
   cross-fade between them by toggling .is-active. */
.scene {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  aspect-ratio: var(--scene-w) / var(--scene-h);
  width: min(100vw, calc(100vh * var(--scene-aspect)));
  height: min(100vh, calc(100vw / var(--scene-aspect)));
  background: #000;
  overflow: hidden;

  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* Visibility is delayed until the opacity fade finishes so the
     leaving scene stays painted while it fades out. */
  transition: opacity 400ms ease, visibility 0s linear 400ms;
}

.scene.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 400ms ease, visibility 0s;
}

@media (prefers-reduced-motion: reduce) {
  .scene { transition: none; }
}

/* Generic ingredient picker (used by meat, bun, …). Vertical column of
   icons; positioned via the variant rules below.

   Stretched top-to-bottom of the scene so it has a *definite* height —
   without that, percentage heights on the children (.ingredient-option)
   would resolve to `auto` and the icons would stay at their intrinsic
   pixel size instead of scaling with the scene. Items are centered
   inside this full-height column via `justify-content: center`. */
.scene[data-scene="room02"] .ingredient-picker {
  --ingredient-col-x: 6%;          /* horizontal center of the column     */
  --ingredient-icon-h: 25.2%;       /* per-icon height (% of scene height) */
  --ingredient-row-gap: 1.5%;       /* vertical gap                        */

  position: absolute;
  top: 0;
  bottom: 0;                        /* definite height = scene height       */
  z-index: 5;                       /* above background (2) + label layer (3) */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--ingredient-row-gap);
  pointer-events: none;             /* dead space passes clicks through    */
}

.scene[data-scene="room02"] .ingredient-picker .ingredient-option {
  pointer-events: auto;             /* but the icons themselves are clickable */
}

/* Variant: anchored to the LEFT side of the scene (e.g. meat).
   Larger icons than the shared default — meat sprites are tall+narrow
   so they can comfortably grow without squeezing the layout. */
.scene[data-scene="room02"] .ingredient-picker--meat {
  --ingredient-icon-h: 30.24%;       /* 20% bigger than the shared default */
  left: var(--ingredient-col-x);
  transform: translateX(-50%);
}

/* Variant: anchored to the RIGHT side of the scene (e.g. bun).
   Bun sprites are roughly square, so their right half would spill past
   the scene at the shared 6% center. Push the column inward via a
   variant-specific col-x so the icons sit fully inside the scene. */
.scene[data-scene="room02"] .ingredient-picker--bun {
  --ingredient-col-x: 9%;
  right: var(--ingredient-col-x);
  transform: translateX(50%);
}

.scene[data-scene="room02"] .ingredient-option {
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  height: var(--ingredient-icon-h);
  display: block;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.35));
  transition: transform 80ms ease, filter 120ms ease, opacity 120ms ease;
}

.scene[data-scene="room02"] .ingredient-option:hover:not(:disabled) {
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.35)) brightness(1.07);
}

.scene[data-scene="room02"] .ingredient-option:active:not(:disabled) {
  transform: translateY(2px);
}

.scene[data-scene="room02"] .ingredient-option:focus-visible {
  outline: 2px solid #ffd166;
  outline-offset: 3px;
  border-radius: 0.4em;
}

/* Disabled when no order label is selected: visually muted + uninteractive. */
.scene[data-scene="room02"] .ingredient-option:disabled,
.scene[data-scene="room02"] .ingredient-picker.is-disabled .ingredient-option,
.scene[data-scene="room02"] .ingredient-grid.is-disabled .ingredient-option {
  cursor: not-allowed;
  opacity: 0.45;
  filter: grayscale(0.65) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.25));
}

/* At-cap visual: this category already has `max` items selected, so
   adding a new (different) one is a no-op. Already-selected options in
   the same category stay clickable (clicking removes them). */
.scene[data-scene="room02"] .ingredient-option.is-at-cap:not(.is-selected) {
  cursor: not-allowed;
  opacity: 0.55;
  filter: grayscale(0.4) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.25));
}

.scene[data-scene="room02"] .ingredient-option__img {
  display: block;
  height: 100%;
  width: auto;
  user-select: none;
  -webkit-user-drag: none;
}

/* Active option in the picker (yellow glow). */
.scene[data-scene="room02"] .ingredient-option.is-selected {
  filter: drop-shadow(0 0 12px rgba(255, 209, 102, 0.8))
          drop-shadow(0 4px 6px rgba(0, 0, 0, 0.35));
}

/* Center bowls picker: a 4×3 grid sitting on top of the bowls drawn in
   room02/background.png. Tweak the four `--grid-*` variables to nudge
   the grid if it doesn't quite line up with your bowls. Holds the
   "remaining" categories (vegetables, specials, sauces); meat & bun
   keep their left/right vertical columns. */
.scene[data-scene="room02"] .ingredient-grid {
  --grid-left:   22%;     /* left edge of the bowl block (% of scene)   */
  --grid-top:    20%;     /* top edge of the bowl block                 */
  --grid-width:  56%;     /* horizontal span across all 4 bowls         */
  --grid-height: 56%;     /* vertical span across all 3 bowl rows       */
  --grid-col-gap: 0%;
  --grid-row-gap: 0%;
  --bowl-icon-fill: 78%;  /* how much of one bowl the icon occupies     */

  position: absolute;
  left: var(--grid-left);
  top:  var(--grid-top);
  width:  var(--grid-width);
  height: var(--grid-height);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows:    repeat(3, 1fr);
  column-gap: var(--grid-col-gap);
  row-gap:    var(--grid-row-gap);
  z-index: 5;                        /* above bg (2) + label layer (3) */
}

/* Bowl-cell variant: overrides the column-style sizing so the option
   fills its grid cell (and the image fits inside the bowl). */
.scene[data-scene="room02"] .ingredient-option--bowl {
  height: 100%;
  width:  100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.scene[data-scene="room02"] .ingredient-option--bowl .ingredient-option__img {
  height: var(--bowl-icon-fill);
  width:  var(--bowl-icon-fill);
  max-height: var(--bowl-icon-fill);
  max-width:  var(--bowl-icon-fill);
  object-fit: contain;
}

.scene[data-scene="room02"] .ingredient-grid__empty { display: block; }

/* Selected-ingredients tray: a single horizontal row anchored to the
   bottom-center of the scene (same position as the original meat-only
   slot). Categories with `max > 1` (vegetables, sauces, specials) can
   show multiple tiles, all lined up horizontally on this one row.
   Clicking a tile removes just that one ingredient. */
.scene[data-scene="room02"] .selected-ingredients {
  --selected-h:    19.2%;     /* row height as % of scene height          */
  /* Offset of the tray's bottom edge from the scene's bottom edge.
     Negative pushes the tray BELOW the scene; combined with
     overflow:visible on .scene[data-scene="room02"], that lets the
     visible food (which sits in the upper portion of each source PNG)
     drop onto the wooden plank that occupies the bottom ~18% of
     room02/background.png. */
  --selected-bottom: -1%;
  --selected-gap:   1.2%;     /* gap between tiles                        */
  --selected-max-w:  96%;     /* horizontal space the tray may use        */

  position: absolute;
  left: 50%;
  bottom: var(--selected-bottom);
  transform: translateX(-50%);
  z-index: 6;                          /* above the pickers (5)          */
  height: var(--selected-h);
  max-width: var(--selected-max-w);
  display: flex;
  flex-wrap: nowrap;                   /* always one horizontal row      */
  align-items: center;
  justify-content: center;
  gap: var(--selected-gap);
  pointer-events: none;                /* tiles opt back in (see below)  */
}

.scene[data-scene="room02"] .selected-ingredient-tile {
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  height: 100%;                        /* full row height                */
  flex: 0 0 auto;
  pointer-events: auto;
  cursor: pointer;
  display: block;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.45));
  transition: transform 80ms ease, filter 120ms ease;
}

.scene[data-scene="room02"] .selected-ingredient-tile:hover {
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.45)) brightness(1.05);
}

.scene[data-scene="room02"] .selected-ingredient-tile:active {
  transform: translateY(2px);
}

.scene[data-scene="room02"] .selected-ingredient-tile:focus-visible {
  outline: 2px solid #ffd166;
  outline-offset: 3px;
  border-radius: 0.4em;
}

.scene[data-scene="room02"] .selected-ingredient-tile img {
  display: block;
  height: 100%;
  width: auto;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}

/* Room02 layout vars + allow customers to extend above the scene box. */
.scene[data-scene="room02"] {
  overflow: visible;
  --room02-strip-top: 0%;
  /* Was 20%; lowered 10pt so strip sits lower. */
  --room02-shift-up: 10%;
  --room02-slot-height: 52%;
  --room02-label-from-top: clamp(2px, 0.4vh, 8px);
  /* Horizontal nudge applied to every room02 customer slot AND its
     #N label so they stay aligned with each other. The room01 line
     spans --customer-cx 10%..65% (midpoint 37.5%), which is fine
     behind the room01 counter but reads as left-biased in room02.
     Shifting by +12.5% re-centers the whole strip on 50%. */
  --room02-x-shift: 12.5%;
}

.scene__layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}

.scene__layer--background { z-index: 1; }
.scene__layer--theke      { z-index: 3; }

/* Room02 layer stack (low -> high z):
     0  .scene__layer--background-back  (room01/background.png)
     1  .customer-line-room02           (customer images)
     2  .scene__layer--background       (room02/background.png — occludes
                                         the customers except where it's
                                         transparent / cut out)
     3  .customer-line-room02-labels    (#N order tags) */
.scene[data-scene="room02"] .scene__layer--background-back {
  z-index: 0;
  /* Slide the room01 backdrop up by ~30% of its own height. The element
     fills the scene (inset: 0) and the scene matches the background's
     16:9 aspect, so element height == rendered image height — making
     translateY(-30%) equal to "30% of the room01 background image". */
  transform: translateY(-30%);
}

.scene[data-scene="room02"] .customer-line-room02 {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  overflow: visible;
}

.scene[data-scene="room02"] .customer-line-room02-labels {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  overflow: visible;
}

.scene[data-scene="room02"] .scene__layer--background {
  z-index: 2;
}

.scene[data-scene="room02"] .customer-slot.customer-slot--room02 {
  /* Same horizontal centers as room01 (--customer-cx copied in JS),
     re-centered for room02 by --room02-x-shift. Width from in-flow
     <img>: only absolutely positioned children => 0×0.

     Opt back into pointer events: the parent #customer-line-room02
     has pointer-events:none so the room02 background can still be
     clicked through, but each customer slot itself is a click target
     (= "select this customer's order"; see setupRoom02CustomerSelection). */
  display: block;
  left: calc(var(--customer-cx) + var(--room02-x-shift));
  top: calc(var(--room02-strip-top) - var(--room02-shift-up));
  height: var(--room02-slot-height);
  width: max-content;
  max-width: min(28vw, 22%);
  min-width: 4.5rem;
  --customer-h: var(--room02-slot-height);
  transform: translateX(-50%);
  z-index: 0;
  pointer-events: auto;
  cursor: pointer;
}

.scene[data-scene="room02"] .customer-slot.customer-slot--room02:focus-visible {
  outline: 3px solid #ffd166;
  outline-offset: 4px;
  border-radius: 0.4em;
}

/* #N sits in a layer above the background (GIFs stay behind it). Anchored
   to the scene top, NOT to the (shifted) strip — otherwise the labels land
   above the viewport and get clipped. Tweak --room02-label-from-top. */
.scene[data-scene="room02"] .room02-order-label-slot {
  position: absolute;
  /* Mirror the room02 customer slot's horizontal shift so each #N
     label stays directly above its corresponding customer. */
  left: calc(var(--customer-cx) + var(--room02-x-shift));
  top: var(--room02-label-from-top);
  transform: translateX(-50%);
  z-index: 1;
}

.scene[data-scene="room02"] .room02-order-label-slot .room02-order-label {
  /* Reset native <button> styles so we keep the original look. */
  margin: 0;
  padding: 0.05em 0.25em;
  background: transparent;
  border: 0;
  font: inherit;
  font-weight: 900;
  font-size: clamp(16px, 2.6vw, 34px);
  letter-spacing: 0.02em;
  color: #fff;
  text-shadow:
    0 0 2px #000,
    0 1px 3px #000,
    0 2px 10px rgba(0, 0, 0, 0.55);
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;     /* parent layer is pointer-events:none */
  user-select: none;
  white-space: nowrap;
  border-radius: 0.2em;
  transition: color 120ms ease, transform 80ms ease, filter 120ms ease;
}

.scene[data-scene="room02"] .room02-order-label-slot .room02-order-label:hover {
  filter: brightness(1.1);
}

.scene[data-scene="room02"] .room02-order-label-slot .room02-order-label:active {
  transform: translateY(1px);
}

.scene[data-scene="room02"] .room02-order-label-slot .room02-order-label:focus-visible {
  outline: 2px solid #ffd166;
  outline-offset: 2px;
}

.scene[data-scene="room02"] .room02-order-label-slot
  .room02-order-label.is-selected {
  color: #ffd166;           /* yellow when this order is selected */
  text-shadow:
    0 0 2px #000,
    0 1px 3px #000,
    0 0 14px rgba(255, 209, 102, 0.55);
}

/* "Select Customer" prompt: only shown when more than one order is
   waiting and the player has not yet chosen which customer to prepare.
   Big yellow text with a thick black outline, centered horizontally
   over the customer strip. Toggled via the [hidden] attribute by JS. */
.scene[data-scene="room02"] .select-customer-prompt {
  position: absolute;
  left: 50%;
  /* Anchored just above the metal-counter top edge of room02/background.png
     (~30% from the scene's top), so the text reads as sitting on the
     containers' rim. Tweak this single value to nudge up/down. */
  top: 17%;
  transform: translateX(-50%);
  z-index: 7;                       /* above all other room02 layers */
  font-weight: 900;
  font-size: clamp(19.6px, 3.85vw, 50.4px);
  letter-spacing: 0.02em;
  line-height: 1;
  color: #ffd166;
  /* Multi-stop offset shadow gives a sharp, thick outline that scales
     with the font size better than `-webkit-text-stroke` on most setups. */
  text-shadow:
    -3px -3px 0 #000,
     3px -3px 0 #000,
    -3px  3px 0 #000,
     3px  3px 0 #000,
     0   -4px 0 #000,
     0    4px 0 #000,
    -4px  0   0 #000,
     4px  0   0 #000,
     0    6px 14px rgba(0, 0, 0, 0.55);
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
}

.scene[data-scene="room02"] .select-customer-prompt[hidden] {
  display: none;
}

.scene[data-scene="room02"] .customer-slot.customer-slot--room02 .customer {
  position: relative;
  display: block;
  margin-top: 0;
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 100%;
  object-fit: contain;
  object-position: top center;
}

/* Customer slot: groups one customer's animated WebP with their order button and
   speech bubble. The slot owns the position vars; children (image,
   button, bubble) are absolutely positioned relative to this slot.
   Sits between the background and the counter (theke), so the counter
   visually occludes anything below the customer's chin. */
.customer-slot {
  /* ---- tweakables ---- */
  --customer-cx: 50%;        /* horizontal center (% of scene width)  */
  --customer-cy: 52%;        /* vertical center  (% of scene height) */
  --customer-h:  50.4%;      /* height           (% of scene height) */
  /* -------------------- */

  position: absolute;
  left: var(--customer-cx);
  top: var(--customer-cy);
  height: var(--customer-h);
  transform: translate(-50%, -50%);
  z-index: 2;
  pointer-events: none;          /* children opt back in as needed */
}

.customer {
  display: block;
  height: 100%;
  width: auto;                   /* keep the GIF's intrinsic aspect ratio */
  user-select: none;
  -webkit-user-drag: none;
}

/* "Take order" button: floats just above the customer's head. Uses two
   stacked images — the idle one is shown by default, the pressed one
   takes over while the button is held (CSS :active). When an order has
   been taken the button is hidden via the `hidden` attribute. */
.order-btn {
  --order-btn-h: 18%;            /* height as % of customer height */

  position: absolute;
  left: 50%;
  bottom: calc(100% + 2%);       /* sits a bit above the customer's head */
  transform: translateX(-50%);
  height: var(--order-btn-h);
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  pointer-events: auto;
}

.order-btn[hidden] { display: none; }

.order-btn__img {
  display: block;
  height: 100%;
  width: auto;
  -webkit-user-drag: none;
  user-select: none;
}

/* Stack the pressed image on top of the idle one and hide it by default. */
.order-btn__img--pressed {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: none;
}

.order-btn:active .order-btn__img--idle    { visibility: hidden; }
.order-btn:active .order-btn__img--pressed { display: block; }

/* ---------------------------------------------------------------------
   Score-feedback badge. Spawned above a customer's head when the player
   returns to room01 after pressing "N Orders Done". Two flavours:
   - .feedback-popup--good  → green "✓", scales in with a bounce
   - .feedback-popup--bad   → red "✗", scales in and shakes
   The badge stays visible for the rest of the round — it's removed by
   `resetOrders()` / `spawnCustomers()`.
   --------------------------------------------------------------------- */
.feedback-popup {
  position: absolute;
  left: 50%;
  bottom: calc(100% - 4%);            /* roughly above the head         */
  transform: translate(-50%, -30%) scale(1);
  font-family: 'Cinzel Decorative', 'Cinzel', Georgia, serif;
  font-weight: 900;
  font-size: clamp(2.5rem, 7vw, 6rem);
  line-height: 1;
  letter-spacing: 0.02em;
  text-shadow:
    0 0   2px rgba(0, 0, 0, 0.85),
    0 2px 0   rgba(0, 0, 0, 0.55),
    0 6px 16px rgba(0, 0, 0, 0.55);
  pointer-events: none;
  user-select: none;
  opacity: 0;                         /* hidden until .is-active fires  */
  z-index: 10;
  white-space: nowrap;
}

.feedback-popup--good {
  color: #5dd06b;
  text-shadow:
    0 0   2px rgba(0, 0, 0, 0.85),
    0 2px 0   rgba(0, 0, 0, 0.55),
    0 0  18px rgba(93, 208, 107, 0.6);
}

.feedback-popup--bad {
  color: #ff5b5b;
  text-shadow:
    0 0   2px rgba(0, 0, 0, 0.85),
    0 2px 0   rgba(0, 0, 0, 0.55),
    0 0  18px rgba(255, 91, 91, 0.6);
}

.feedback-popup--good.is-active {
  animation: feedback-pop-in 0.9s cubic-bezier(0.2, 1.4, 0.4, 1) forwards;
}

.feedback-popup--bad.is-active {
  animation: feedback-shake-in 1.3s ease-out forwards;
}

/* Both intros end with opacity:1 + the same resting transform so the
   badges stay put after the animation completes. */
@keyframes feedback-pop-in {
  0%   { transform: translate(-50%,  20%) scale(0.4);  opacity: 0; }
  60%  { transform: translate(-50%, -45%) scale(1.35); opacity: 1; }
  80%  { transform: translate(-50%, -32%) scale(0.95); opacity: 1; }
  100% { transform: translate(-50%, -30%) scale(1);    opacity: 1; }
}

@keyframes feedback-shake-in {
  0%   { transform: translate(-50%, 20%)   scale(0.4); opacity: 0; }
  20%  { transform: translate(-50%, -35%)  scale(1.4); opacity: 1; }
  30%  { transform: translate(-65%, -35%)  scale(1.3); opacity: 1; }
  40%  { transform: translate(-35%, -35%)  scale(1.3); opacity: 1; }
  50%  { transform: translate(-65%, -35%)  scale(1.3); opacity: 1; }
  60%  { transform: translate(-35%, -35%)  scale(1.3); opacity: 1; }
  75%  { transform: translate(-50%, -32%)  scale(1.0); opacity: 1; }
  100% { transform: translate(-50%, -30%)  scale(1);   opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .feedback-popup.is-active {
    animation: none;
    opacity: 1;
    transform: translate(-50%, -30%) scale(1);
  }
}

/* ---------------------------------------------------------------------
   Floating "+N" / "−N" points number that drifts up over the customer
   and fades. Auto-removed in JS after the animation; it sits ABOVE the
   persistent ✓/✗ badge so the two don't overlap.
   --------------------------------------------------------------------- */
.feedback-points {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 4%);            /* starts higher than the ✓/✗    */
  transform: translate(-50%, 0) scale(1);
  font-family: 'Cinzel Decorative', 'Cinzel', Georgia, serif;
  font-weight: 900;
  font-size: clamp(2rem, 5vw, 4.5rem);
  line-height: 1;
  letter-spacing: 0.02em;
  text-shadow:
    0 0   2px rgba(0, 0, 0, 0.85),
    0 2px 0   rgba(0, 0, 0, 0.55),
    0 6px 16px rgba(0, 0, 0, 0.55);
  pointer-events: none;
  user-select: none;
  opacity: 0;
  z-index: 11;                        /* above the ✓/✗ badge (10)      */
  white-space: nowrap;
}

.feedback-points--good {
  color: #5dd06b;
  text-shadow:
    0 0   2px rgba(0, 0, 0, 0.85),
    0 2px 0   rgba(0, 0, 0, 0.55),
    0 0  18px rgba(93, 208, 107, 0.6);
}

.feedback-points--bad {
  color: #ff5b5b;
  text-shadow:
    0 0   2px rgba(0, 0, 0, 0.85),
    0 2px 0   rgba(0, 0, 0, 0.55),
    0 0  18px rgba(255, 91, 91, 0.6);
}

.feedback-points.is-active {
  animation: feedback-points-float 1.6s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
}

@keyframes feedback-points-float {
  0%   { transform: translate(-50%,  30%) scale(0.5); opacity: 0; }
  20%  { transform: translate(-50%,  -5%) scale(1.25); opacity: 1; }
  55%  { transform: translate(-50%, -70%) scale(1.05); opacity: 1; }
  100% { transform: translate(-50%, -160%) scale(0.95); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .feedback-points.is-active {
    animation: none;
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
    transition: opacity 250ms ease 1.4s;
  }
}

/* ---------------------------------------------------------------------
   Black fade overlay: covers the whole viewport while game.js swaps the
   level / customer line. Sits ABOVE the gameplay scenes but BELOW the
   level toast (z-index 9000) and the game-over overlay (z-index 9999),
   so the "Level N completed" banner remains readable on the black.
   --------------------------------------------------------------------- */
.scene-fader {
  position: fixed;
  inset: 0;
  background: #000;
  opacity: 0;
  pointer-events: none;
  z-index: 8500;
  /* Default duration; game.js can override per-fade via inline style. */
  transition: opacity 450ms ease;
}

.scene-fader.is-active {
  opacity: 1;
  pointer-events: auto;              /* block clicks while opaque */
}

@media (prefers-reduced-motion: reduce) {
  .scene-fader { transition: opacity 80ms linear; }
}

/* ---------------------------------------------------------------------
   Level-cleared toast: short banner shown between rounds when every
   order was correct. game.js controls show/hide via the `is-visible`
   class so it can stay on screen across the fade-out / fade-in.
   --------------------------------------------------------------------- */
.level-toast {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 9000;                     /* above gameplay + fader, below game-over */
  opacity: 0;
  transition: opacity 250ms ease;
}

.level-toast.is-visible { opacity: 1; }

.level-toast[hidden] { display: none; }

.level-toast__card {
  padding: 0.7em 1.6em;
  background: linear-gradient(180deg, #18341c 0%, #0f1f12 100%);
  border: 3px solid #5dd06b;
  border-radius: 12px;
  box-shadow:
    0 0 0 2px rgba(0, 0, 0, 0.6),
    0 14px 30px rgba(0, 0, 0, 0.5),
    inset 0 0 40px rgba(93, 208, 107, 0.18);
  transform: scale(0.9);
  transition: transform 280ms cubic-bezier(0.2, 0.7, 0.2, 1.2);
}

.level-toast.is-visible .level-toast__card { transform: scale(1); }

.level-toast__text {
  font-family: 'Cinzel Decorative', 'Cinzel', Georgia, serif;
  font-weight: 900;
  font-size: clamp(1.6rem, 4vw, 3rem);
  letter-spacing: 0.05em;
  color: #e8ffe8;
  text-shadow:
    0 0 4px rgba(0, 0, 0, 0.85),
    0 2px 0 rgba(0, 0, 0, 0.6),
    0 0 18px rgba(93, 208, 107, 0.55);
}

@media (prefers-reduced-motion: reduce) {
  .level-toast,
  .level-toast__card { transition: none; }
}

/* ---------------------------------------------------------------------
   Game Over overlay: full-screen modal shown when a single ingredient
   was wrong in any order of a round. Sits above EVERYTHING (including
   the Prepare Order button at z-index 1000 and the schnurrbart).
   --------------------------------------------------------------------- */
.game-over-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.72);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  z-index: 9999;
  animation: game-over-fade-in 320ms ease-out both;
}

.game-over-overlay[hidden] { display: none; }

.game-over-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.8em;
  max-width: min(90vw, 520px);
  padding: 1.6em 2em 1.8em;
  text-align: center;
  background: linear-gradient(180deg, #2a1a14 0%, #1a0d09 100%);
  border: 3px solid #d33;
  border-radius: 14px;
  box-shadow:
    0 0 0 2px rgba(0, 0, 0, 0.6),
    0 18px 40px rgba(0, 0, 0, 0.55),
    inset 0 0 60px rgba(255, 80, 80, 0.18);
  color: #f4e9d8;
  animation: game-over-pop-in 360ms cubic-bezier(0.2, 0.7, 0.2, 1.2) both;
}

.game-over-title {
  margin: 0;
  font-family: 'Cinzel Decorative', 'Cinzel', Georgia, serif;
  font-weight: 900;
  font-size: clamp(2.2rem, 6vw, 4rem);
  letter-spacing: 0.06em;
  color: #ff5b5b;
  text-shadow:
    0 0 4px rgba(0, 0, 0, 0.9),
    0 2px 0 rgba(0, 0, 0, 0.65),
    0 0 24px rgba(255, 91, 91, 0.55);
}

.game-over-subtitle {
  margin: 0;
  font-size: clamp(0.95rem, 1.6vw, 1.25rem);
  opacity: 0.85;
}

.game-over-score {
  margin: 0.2em 0 0.4em;
  font-size: clamp(1.05rem, 1.8vw, 1.4rem);
  font-weight: 700;
}

.game-over-score #game-over-score-value {
  display: inline-block;
  min-width: 1.2em;
  padding: 0 0.25em;
  color: #ffd25a;
}

/* The Try Again button reuses .prepare-order-btn for visual parity, but
   it lives inside the centered card — undo the absolute positioning
   that .prepare-order-btn applies. The combined-class selector raises
   specificity so this rule wins regardless of source order, and the
   `right`/`bottom: auto` resets keep the button in normal flow even if
   the cascade ever changes. The card's pop-in keyframe leaves a
   non-`none` `transform`, which would otherwise turn the card into a
   containing block for the absolutely-positioned button and pin it to
   the card's bottom-right. */
.prepare-order-btn.game-over-restart-btn {
  position: static;
  right: auto;
  bottom: auto;
  margin-top: 0.4em;
}

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

@keyframes game-over-pop-in {
  0%   { transform: scale(0.7) translateY(20px); opacity: 0; }
  60%  { transform: scale(1.04) translateY(0);   opacity: 1; }
  100% { transform: scale(1)    translateY(0);   opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .game-over-overlay,
  .game-over-card { animation: none; }
}

/* Speech bubble: lives inside a .customer-slot and floats just above
   that customer's head. Anchored at its bottom-center so the bubble's
   tail points down at the customer. Sized as % of the customer's
   height so it scales with the slot. */
.speech-bubble {
  --bubble-h: 45%;               /* height as % of customer height */

  position: absolute;
  left: 50%;
  bottom: calc(100% + 4%);       /* a bit of breathing room above the head */
  transform: translateX(-50%);
  height: var(--bubble-h);
  z-index: 4;
  pointer-events: none;
}

.speech-bubble[hidden] { display: none; }

.speech-bubble__bg {
  display: block;
  height: 100%;
  width: auto;
  -webkit-user-drag: none;
  user-select: none;
}

.speech-bubble__text {
  /* Insets carve out the text area inside the bubble graphic. The
     bottom inset leaves room for the bubble's tail. Tweak if your
     speechbubble.png has a different shape. */
  position: absolute;
  inset: 12% 12% 28% 12%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  white-space: pre-line;         /* "\n" in textContent renders as line break */
  font-weight: 700;
  color: #1c1c1c;
  font-size: clamp(11px, 1.2vw, 24px);
  line-height: 1.2;
}

/* "Prepare Order" button: bottom-right of the scene. Hidden until at
   least one customer's order has been taken. Clicking it triggers
   phase two of the game (see Achmet.onPrepareOrder in game.js). */
.prepare-order-btn {
  position: absolute;
  right: 2%;
  bottom: 3%;
  z-index: 1000;             /* always on top of every other graphic */
  padding: 0.6em 1.4em;
  font: inherit;
  font-size: clamp(14px, 1.6vw, 28px);
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #1c1c1c;
  background: var(--accent);
  border: 2px solid #1c1c1c;
  border-radius: 0.5em;
  cursor: pointer;
  box-shadow: 0 4px 0 rgba(0,0,0,0.4), 0 6px 12px rgba(0,0,0,0.3);
  transition: transform 80ms ease, box-shadow 80ms ease;
}

.prepare-order-btn:hover:not(:disabled)  { filter: brightness(1.05); }
.prepare-order-btn:active:not(:disabled) {
  transform: translateY(2px);
  box-shadow: 0 2px 0 rgba(0,0,0,0.4), 0 3px 6px rgba(0,0,0,0.3);
}

.prepare-order-btn:disabled {
  filter: grayscale(0.7) brightness(0.85);
  opacity: 0.55;
  cursor: not-allowed;
}

.prepare-order-btn[hidden] { display: none; }

/* Room02 "N Orders Done" — allow wrapping if the label is long. */
.done-btn {
  max-width: 78%;
  line-height: 1.12;
  white-space: normal;
  text-align: center;
}

/* "Start Game" button: huge, centered call-to-action on the title screen
   (room00). Same visual language as the smaller scene buttons but
   scaled up for impact. */
.start-game-btn {
  position: absolute;
  left: 50%;
  bottom: 6%;
  transform: translateX(-50%);
  z-index: 1000;
  padding: 0.7em 1.8em;
  font: inherit;
  font-size: clamp(28px, 5vw, 72px);
  font-weight: 900;
  letter-spacing: 0.04em;
  color: #1c1c1c;
  background: var(--accent);
  border: 4px solid #1c1c1c;
  border-radius: 0.4em;
  cursor: pointer;
  box-shadow: 0 8px 0 rgba(0,0,0,0.45), 0 14px 24px rgba(0,0,0,0.35);
  transition: transform 80ms ease, box-shadow 80ms ease, filter 120ms ease;
}

.start-game-btn:hover  { filter: brightness(1.08); }
.start-game-btn:active {
  transform: translateX(-50%) translateY(4px);
  box-shadow: 0 4px 0 rgba(0,0,0,0.45), 0 7px 12px rgba(0,0,0,0.35);
}

/* Title screen toggle buttons (background music + animated beard).
   Same accent style as other UI buttons; the beard toggle stacks below
   the music toggle. */
.music-toggle-btn,
.beard-toggle-btn {
  position: absolute;
  left: 2%;
  z-index: 1000;
  padding: 0.45em 0.95em;
  font: inherit;
  font-size: clamp(14px, 1.85vw, 26px);
  font-weight: 800;
  letter-spacing: 0.03em;
  color: #1c1c1c;
  background: var(--accent);
  border: 3px solid #1c1c1c;
  border-radius: 0.45em;
  cursor: pointer;
  box-shadow: 0 4px 0 rgba(0,0,0,0.4), 0 5px 10px rgba(0,0,0,0.25);
  transition: transform 80ms ease, box-shadow 80ms ease, filter 120ms ease;
}

.music-toggle-btn { top: 2%; }
.beard-toggle-btn { top: calc(2% + 3em); }

.music-toggle-btn:hover,
.beard-toggle-btn:hover  { filter: brightness(1.06); }
.music-toggle-btn:active,
.beard-toggle-btn:active {
  transform: translateY(2px);
  box-shadow: 0 2px 0 rgba(0,0,0,0.4), 0 3px 6px rgba(0,0,0,0.25);
}

/* HUD: row of small status badges anchored to the top-right of the scene
   (e.g. Level, Score). */
.hud-top-right {
  position: absolute;
  top: 2%;
  right: 2%;
  z-index: 10;
  display: flex;
  gap: 0.6em;
  pointer-events: none;
  user-select: none;
}

.hud-badge {
  padding: 0.4em 0.9em;
  font-weight: 800;
  letter-spacing: 0.04em;
  font-size: clamp(14px, 1.6vw, 28px);
  color: #fff;
  background: rgba(0, 0, 0, 0.6);
  border: 2px solid rgba(255, 255, 255, 0.85);
  border-radius: 0.4em;
}

/* Cash-register display.
   The wrapper covers the whole scene so percentage coordinates inside
   it are scene-relative. The text is positioned at the visual center
   of the screen quad and rotated to match the screen's tilt. */
.register-screen {
  /* 3D perspective distance. Smaller = stronger effect.
     Defined here (the parent) because the CSS `perspective` property
     needs to be on an ancestor of the rotated element. */
  --rd-perspective: 800px;

  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  perspective: var(--rd-perspective);
  perspective-origin: 50% 50%;
  transform-style: preserve-3d;
}

.register-screen__display {
  /* ===== Position ============================================== */
  /* Visual center of the screen quad (% of scene size). */
  --rd-cx: 79.90%;
  --rd-cy: 48.00%;

  /* ===== Size ================================================== */
  /* Visible text area on the blue display (% of scene size). */
  --rd-w: 9.0%;
  --rd-h: 16.0%;

  /* ===== Skew / Perspective =================================== */
  /* In-plane rotation of the text (clockwise in degrees).        */
  --rd-rot: 11.5deg;
  /* 2D skew (parallelogram look). Set to 0deg to disable.        */
  --rd-skew-x: 0deg;
  --rd-skew-y: 0deg;
  /* NOTE: `--rd-perspective` is defined on the parent             */
  /* (`.register-screen`) — change it there.                       */
  /* Tilt the panel around the X axis (top tilts back/forward).   */
  --rd-tilt-x: -30deg;
  /* Tilt around the Y axis (left/right side leans toward viewer).*/
  --rd-tilt-y: -5deg;
  /* ============================================================ */

  position: absolute;
  left: var(--rd-cx);
  top: var(--rd-cy);
  width: var(--rd-w);
  height: var(--rd-h);
  transform:
    translate(-50%, -50%)
    rotateX(var(--rd-tilt-x))
    rotateY(var(--rd-tilt-y))
    rotateZ(var(--rd-rot))
    skewX(var(--rd-skew-x))
    skewY(var(--rd-skew-y));
  transform-origin: 50% 50%;
  backface-visibility: hidden;

  /* Stack multi-line text (one "Order #N" per line) vertically and
     center it inside the screen quad. `pre-line` renders the "\n"
     characters from JS as actual line breaks. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  white-space: pre-line;
  /* Anything that does not fit inside the screen is clipped, so long
     test strings cannot spill over onto the cash register body. */
  overflow: hidden;
  padding: 0.3em 0.4em;
  box-sizing: border-box;

  font-family: "Courier New", ui-monospace, Menlo, monospace;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.15;
  color: #0a2a3a;
  font-size: clamp(0.9rem, 1.2vw, 1.4rem);
}

/* Schnurrbart: anchored to the actual viewport, NOT the (letterboxed) scene.
   The source PNG has a lot of transparent space below the visible moustache,
   so we push the element below the viewport edge to make the visible
   moustache sit flush with the bottom of the screen. */
.schnurrbart {
  position: fixed;
  z-index: 10;
  left: 50%;
  width: 100vw;
  height: auto;
  /* PNG aspect is 16:9, so element height ~= 100vw * 9/16 = 56.25vw.
     Visible moustache ends at ~67% of image height, leaving ~33% of
     transparent padding below it. 33% * 56.25vw ~= 18.6vw, so we shift
     the element down by that much to keep the visible moustache
     anchored to the bottom of the screen. */
  bottom: -18.6vw;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  transform-origin: 50% 100%;
  --sx: 0px;
  --rot: 0deg;
  transform: translate3d(calc(-50% + var(--sx)), 0, 0) rotate(var(--rot));
  will-change: transform;
  transition: opacity 400ms ease;
}

/* Hide the moustache on the title screen (room00). Uses :has() to react
   to whichever scene is currently active, so the moustache fades along
   with the scene cross-fade. */
#app:has(.scene[data-scene="room00"].is-active) .schnurrbart {
  opacity: 0;
}

/* User-disabled via the room00 "Beard" toggle: hide everywhere and skip
   layout so the JS animation loop has nothing to update. */
#app.is-beard-disabled .schnurrbart {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .schnurrbart { transform: translate3d(-50%, 0, 0); transition: none; }
}
