/* ==========================================================================
   Cartridge Tycoon — components

   Same rule as main.css: no colours, fonts, radii, shadows or effect values in
   here. Every one comes from an era variable defined in src/config/theme.js.

   The point of this file is that it is *era-agnostic*. A button's markup and
   its box model never change; what changes is that in 1983 it has a 3px black
   outline and a hard offset shadow with instant state changes, and by 2010 it
   is a soft rounded card with an eased hover. Both are the same forty lines
   of CSS reading different variables.
   ========================================================================== */

/* ----------------------------------------------------------------- panel */

.panel {
  padding: 14px;
  background: var(--panel);
  background-image: var(--panel-texture);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius);
  box-shadow: var(--shadow), var(--shadow-inset);

  transition:
    background-color var(--t-slow) var(--ease),
    border-color var(--t-slow) var(--ease),
    border-radius var(--t-slow) var(--ease),
    box-shadow var(--t-slow) var(--ease);
}

.panel__heading {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 1.15em;
  text-transform: var(--text-transform);
  text-shadow: var(--text-shadow);
  color: var(--accent);
  margin-bottom: 6px;
}

.panel__lead {
  color: var(--ink-dim);
  font-size: 0.92em;
  margin-bottom: 16px;
  max-width: 62ch;
}

/* A lead paragraph used as a footnote under a control group rather than as an
   intro above one — same voice, no bottom margin to fight the panel padding. */
.panel__lead--tight {
  margin-top: 12px;
  margin-bottom: 0;
  font-size: 0.82em;
}

.empty-state {
  margin-bottom: 0;
}

/* Validation and affordability messages. Empty most of the time, so it must
   not reserve space when it has nothing to say. */
.panel__lead--warn {
  color: var(--bad);
}

.panel__lead--warn:empty {
  display: none;
}

.section-heading {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.85em;
  text-transform: var(--text-transform);
  color: var(--ink-dim);
  margin: 18px 0 8px;
}

/*
  Collapse the top margin only when a section heading directly follows the
  panel's own title or lead — there is already spacing there.

  Deliberately not `:first-of-type`: panels now open with a progress bar before
  their first section heading, and `:first-of-type` would strip the margin from
  that heading too, jamming it against the bar.
*/
.panel__heading + .section-heading,
.panel__heading + .panel__lead + .section-heading {
  margin-top: 0;
}

/* ---------------------------------------------------------------- button */

.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Trailing actions — "abandon", "close" — pushed away from the primary flow. */
.button-row--end {
  justify-content: flex-end;
  margin-top: 14px;
}

.button {
  padding: 10px 16px;
  cursor: pointer;

  background: var(--panel-raised);
  color: var(--ink);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);

  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.9em;
  text-transform: var(--text-transform);
  text-shadow: var(--text-shadow);

  transition:
    background-color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease),
    border-radius var(--t-slow) var(--ease),
    box-shadow var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease);
}

.button:hover:not(:disabled) {
  background: var(--accent);
  color: var(--bg);
}

/*
  The press. `--motion-scale` is 0 in the 8-bit era, so the button does not
  travel at all — it just swaps colour, like a sprite. By the modern era the
  full 2px depress is in play. One variable, four different feels.
*/
.button:active:not(:disabled) {
  transform: translateY(calc(2px * var(--motion-scale)));
  box-shadow: var(--shadow-inset);
}

.button:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

/*
  Selected state for buttons that behave as a toggle group — the speed
  controls, and later the phase and platform pickers. Keyed off `aria-pressed`
  so the styling cannot drift out of step with what assistive technology is
  told, with `.is-active` accepted alongside it for buttons that are marked
  selected without being toggles.
*/
.button[aria-pressed='true'],
.button.is-active {
  background: var(--accent);
  color: var(--bg);
  box-shadow: var(--shadow-inset);
}

.button--primary {
  background: var(--accent);
  color: var(--bg);
}

.button--primary:hover:not(:disabled) {
  background: var(--accent-alt);
}

.button--danger {
  background: var(--bad);
  color: var(--bg);
}

.button--danger:hover:not(:disabled) {
  background: var(--bad);
  filter: brightness(1.15);
}

/* ---------------------------------------------------------------- slider */

.slider-group {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.slider {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.slider__label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  font-size: 0.85em;
  text-transform: var(--text-transform);
  color: var(--ink-dim);
}

.slider__name {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

/*
  The "+++ / ++ / +" emphasis marks next to a field name, shown once the studio
  has learned a genre. Relative marks rather than the raw ideal weight: a
  number would be the answer key, a mark is a nudge.
*/
.slider__emphasis {
  font-style: normal;
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  color: var(--accent);
  letter-spacing: 0.1em;
}

.slider__value {
  font-style: normal;
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

/*
  Range inputs have to be rebuilt from scratch to be themeable at all — the
  native control ignores almost everything. Track and thumb are styled twice
  because WebKit and Firefox expose different pseudo-elements and will drop the
  whole rule if the other vendor's selector is in the same list.
*/
.slider input[type='range'] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  margin: 0;
  background: transparent;
  cursor: pointer;
}

.slider input[type='range']::-webkit-slider-runnable-track {
  height: 12px;
  background: var(--panel-sunken);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
}

.slider input[type='range']::-moz-range-track {
  height: 12px;
  background: var(--panel-sunken);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
}

.slider input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px;
  height: 22px;
  /* Centre the thumb on a 12px track allowing for the era's border width. */
  margin-top: calc(-5px - var(--border-width));
  background: var(--accent);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}

.slider input[type='range']::-moz-range-thumb {
  width: 22px;
  height: 22px;
  background: var(--accent);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
}

/* -------------------------------------------------------------- progress */

.progress {
  position: relative;
  height: 30px;
  overflow: hidden;

  background: var(--panel-sunken);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
}

.progress__fill {
  height: 100%;
  width: var(--progress, 0%);
  background: var(--accent);
  background-image: var(--panel-texture);
  transition: width var(--t-slow) var(--ease);
}

/*
  The label is rendered twice, stacked exactly on top of each other:

    .progress__text            in --ink, correct against the empty track
    .progress__text--inverse   in --bg, clipped to exactly the filled portion

  A single label cannot work here. The fill is --accent, which in every era is
  a bright, saturated colour chosen to stand out against the panel — so
  whichever ink colour reads well on the track is the one that disappears on
  the fill. `mix-blend-mode` is the usual trick but it is unreliable against
  the dithered textures the early eras paint over the fill.

  Both layers and the fill width read the same `--progress` property, so moving
  the bar is one assignment.
*/
.progress__text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.8em;
  text-transform: var(--text-transform);
  color: var(--ink);
}

.progress__text--inverse {
  color: var(--bg);
  clip-path: inset(0 calc(100% - var(--progress, 0%)) 0 0);
  transition: clip-path var(--t-slow) var(--ease);
}

/* ------------------------------------------------------------------ tile */

.tile-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
  gap: 8px;
}

/*
  A clean 2x2 block on a phone. Auto-fit fits three across at 375px and leaves
  the fourth stranded alone on its own row, which reads as a layout bug rather
  than a design.

  This override lives here rather than with the other narrow-screen rules in
  main.css: components.css loads second, so an equally-specific rule there
  would win the cascade and quietly undo it.
*/
@media (max-width: 559px) {
  .tile-row {
    grid-template-columns: repeat(2, 1fr);
  }
}

.tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px;

  background: var(--panel-sunken);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);

  transition:
    background-color var(--t-slow) var(--ease),
    border-color var(--t-slow) var(--ease),
    border-radius var(--t-slow) var(--ease);
}

.tile__label {
  font-size: 0.75em;
  color: var(--ink-dim);
  text-transform: var(--text-transform);
}

.tile__value {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.95em;
  font-variant-numeric: tabular-nums;
  /*
    Uppercased in the pixel eras (and left alone from 32-bit on, where the
    variable is `none`). Silkscreen's lowercase glyphs are only a few pixels
    tall, so a unit like "wks" or "/wk" turns to mush at this size while the
    uppercase forms stay crisp.
  */
  text-transform: var(--text-transform);
  /*
    Never wrap. The early eras' fonts are wide enough that a two-word value
    like "299 wks" breaks across lines, which makes one tile taller than its
    neighbours and the whole row looks broken.
  */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tile--good .tile__value { color: var(--good); }
.tile--bad  .tile__value { color: var(--bad); }

/* ----------------------------------------------------------------- badge */

.badge {
  display: inline-block;
  padding: 3px 8px;
  background: var(--accent);
  color: var(--bg);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.7em;
  text-transform: var(--text-transform);
  white-space: nowrap;
}

/* --------------------------------------------------------------- badge row */

.badge-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  font-size: 0.82em;
  color: var(--ink-dim);
  margin-bottom: 14px;
}

.badge--good {
  background: var(--good);
}

.badge--bad {
  background: var(--bad);
}

/* ------------------------------------------------------------ campaign button */

/*
  Marketing buttons carry two lines — what it costs and what it is worth right
  now — so they stack rather than centring a single label. The value figure
  falls every time a campaign is run, and watching that happen is the whole
  point of showing it.
*/
#dev-marketing .button {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  text-align: left;
  flex: 1 1 170px;
}

#dev-marketing .button .option__sub {
  color: inherit;
  opacity: 0.75;
}

/* ---------------------------------------------------------------- boxart */

.boxart-era {
  margin-bottom: 18px;
}

.boxart-era__heading {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.8em;
  text-transform: var(--text-transform);
  color: var(--ink-dim);
  margin-bottom: 8px;
}

.boxart-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 10px;
}

.boxart-card {
  display: flex;
  flex-direction: column;
  background: var(--panel-sunken);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  overflow: hidden;

  transition:
    border-color var(--t-slow) var(--ease),
    border-radius var(--t-slow) var(--ease),
    box-shadow var(--t-slow) var(--ease);
}

.boxart-card__canvas {
  width: 100%;
  /* Game-case proportions, matching BOX_ASPECT in src/render/boxart.js. */
  aspect-ratio: 1 / 1.4;
}

.boxart-card__caption {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 6px;
  text-align: center;
}

.boxart-card__title {
  font-size: 0.72em;
  line-height: 1.3;
  text-transform: var(--text-transform);
  /* Two lines maximum, so one long title cannot make a whole row taller. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.boxart-card__score {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.72em;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  white-space: nowrap;
}

/* Era, release date, units sold — supporting detail, deliberately quiet. */
.boxart-card__meta {
  font-size: 0.62em;
  line-height: 1.4;
  color: var(--ink-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* ----------------------------------------------------------------- toast */

#toasts {
  position: fixed;
  left: 50%;
  bottom: calc(78px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: 60;

  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: min(420px, calc(100vw - 24px));
  pointer-events: none;
}

.toast {
  width: 100%;
  padding: 10px 14px;

  background: var(--panel-raised);
  background-image: var(--panel-texture);
  color: var(--ink);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  font-size: 0.88em;
  text-align: center;

  /*
    Entry animation. Duration comes from the era, so in 8-bit the toast simply
    appears — no slide, no fade — and by the modern era it eases up into place.
  */
  animation: toast-enter var(--t-slow) var(--ease);
  transition:
    opacity var(--t-slow) var(--ease),
    transform var(--t-slow) var(--ease);
}

.toast.is-leaving {
  opacity: 0;
  transform: translateY(calc(8px * var(--motion-scale)));
}

.toast__title {
  display: block;
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  text-transform: var(--text-transform);
  text-shadow: var(--text-shadow);
  margin-bottom: 2px;
}

.toast--good  { border-color: var(--good); }
.toast--bad   { border-color: var(--bad); }

/* The era-up toast is the loudest message the game ever shows. */
.toast--era {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--border-color);
  font-size: 1em;
}

.toast--era .toast__title {
  font-size: 1.15em;
}

@keyframes toast-enter {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: none;
  }
}

/* ----------------------------------------------------------- panel group */

/*
  A tab's worth of panels. `display: contents` so the sections inside still
  participate in #stage's flex column and pick up its gap — wrapping them in a
  real box would swallow the spacing and need it re-declared.

  The `[hidden]` rule has to be restated because `display: contents` overrides
  the user-agent `[hidden] { display: none }`.
*/
.panel-group {
  display: contents;
}

.panel-group[hidden] {
  display: none;
}

/* ------------------------------------------------------------ text input */

.field-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: stretch;
}

.text-input {
  flex: 1 1 200px;
  min-width: 0;
  min-height: 44px;
  padding: 10px 12px;

  background: var(--panel-sunken);
  color: var(--ink);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);

  transition:
    background-color var(--t-slow) var(--ease),
    border-color var(--t-fast) var(--ease),
    border-radius var(--t-slow) var(--ease);
}

.text-input::placeholder {
  color: var(--ink-dim);
}

.text-input:focus {
  border-color: var(--accent);
  outline: none;
}

/* ---------------------------------------------------------------- option */

/*
  The wizard's choice buttons. A grid rather than a select element: the whole
  point of the create screen is comparing options side by side, and a dropdown
  hides everything except the one already chosen.
*/
.option-grid {
  display: grid;
  /* 150px, not 120: "Platformer" in the 8-bit face at 0.86em is about 110px of
     glyphs before padding, and a clipped genre name is a bad first impression
     on the most important screen in the game. */
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px;
}

.option-grid--wide {
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
}

.option {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 10px;
  cursor: pointer;
  text-align: left;

  background: var(--panel-sunken);
  color: var(--ink);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);

  transition:
    background-color var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease),
    border-radius var(--t-slow) var(--ease);
}

.option:hover:not(:disabled) {
  background: var(--panel-raised);
}

.option.is-selected {
  background: var(--accent);
  color: var(--bg);
  border-color: var(--border-color);
  box-shadow: var(--shadow);
}

.option:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

.option__label {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.86em;
  text-transform: var(--text-transform);
  /* A long single-word label breaks rather than spilling out of its button. */
  overflow-wrap: anywhere;
}

.option__sub {
  font-size: 0.7em;
  line-height: 1.4;
  color: var(--ink-dim);
}

/* A selected option inverts, so its supporting text has to invert too or it
   disappears into the accent fill. */
.option.is-selected .option__sub,
.option.is-selected .option__hint {
  color: var(--bg);
  opacity: 0.75;
}

/*
  Chemistry hints. Only rendered once the studio has shipped enough games in a
  genre to have earned them — see the note in src/ui/wizard.js.
*/
.option__hint {
  font-size: 0.68em;
  text-transform: var(--text-transform);
  color: var(--ink-dim);
}

.option__hint--great,
.option__hint--good {
  color: var(--good);
}

.option__hint--poor,
.option__hint--terrible {
  color: var(--bad);
}

/* --------------------------------------------------------------- summary */

.summary {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.summary__line {
  font-size: 0.84em;
  color: var(--ink-dim);
}

/* ------------------------------------------------------------------ chip */

.chip-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
}

.chip {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;

  background: var(--panel-sunken);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
}

.chip__label {
  font-size: 0.68em;
  text-transform: var(--text-transform);
  color: var(--ink-dim);
}

.chip__value {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.8em;
}

.chip--good .chip__value { color: var(--good); }
.chip--bad  .chip__value { color: var(--bad); }

/* ---------------------------------------------------------------- record */

/*
  The generic "one thing in a list" card, used for staff, candidates, offices
  and research nodes. They share a component because they share a shape —
  title, supporting detail, actions — and four near-identical card styles would
  drift apart the first time one of them was tweaked.
*/

.record-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 10px;
  align-items: start;
}

.record {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 12px;

  background: var(--panel-sunken);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);

  transition:
    background-color var(--t-slow) var(--ease),
    border-color var(--t-slow) var(--ease),
    border-radius var(--t-slow) var(--ease),
    opacity var(--t-fast) var(--ease);
}

/* Someone away on a course, or a research node already bought. */
.record--muted,
.record--owned {
  opacity: 0.6;
}

/* A research node whose prerequisites are not met yet. */
.record--locked {
  opacity: 0.45;
}

.record__title {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.9em;
  text-transform: var(--text-transform);
  overflow-wrap: anywhere;
}

.record__sub {
  font-size: 0.76em;
  line-height: 1.45;
  color: var(--ink-dim);
}

.record__sub--good { color: var(--good); }
.record__sub--warn { color: var(--bad); }

.record__actions {
  display: flex;
  gap: 6px;
  margin-top: 4px;
}

.record__actions--wrap {
  flex-wrap: wrap;
}

/* Record buttons are secondary actions in a dense list, so they shrink. */
.record .button {
  padding: 7px 10px;
  font-size: 0.78em;
  min-height: 38px;
}

/* ---------------------------------------------------------------- skills */

.skills {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 4px 0;
}

/*
  One row per discipline: label, bar, number. The bar is what the player
  actually reads — "is this person shaped like the games I make?" is a
  question about proportions, and a shape answers it faster than four decimals.
  The number stays for when 80 versus 75 genuinely matters.
*/
.skill {
  display: grid;
  grid-template-columns: 4.5em 1fr 1.8em;
  align-items: center;
  gap: 6px;
}

.skill__label {
  font-size: 0.68em;
  text-transform: var(--text-transform);
  color: var(--ink-dim);
}

.skill__track {
  height: 8px;
  background: var(--panel);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.skill__fill {
  display: block;
  height: 100%;
  background: var(--accent);
  transition: width var(--t-slow) var(--ease);
}

.skill__value {
  font-size: 0.68em;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--ink-dim);
}

/* ----------------------------------------------------------------- modal */

.modal {
  position: fixed;
  inset: 0;
  z-index: 75;
  display: flex;
  /*
    `flex-start` plus `margin: auto` on the card, rather than `align-items:
    center`. Centred flex items whose content is taller than the container
    overflow in both directions, and the overflow above the top edge is
    unreachable by scrolling — so on a short screen the score and box art
    would be permanently cut off. Auto margins centre when there is room and
    simply do not when there is not.
  */
  align-items: flex-start;
  justify-content: center;
  padding: 16px;
  overflow-y: auto;
}

.modal[hidden] {
  display: none;
}

.modal__backdrop {
  position: fixed;
  inset: 0;
  /* Dimming is a shadow, not a colour — reusing the era's shadow token keeps
     the backdrop consistent without introducing a literal. */
  background: var(--bg);
  opacity: 0.82;
}

.modal__card {
  position: relative;
  margin: auto;
  width: min(560px, 100%);
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  padding: 16px;

  background: var(--panel);
  background-image: var(--panel-texture);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius);
  box-shadow: var(--shadow), var(--shadow-inset);
}

/* ---------------------------------------------------------------- review */

.review__head {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  margin-bottom: 6px;
}

.review__boxart {
  flex: 0 0 auto;
  width: 96px;
  /* Game-case proportions, matching BOX_ASPECT in src/render/boxart.js. */
  aspect-ratio: 1 / 1.4;
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
}

.review__headline {
  min-width: 0;
}

.review__scoreline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.review__score {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 2.2em;
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  text-shadow: var(--text-shadow);
}

.review__outof {
  font-size: 0.8em;
  color: var(--ink-dim);
}

.review__quotes {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.review-quote {
  display: grid;
  /* Outlet and score on one row, the quote spanning both beneath — keeps the
     scores in a scannable column without squeezing the text. */
  grid-template-columns: 1fr auto;
  gap: 2px 8px;
  padding: 8px 10px;
  background: var(--panel-sunken);
  border: var(--border-width) var(--border-style) var(--border-color);
  border-radius: var(--radius-sm);
}

.review-quote__outlet {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.74em;
  text-transform: var(--text-transform);
  color: var(--ink-dim);
}

.review-quote__score {
  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.86em;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

.review-quote__text {
  grid-column: 1 / -1;
  font-size: 0.82em;
  line-height: 1.45;
}

/* ------------------------------------------------------------------- fx */

/*
  Number pops, confetti and the celebration flash. A fixed, non-interactive
  layer above the game but below the review modal, so a celebration never
  swallows a click or covers the verdict that triggered it.
*/

#fx-layer {
  position: fixed;
  inset: 0;
  z-index: 55;
  pointer-events: none;
  overflow: hidden;
}

.pop {
  position: absolute;
  transform: translate(-50%, 0);
  white-space: nowrap;

  font-family: var(--font-display);
  font-weight: var(--font-weight-display);
  font-size: 0.9em;
  font-variant-numeric: tabular-nums;
  text-shadow: var(--text-shadow);

  animation: pop-rise 1.2s var(--ease) forwards;
}

.pop--good { color: var(--good); }
.pop--bad { color: var(--bad); }

@keyframes pop-rise {
  0% { opacity: 0; transform: translate(-50%, 0) scale(0.8); }
  15% { opacity: 1; transform: translate(-50%, -6px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -46px) scale(1); }
}

.confetti {
  position: absolute;
  top: -20px;
  left: 50%;
  width: 8px;
  height: 8px;
  /* Square in the pixel eras, round by the modern one — the confetti ages
     along with everything else. */
  border-radius: var(--radius-sm);
  animation: confetti-fall 2.6s var(--ease) forwards;
  animation-delay: var(--delay, 0s);
}

/* Three tints drawn from the era palette, so a celebration is always in the
   colours of whichever generation the studio is currently in. */
.confetti[data-tint='0'] { background: var(--accent); }
.confetti[data-tint='1'] { background: var(--accent-alt); }
.confetti[data-tint='2'] { background: var(--good); }

@keyframes confetti-fall {
  0% {
    opacity: 1;
    transform: translate(0, 0) rotate(0deg);
  }
  100% {
    opacity: 0;
    transform: translate(var(--drift, 0), var(--fall, 100vh)) rotate(var(--spin, 360deg));
  }
}

/* `.screen-flash` lives in styles/crt.css — it needs a literal white, which is
   the one file where that exception is documented. */

/*
  Reduced motion: src/render/fx.js already skips confetti and the flash
  entirely, and pops appear without travelling.
*/
@media (prefers-reduced-motion: reduce) {
  .pop {
    animation: none;
    opacity: 1;
  }
  .confetti,
  .screen-flash {
    display: none;
  }
}
