/* --------------------------------------------------
 * Cascade layers
 *
 * Declared up-front so the order is explicit and does
 * not depend on where each block appears in the file.
 *
 *  reset      - normalise browser defaults (never edited)
 *  base       - design tokens + bare element styling
 *  layout     - page/game structure and positioning
 *  components - individual UI pieces (HUD, overlays, buttons)
 *  utilities  - single-purpose helpers (fonts, .hidden)
 *  responsive - viewport-driven overrides
 * -------------------------------------------------- */
@layer reset, base, layout, components, utilities, responsive;

/* ==================================================
 * RESET
 * ================================================== */
@layer reset {
  *,
  *::after,
  *::before {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }

  :where(html) {
    color-scheme: light dark;
    hanging-punctuation: first allow-end last;
    interpolate-size: allow-keywords;
    scroll-behavior: smooth;
    scrollbar-gutter: stable;
    text-size-adjust: none;
    -webkit-text-size-adjust: none;
    -webkit-tap-highlight-color: transparent;
    -webkit-font-smoothing: antialiased;
  }

  :where(html.translated-rtl) {
    direction: rtl;
  }

  @media (prefers-reduced-motion: reduce) {
    :where(html) {
      scroll-behavior: auto;
    }
  }

  :where(body) {
    min-block-size: 100svb;
    min-inline-size: 300px;
  }

  :where(canvas, img, picture, svg, video) {
    block-size: auto;
    border: none;
    display: block;
    max-inline-size: 100%;
  }

  :where(button, input, progress, select, textarea) {
    appearance: none;
    background: transparent;
    border: none;
    color: inherit;
    font: inherit;
    hanging-punctuation: none;
    line-height: inherit;
    text-align: start;
    touch-action: manipulation;
  }

  :where(button) {
    cursor: pointer;
    user-select: none;
  }

  :where(textarea) {
    resize: vertical;
  }

  :where(textarea:not([rows])) {
    field-sizing: content;
  }

  :where(fieldset, iframe) {
    border: none;
  }

  :where(p, li, h1, h2, h3, h4, h5, h6) {
    overflow-wrap: break-word;
    text-wrap: pretty;
  }

  :where(abbr[title]) {
    border: none;
    text-decoration: none;
  }

  :where(cite) {
    font-style: inherit;
  }

  :where(small) {
    font-size: inherit;
  }

  :where(li, ol, ul) {
    list-style: none;
  }

  :where(dialog, [popover]) {
    background: transparent;
    border: none;
    color: inherit;
    margin: auto;
    max-block-size: none;
    max-inline-size: none;
    position: fixed;
  }

  :where([popover]) {
    inset: auto;
  }

  :where(dialog:not([open], [popover]), [popover]:not(:popover-open)) {
    display: none;
  }

  :where([hidden]:not([hidden='until-found'])) {
    display: none !important;
  }
}

/* ==================================================
 * BASE - tokens and bare elements
 * ================================================== */
@layer base {
  :root {
    --accent-fill: linear-gradient(
      135deg,
      oklch(0.6 0.16 272) 0%,
      oklch(0.5 0.14 300) 100%
    );

    --chrome-gap: 1.5rem;

    /* Vertical space reserved for everything that is not the board.
       Just breathing room here; the portrait layout raises it to pay
       for the HUD row above the board. */
    --page-chrome-height: 2rem;

    /* Zero unless the responsive layer turns the controls on, so the
       board budget composes from two independent knobs. */
    --touch-controls-height: 0rem;

    /* 640px is the board's nominal size, which keeps desktop exactly
       as it was; large touch screens raise it. */
    --board-max-height: 640px;

    /* Shared so the preview can scale with the board rather than
       being pinned while the board grows around it. */
    --board-display-height: min(
      calc(100svh - var(--page-chrome-height) - var(--touch-controls-height)),
      var(--board-max-height)
    );

    /* Purely a visual proportion, not a derived constant -
       js/renderer.js matches the backing store to whatever this
       resolves to, so the preview stays sharp at any value. 0.125
       reproduces the original 80px preview against a 640px board. */
    --preview-scale: 0.125;
  }

  body {
    margin: 0;
    background: oklch(0.24 0 270);

    display: flex;
    flex-direction: column;
    gap: var(--chrome-gap);

    justify-content: center;
    align-items: center;

    /* svh, not vh, so mobile browser chrome doesn't push the page
       taller than the visible viewport */
    min-height: 100svh;
  }

  canvas {
    background: oklch(0.3 0 0);
    box-shadow: 0 0px 50px oklch(0 0 0 / 0.65);
  }
}

/* ==================================================
 * LAYOUT
 * ================================================== */
@layer layout {
  /* The board spans both rows; the rail splits into a 1fr row (score
     HUD, pinned to the top) and an auto row (preview, which
     therefore sits at the bottom). */
  .game-container {
    display: grid;
    grid-template-areas:
      'board hud'
      'board next'
      'controls controls';
    grid-template-columns: auto auto;
    grid-template-rows: 1fr auto auto;
    column-gap: var(--chrome-gap);
  }

  /* Containing block for the overlays. Sized by the canvas alone, so
     `inset: 0` overlays always cover the board exactly - never the
     surrounding chrome. */
  .board-stage {
    grid-area: board;
    position: relative;
    justify-self: center;
  }
}

/* ==================================================
 * COMPONENTS
 * ================================================== */
@layer components {
  /* Sits inside the start overlay rather than on the page, so it has
     to fit the board's width rather than the viewport's - hence a
     smaller ceiling than a page heading would take. */
  .text-header {
    font-size: clamp(1.1rem, 6vw, 1.75rem);
    text-wrap: balance;
    color: oklch(0.65 0.2 300);
    text-shadow: 0 0px 20px oklch(0 0 0 / 0.2);
  }

  /* Debug HUD */

  .dev-hud {
    position: absolute;
    top: 1rem;
    left: 1rem;

    min-width: 180px;
    padding: 0.75rem 1rem;

    background: oklch(0 0 0 / 0.65);
    backdrop-filter: blur(6px);

    border: 1px solid oklch(1 0 0 / 0.1);
    border-radius: 8px;

    box-shadow: 0 0 25px oklch(0 0 0 / 0.6);

    font-size: 0.85rem;
    pointer-events: none; /* doesn't interfere with gameplay */

    transition:
      opacity 0.5s,
      transform 0.5s;
    @starting-style {
      opacity: 0;
      transform: translateY(20px);
    }
  }

  .hud-header {
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    opacity: 0.7;
    margin-bottom: 0.5rem;
  }

  .hud-content {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
  }

  /* Score HUD */

  .score-hud {
    grid-area: hud;

    display: flex;
    flex-direction: column;
    gap: 1.5rem;

    min-width: 120px;
  }

  .score-block {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
  }

  .score-label {
    font-size: 0.75rem;
    letter-spacing: 0.15em;
    opacity: 0.6;
    color: oklch(1 0 0);
  }

  .score-value {
    font-size: clamp(1.1rem, 4.5vw, 1.5rem);
    font-weight: 600;
    color: oklch(1 0 0);
  }

  /* Next piece preview */

  .preview-container {
    grid-area: next;

    display: flex;
    flex-direction: column;
    gap: 0.5rem;

    align-items: flex-start;
  }

  .preview-label {
    font-size: 0.75rem;
    letter-spacing: 0.15em;
    opacity: 0.6;
    color: oklch(1 0 0);
  }

  #previewCanvas {
    background: oklch(0 0 0 / 0.15);
    border: 1px solid oklch(1 0 0 / 0.15);
    border-radius: 4px;
    box-shadow: 0 0 10px oklch(0.6 0.16 272 / 0.3);
    max-inline-size: none;
  }

  /* --------------------------------------------------
   * Board overlays (start screen, game over)
   *
   * Both cover the board exactly and dismiss the same way, so
   * everything shared lives here and only the contents differ
   * further down.
   * -------------------------------------------------- */

  .start-screen,
  .game-over-screen {
    --overlay-gap: clamp(0.75rem, 4vw, 2rem);

    position: absolute;
    inset: 0;

    display: flex;
    flex-direction: column;
    align-items: center;
    /* `safe` keeps the top of the content reachable if it ever does
       overflow; centred overflow is otherwise unscrollable. Plain
       `center` first as the fallback. */
    justify-content: center;
    justify-content: safe center;
    gap: var(--overlay-gap);

    padding: var(--overlay-gap);
    overflow-y: auto;

    background: oklch(0 0 0 / 0.85);
    backdrop-filter: blur(8px);

    z-index: 100;

    /* `display` is transitioned discretely so the .hidden utility's
       display: none only lands once the fade has finished. Without
       allow-discrete the element vanishes instantly and the opacity
       transition never actually runs. */
    transition:
      opacity 0.3s,
      visibility 0.3s,
      display 0.3s allow-discrete;

    &.hidden {
      opacity: 0;
      visibility: hidden;
      pointer-events: none;
    }
  }

  /* --------------------------------------------------
   * Buttons
   *
   * One appearance shared by the start button, the game-over
   * buttons and the on-screen touch controls. Only sizing and press
   * behaviour differ per use - see .touch-button below.
   * -------------------------------------------------- */

  .start-button,
  .menu-button,
  .touch-button {
    /* Glow as tokens so a variant can dial it back by overriding
       three values rather than restating every shadow rule. */
    --button-glow: 0 4px 15px oklch(0.6 0.16 272 / 0.4);
    --button-glow-hover: 0 6px 20px oklch(0.6 0.16 272 / 0.6);
    --button-glow-active: 0 2px 10px oklch(0.6 0.16 272 / 0.4);

    color: oklch(1 0 0);
    padding: clamp(0.7rem, 3vw, 1rem) clamp(1.25rem, 8vw, 3rem);
    font-size: clamp(1rem, 4vw, 1.25rem);
    border: none;
    border-radius: 8px;
    text-align: center;
    cursor: pointer;

    background: var(--accent-fill);
    box-shadow: var(--button-glow);
    transition:
      transform 0.2s,
      box-shadow 0.2s;
    &:hover {
      transform: translateY(-2px);
      box-shadow: var(--button-glow-hover);
    }
    &:active {
      transform: translateY(0);
      box-shadow: var(--button-glow-active);
    }
  }

  /* Start screen contents */

  .start-screen .prompt {
    font-size: clamp(1.75rem, 9vw, 2.5rem);
    color: white;
    margin: 0;
    text-shadow: 0 0px 20px oklch(0.6 0.16 272 / 0.8);
  }

  .controls-hint {
    color: oklch(1 0 0 / 0.75);
    text-align: center;
    font-size: clamp(0.7rem, 3.2vw, 0.9rem);
    line-height: 1.6;

    & p {
      &.action {
        color: oklch(1 0.16 272 / 0.6);
      }

      &.inputs {
        color: oklch(1 0 0);
        margin-bottom: clamp(0.3rem, 2vw, 0.75rem);
      }
    }
  }

  /* Only one hint variant is ever shown - the responsive layer swaps
     them over for coarse pointers. */
  .controls-hint--touch {
    display: none;
  }

  /* --------------------------------------------------
   * On-screen touch controls
   *
   * Two rows of three:
   *   rotate left | hard drop  | rotate right
   *   move left   | soft drop  | move right
   * -------------------------------------------------- */

  .touch-controls {
    grid-area: controls;

    /* Switched to grid by the responsive layer for coarse pointers */
    display: none;

    grid-template-columns: repeat(3, 1fr);
    gap: 0.5rem;

    margin-block-start: 0.75rem;
  }

  /* Appearance comes from the shared button rule above - only what
     has to differ is set here. */
  .touch-button {
    /* Six of these sit side by side, and at the full glow they bloom
       into one another. */
    --button-glow: 0 2px 6px oklch(0.6 0.16 272 / 0.2);
    --button-glow-hover: var(--button-glow);
    --button-glow-active: 0 1px 4px oklch(0.6 0.16 272 / 0.3);

    display: flex;
    align-items: center;
    justify-content: center;

    /* Thumb-sized. The grid column sets the width, so the menu
       buttons' generous horizontal padding is dropped. */
    min-block-size: clamp(2.75rem, 9vw, 3.5rem);
    padding: 0.25rem;
    font-size: clamp(1.1rem, 5vw, 1.4rem);
    line-height: 1;

    /* Game inputs, not page gestures - no double-tap zoom, no pan */
    touch-action: none;

    /* No hover lift. These only ever appear on coarse pointers,
       where a lingering :hover is a leftover from the last tap
       rather than a cursor sitting on the button. */
    &:hover {
      transform: none;
    }

    /* Driven from js/touch.js rather than :active, which stops
       firing reliably once the pointer has been captured. The
       brightness carries most of the feedback, since the softened
       shadow is too subtle to read on its own. */
    &.is-pressed {
      transform: translateY(1px);
      box-shadow: var(--button-glow-active);
      filter: brightness(1.3);
    }
  }

  /* Game over screen contents */

  .game-over-title {
    font-size: clamp(1.5rem, 8vw, 2.5rem);
    color: white;
    text-shadow: 0 0px 20px oklch(0.6 0.16 272 / 0.8);
  }

  .game-over-score-block {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
  }

  .game-over-score-value {
    font-size: clamp(2rem, 10vw, 3rem);
    color: oklch(0.85 0.16 272);
    text-shadow: 0 0 20px oklch(0.6 0.16 272 / 0.6);
  }

  /* No horizontal padding of its own - the overlay's padding already
     insets it, and doubling up squeezes the buttons. */
  .game-over-buttons {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
  }

  .menu-button {
    width: 100%;
  }

  .game-over-buttons .start-button {
    width: 100%;
  }

  /* --------------------------------------------------
   * Audio settings
   *
   * Pill toggles rather than checkboxes, for a target big enough
   * for a thumb - the old label was only as tall as its 20px
   * checkbox, under the 24px WCAG minimum.
   *
   * The real <input type="checkbox"> is still there, visually
   * hidden but focusable: main.js reads .checked and listens for
   * change, and keeping it preserves the keyboard and screen reader
   * behaviour a checkbox already has for free.
   * -------------------------------------------------- */

  .audio-settings {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.75rem;
  }

  .audio-toggle {
    display: flex;
    align-items: center;
    justify-content: center;

    min-block-size: 2.75rem;

    /* Both pills sit at this width so they read as a matched pair
       rather than being sized by their labels. Clears the longer
       label at the largest clamped font size, and still leaves the
       two of them fitting side by side on a 320px board. */
    min-inline-size: 7rem;

    padding: 0.5rem 1.25rem;
    font-size: clamp(0.8rem, 3.5vw, 0.9rem);
    line-height: 1;

    border-radius: 999px;
    cursor: pointer;
    user-select: none;

    /* Off - outlined and muted */
    color: oklch(1 0 0 / 0.55);
    background: oklch(1 0 0 / 0.05);
    border: 1px solid oklch(1 0 0 / 0.18);

    transition:
      color 0.2s,
      background 0.2s,
      border-color 0.2s,
      box-shadow 0.2s;

    /* On - picks up the same accent fill as the buttons */
    &:has(input:checked) {
      color: oklch(1 0 0);
      background: var(--accent-fill);
      border-color: transparent;
      box-shadow: 0 2px 6px oklch(0.6 0.16 272 / 0.2);
    }

    &:hover {
      border-color: oklch(1 0 0 / 0.35);
    }

    /* The input is visually hidden, so the focus ring has to be
       drawn on the label or keyboard users lose it entirely. */
    &:has(input:focus-visible) {
      outline: 2px solid oklch(0.75 0.16 272);
      outline-offset: 2px;
    }

    & span {
      line-height: 1;
      display: flex;
      align-items: center;
    }

    /* Hidden from view but not from the accessibility tree, and
       still focusable - display: none would lose both. */
    & input[type='checkbox'] {
      position: absolute;
      inline-size: 1px;
      block-size: 1px;
      margin: -1px;
      padding: 0;
      border: 0;
      overflow: hidden;
      clip-path: inset(50%);
      white-space: nowrap;
    }
  }
}

/* ==================================================
 * UTILITIES
 * ================================================== */
@layer utilities {
  .poppins-light {
    font-family: 'Poppins', sans-serif;
    font-weight: 300;
    font-style: normal;
  }

  .poppins-regular {
    font-family: 'Poppins', sans-serif;
    font-weight: 400;
    font-style: normal;
  }

  .poppins-semibold {
    font-family: 'Poppins', sans-serif;
    font-weight: 600;
    font-style: normal;
  }

  .poppins-bold {
    font-family: 'Poppins', sans-serif;
    font-weight: 700;
    font-style: normal;
  }

  .changa-one-regular {
    font-family: 'Changa One', sans-serif;
    font-weight: 400;
    font-style: normal;
  }

  .changa-one-regular-italic {
    font-family: 'Changa One', sans-serif;
    font-weight: 400;
    font-style: italic;
  }

  .hidden {
    display: none;
  }
}

/* ==================================================
 * RESPONSIVE
 * ================================================== */
@layer responsive {
  /* The stylesheet owns the display size and js/renderer.js matches
     the backing store to it, so the board is sharp at whatever size
     it lands on.

     The ratio has to be stated rather than inherited from the canvas
     attributes, or the two would be circular - the size resolved
     here is what those attributes get derived from. js/renderer.js
     sets --board-aspect; the fallback covers the frame before it
     first runs. */
  #canvas {
    aspect-ratio: var(--board-aspect, 10 / 20);
    inline-size: auto;
    block-size: var(--board-display-height);
  }

  /* Same arrangement as the board. Sized on the inline axis with the
     block axis auto, so the portrait cap below can clamp it without
     the fixed ratio distorting the cells. */
  #previewCanvas {
    aspect-ratio: 1;
    inline-size: calc(var(--board-display-height) * var(--preview-scale));
    block-size: auto;
  }

  /* --------------------------------------------------
   * Stacked layout
   *
   * Triggered on available width, not device class. The
   * side-by-side layout needs roughly board (320px max) + gap +
   * rail (120px min), so it stops fitting a little under 34rem.
   * Wide phones and tablets keep the rail.
   * -------------------------------------------------- */
  @media (width < 34rem) {
    :root {
      /* Covers the HUD row above the board plus its row gap. Too
         large just means a smaller board, too small means the page
         scrolls vertically - err large. */
      --page-chrome-height: 6rem;
    }

    .game-container {
      grid-template-areas:
        'hud next'
        'board board'
        'controls controls';
      grid-template-columns: 1fr auto;
      grid-template-rows: auto auto auto;
      row-gap: 0.75rem;
    }

    /* Score blocks run across the top instead of down the side.
       min-width has to go, or three 120px blocks overflow a board
       that is only ~300px wide. */
    .score-hud {
      flex-direction: row;
      justify-content: space-between;
      align-items: flex-end;
      gap: 0.75rem;
      min-width: 0;
    }

    .preview-container {
      align-items: flex-end;
      gap: 0.25rem;
    }

    /* Cap the preview so the top row stays within what
       --page-chrome-height above is paying for. Clamped on the
       inline axis, matching how it is sized, so the height follows
       and the cells stay square. */
    #previewCanvas {
      max-inline-size: 3rem;
    }

    /* Debug mode is unavailable at this width - js/debug.js gates
       the toggle on the same breakpoint. Belt and braces: the panel
       can never be stranded over the board, and the start screen
       never advertises a key that does nothing. */
    .dev-hud,
    .controls-hint-debug {
      display: none;
    }
  }

  /* --------------------------------------------------
   * Touch devices
   *
   * Keyed on pointer type rather than width: a phone in landscape
   * still needs the controls, and a narrow desktop window still
   * does not.
   * -------------------------------------------------- */
  @media (pointer: coarse) {
    :root {
      /* Two button rows plus the gap above them */
      --touch-controls-height: 7rem;
    }

    .touch-controls {
      display: grid;
    }

    /* Swap the keyboard hints for the on-screen ones */
    .controls-hint--keyboard {
      display: none;
    }

    .controls-hint--touch {
      display: block;
    }

    /* The board is a game surface, not a scrollable page area */
    #canvas {
      touch-action: none;
    }

    /* No pull-to-refresh or bounce while playing */
    body {
      overscroll-behavior: none;
    }

    /* On touch a :hover lingers after the tap, so an "off" pill
       would keep the brighter border until you tapped elsewhere. */
    .audio-toggle:hover {
      border-color: oklch(1 0 0 / 0.18);
    }

    .audio-toggle:hover:has(input:checked) {
      border-color: transparent;
    }
  }

  /* Keyed on coarse pointer as well as width so desktop is
     deliberately left exactly as it was. Only raises the ceiling -
     the budget calc still applies, so a short-but-wide tablet still
     shrinks to fit. */
  @media (pointer: coarse) and (width >= 48rem) {
    :root {
      --board-max-height: 960px;
    }
  }

  /* --------------------------------------------------
   * Touch devices, landscape
   *
   * Height is the scarce resource here, so the controls move beside
   * the board instead of below it. Same 3x2 block of buttons in the
   * same DOM order - only its position changes.
   *
   * Must come after the block above: it overrides both budget
   * tokens now that nothing is stacked under the board.
   * -------------------------------------------------- */
  @media (pointer: coarse) and (orientation: landscape) {
    :root {
      --page-chrome-height: 2rem;
      --touch-controls-height: 0rem;
    }

    /* --overlay-gap scales off viewport *width*, which is generous
       here and exactly the wrong axis - a landscape board is short,
       and five gaps at the maximum push the start screen into
       scrolling. */
    .start-screen,
    .game-over-screen {
      --overlay-gap: 0.75rem;
    }

    .game-container {
      grid-template-areas:
        'hud board controls'
        'next board controls';
      grid-template-columns: auto auto auto;
      grid-template-rows: 1fr auto;
    }

    .touch-controls {
      align-content: center;
      margin-block-start: 0;
    }

    /* The controls column is content-sized, so without a minimum
       inline size the buttons collapse to the width of the glyph. */
    .touch-button {
      min-inline-size: clamp(2.75rem, 9vw, 3.5rem);
    }
  }
}
