/*
 * Orfeo — styles for the retro-style platformer.
 * Companion to main.js. All selectors prefixed with `or-` to
 * avoid collisions with the host page and the Tetro game.
 */

/* Scoped reset + global text-selection / long-press suppression for
   every descendant of the overlay. Firefox Mobile (and some Android
   browsers) pop a "Search / Find in Page" toolbar the moment ANY
   selection takes hold, and the inherited `user-select` rule alone
   isn't reliable across all element types — applying it directly to
   every node closes that gap. */
.or-overlay,
.or-overlay * {
    box-sizing: border-box;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}

.or-overlay {
    --or-fg: #0f380f;
    --or-bg: #6b8a18;
    --or-mid: #306230;
    --or-shadow: 4px 4px 0 var(--or-fg);
    --or-shadow-sm: 3px 3px 0 var(--or-mid);
    /* Cell size for the play area (16 wide × 16 tall visible viewport;
       world can be taller and scrolled via cameraY). Each tile is
       2 monospace chars wide × 1 line tall, so the rendered screen is
       32 chars × 16 lines. In typical monospace fonts a char's advance
       width is ~0.6 × font-size, so screen-width ≈ 32 × 0.6 × cell ≈ 19.2c.
         Width budget:  19.2c (screen) + ~40 (overlay padding + screen border).
         Height budget: 16c (screen) + ~6c (dpad) = 22c + ~120 + 10vw. */
    --or-cell: clamp(10px, min(calc((100dvh - 120px - 10vw) / 22), calc((100dvw - 50px) / 19.5)), 22px);
    position: fixed;
    inset: 0;
    background: var(--or-bg);
    z-index: 1000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: safe center;
    gap: clamp(12px, 2.5vw, 28px);
    padding: clamp(12px, 2vw, 24px) clamp(4px, 1.5vw, 16px);
    opacity: 0;
    transform: scale(1.05);
    pointer-events: none;
    transition: opacity 300ms ease, transform 300ms ease;
    font-family: "Lucida Console", "Lucida Sans Typewriter", "Courier New", Courier, monospace;
    overflow-y: auto;
    color: var(--or-fg);
    /* Touch hardening: disable browser gestures so multi-finger presses
       on the D-pad / A / B can't trigger pinch-zoom, double-tap-zoom,
       pull-to-refresh, or page scroll. */
    touch-action: none;
    overscroll-behavior: contain;
}
.or-overlay.is-visible {
    opacity: 1;
    transform: scale(1);
    pointer-events: auto;
}

/* Shared button styles. */
.or-btn-outline {
    font: inherit;
    font-weight: bold;
    letter-spacing: 0.1em;
    color: var(--or-fg);
    background: var(--or-bg);
    border: 2px solid var(--or-fg);
    text-shadow: 1px 1px 0 var(--or-mid);
    box-shadow: var(--or-shadow);
    cursor: pointer;
    transition: transform 60ms ease, box-shadow 60ms ease, background 120ms ease, color 120ms ease;
    -webkit-tap-highlight-color: transparent;
}
.or-btn-outline:hover {
    background: var(--or-fg);
    color: var(--or-bg);
}
.or-btn-outline:active,
.or-btn-outline.is-pressed {
    transform: translate(4px, 4px);
    box-shadow: 0 0 0 var(--or-fg);
}

.or-btn-fill {
    font: inherit;
    font-weight: bold;
    color: var(--or-bg);
    background: var(--or-fg);
    border: none;
    box-shadow: var(--or-shadow-sm);
    cursor: pointer;
    transition: transform 60ms ease, box-shadow 60ms ease, background 80ms ease;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
    -webkit-touch-callout: none;
    -webkit-user-drag: none;
    /* `none` (not `manipulation`) so the browser never tries to
       interpret a press as scroll/zoom — required for reliable
       multi-touch button presses on iOS Safari. */
    touch-action: none;
    display: grid;
    place-items: center;
    padding: 0;
}
.or-btn-fill:active,
.or-btn-fill.is-pressed {
    transform: translate(3px, 3px);
    box-shadow: 0 0 0 var(--or-mid);
    background: var(--or-mid);
}

.or-btn-outline:focus-visible,
.or-btn-fill:focus-visible {
    outline: 2px dashed var(--or-bg);
    outline-offset: 3px;
}

.or-title {
    color: var(--or-fg);
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
    font-size: clamp(14px, 2.4vw, 22px);
    font-weight: bold;
    text-shadow: 1px 1px 0 var(--or-mid);
}

/* Top bar holds the X-close + title on a single row, constrained to the same
   width as the play screen (32ch under the screen's font metrics) so the X
   sits at the screen's left margin rather than the page edge. */
.or-top-bar {
    position: relative;
    width: 100%;
    /* Match the screen's font metrics so `32ch` resolves to the same pixel
       width here as on .or-screen. */
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: var(--or-cell);
    line-height: 1;
    max-width: 32ch;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: clamp(28px, 4vw, 40px);
}
.or-close-x {
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    font-size: clamp(12px, 1.8vw, 18px);
    line-height: 1;
    padding: clamp(3px, 0.5vw, 6px) clamp(8px, 1vw, 12px);
    letter-spacing: 0;
}
.or-close-x:active,
.or-close-x.is-pressed {
    /* Override the .or-btn-outline 'translate(4px,4px)' so the translateY(-50%)
       keep-centered isn't lost when pressed. */
    transform: translate(4px, calc(-50% + 4px));
}

.or-stage {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: clamp(20px, 4vw, 40px);
}

/* The square play area: world <pre> + sprite overlay <div> + HUD + overlays.
   Sets font-size here so children (.or-world, .or-sprites, .or-sprite) inherit
   it — that way 1ch and 1em inside them refer to the same metrics, keeping
   sprite alignment pixel-perfect against the world's tile grid. */
.or-screen {
    position: relative;
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: var(--or-cell);
    line-height: 1;
    width: 32ch;
    height: 16em;
    background: var(--or-bg);
    border: 2px solid var(--or-fg);
    box-shadow: var(--or-shadow);
    overflow: hidden;
    user-select: none;
}
.or-screen.is-game-over,
.or-screen.is-paused,
.or-screen.is-win {
    cursor: pointer;
}

/* Parallax background layers: clouds (far, slow) + hills (mid, faster).
   Both share the world's font metrics so 1ch/1em match, and the world's
   empty cells render as transparent over them. Both sit at z-index 0 with
   the world (z 1) on top — DOM order puts hills above clouds since they
   are drawn closer. */
.or-bg,
.or-bg2 {
    position: absolute;
    inset: 0;
    z-index: 0;
    margin: 0;
    padding: 0;
    letter-spacing: 0;
    white-space: pre;
    pointer-events: none;
}
.or-bg {
    color: var(--or-mid);
    /* Slightly softened so clouds don't look as crisp as foreground tiles —
       reinforces "distance" subliminally. */
    opacity: 0.7;
}
.or-bg2 {
    color: var(--or-mid);
    /* Hills sit closer than clouds — more opaque so they read as
       mid-distance horizon rather than far sky. */
    opacity: 0.9;
}
.or-world {
    position: absolute;
    inset: 0;
    z-index: 1;
    margin: 0;
    padding: 0;
    letter-spacing: 0;
    white-space: pre;
    color: var(--or-fg);
    text-shadow: 1px 1px 0 var(--or-mid);
    /* Intentionally NOT setting font-family — the UA stylesheet's
       `pre { font-family: monospace }` is what gives us seamless Block
       Element rendering on mobile (some monospace stacks like ui-monospace
       render █ with sub-pixel gaps between adjacent cells). The cell
       probe in .or-cell-probe is forced to `monospace` to match this
       choice so its measurement matches the world's actual cell width. */
}

/* Hidden probe used to measure the actual rendered cell width in the
   world's font (font + font-size inherited from .or-screen). The screen
   is sized in `ch`, but on mobile the rendered glyph advance for tile
   characters (██) doesn't always match the `0`-advance `ch` unit, so the
   screen-box-derived cellPxW is off. Reading getBoundingClientRect() on
   this probe gives the actual rendered width of one tile (2 chars). */
.or-cell-probe {
    position: absolute;
    left: -9999px;
    top: 0;
    visibility: hidden;
    pointer-events: none;
    white-space: pre;
    line-height: 1;
    letter-spacing: 0;
    /* Match the world `<pre>`'s effective font — UA stylesheets force
       `pre { font-family: monospace }`, so the world renders in generic
       monospace. The probe needs the same font so its measured width
       equals the world's actual rendered cell width. */
    font-family: monospace;
    font-size: var(--or-cell);
}

/* Tile-decoration layer: sits ON the world but UNDER sprites. Hosts the
   centered icon overlays on coin boxes — lets the world stay in Block
   Elements (which fill the cell) while the decoration can be any unicode
   character (Geometric Shapes, ?, ★ …) without needing to fill the cell. */
.or-tile-overlay {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    letter-spacing: 0;
}
.or-box-icon {
    position: absolute;
    left: 0;
    top: 0;
    /* Tile is 2 monospace cells wide × 1 cell tall. The arrow itself is
       drawn as a CSS-clipped triangle via ::before below — using a unicode
       glyph (▲/▼) is unreliable across platforms because Geometric Shapes
       are positioned differently inside their em box in each mobile font
       fallback (Lucida Console is desktop-only, so iOS/Android render with
       Courier or system monospace where the glyph sits low or off-center). */
    width: 2ch;
    height: var(--or-cell);
}
/* Center the triangle via the absolute-inset + margin:auto trick — flex
   centering of an empty ::before pseudo can land off-center on some
   mobile browsers (the pseudo inherits a baseline that flex aligns to
   rather than the box center). */
.or-box-icon-top::before,
.or-box-icon-bottom::before {
    content: "";
    position: absolute;
    inset: 0;
    margin: auto;
    /* Triangle dimensions: ~55% of cell width / ~50% of cell height. The
       drop-shadow filter replicates the offset hard shadow that used to
       come from text-shadow on the glyph version. */
    width: calc(var(--or-cell) * 0.55);
    height: calc(var(--or-cell) * 0.5);
    background: var(--or-bg);
    filter: drop-shadow(1px 1px 0 var(--or-fg));
    /* On touch-primary devices the platform monospace font (Courier on
       iOS, Roboto Mono / Droid Sans Mono on Android) paints `█` aligned
       to the cap-height region — filling the upper part of the em-box,
       not the full em-square. A triangle centered in the full line-box
       therefore sits below the block's visual center. Shift it up by a
       fraction of cell height via media query; desktop renders block as
       a full em-square fill so no shift is needed there. */
    transform: translateY(var(--or-block-offset, 0));
}
@media (hover: none) and (pointer: coarse) {
    .or-overlay {
        --or-block-offset: calc(var(--or-cell) * -0.1);
    }
}
.or-box-icon-top::before {
    clip-path: polygon(50% 0%, 100% 100%, 0% 100%);
}
.or-box-icon-bottom::before {
    clip-path: polygon(0% 0%, 100% 0%, 50% 100%);
}
/* Transient bump element — replaces the world tile (rendered as AIR
   underneath) while a hit/stomped block plays its short bounce. */
.or-tile-bump {
    position: absolute;
    left: 0;
    top: 0;
    width: 2ch;
    height: var(--or-cell);
    line-height: 1;
    pointer-events: none;
    will-change: transform;
}
.or-tile-bump-glyph {
    position: absolute;
    inset: 0;
    color: var(--or-fg);
    text-shadow: 1px 1px 0 var(--or-mid);
}
.or-tile-bump-icon {
    position: absolute;
    inset: 0;
}

.or-sprites {
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    letter-spacing: 0;
}

.or-sprite {
    position: absolute;
    left: 0;
    top: 0;
    margin: 0;
    padding: 0;
    line-height: 1;
    white-space: pre;
    color: var(--or-fg);
    text-shadow: 1px 1px 0 var(--or-mid);
    /* Center origin so `scaleX(-1)` (applied in JS for left-facing motion)
       flips the sprite in place rather than shifting it by its width. */
    transform-origin: center;
}
/* High-density sprite layer: halves the font-size so quadrant-block glyphs
   inside represent twice as many pixels per character cell. An 8×8 pixel
   sprite (compiled to 4×4 quadrant chars) renders at the same on-screen
   footprint as the prior 2×2-char (4×4-pixel) sprite, just with 4× the
   pixel detail. Position is set in CSS pixels from JS, not ch/em, so this
   font-size difference doesn't affect placement. */
.or-sprite.or-sprite-pixel-2x {
    font-size: calc(var(--or-cell) * 0.5);
}
.or-sprite.is-squish {
    transition: transform 80ms ease;
    transform-origin: bottom left;
}
.or-sprite.is-debris {
    transition: opacity 360ms linear;
}
/* Moving platforms in Sky High levels — match the dark palette
   color used elsewhere so they read as in-world tiles, not props. */
.or-sprite.or-moving-platform {
    color: var(--or-fg);
    text-shadow: 1px 1px 0 var(--or-mid);
}
/* Rare diamond pickup — bigger than the standard cell glyph and
   hard-flips between the dark palette color and the bright green
   each cycle (no tween — old-school sprite flicker). */
.or-sprite.or-diamond {
    font-size: calc(var(--or-cell) * 1.45);
    /* Lift up so the larger glyph still sits centered on its cell. */
    margin-top: calc(var(--or-cell) * -0.22);
    animation: or-diamond-pulse 0.55s steps(1, end) infinite alternate;
}
/* Invincibility star pickup — same big-glyph + hard-flip treatment
   as the diamond, slightly faster cycle. The textContent cycles
   through ✶✷✸✹ frames in JS for the "energetic sparkle" look. */
.or-sprite.or-star {
    font-size: calc(var(--or-cell) * 1.45);
    margin-top: calc(var(--or-cell) * -0.22);
    animation: or-diamond-pulse 0.32s steps(1, end) infinite alternate;
}
/* Player while invincible — fast 3-phase color cycle spanning the
   full palette (dark fg → mid → light bg) so the alternation is
   clearly perceptible on the small pixel-2x sprite (0.5× font-size).
   The earlier 2-phase fg↔mid version was too subtle at the player's
   small scale even though it worked fine on the diamond/star pickups
   which render at 1.45× font-size. */
.or-sprite.or-invincible {
    animation: or-invincible-flicker 0.18s steps(1, end) infinite;
}
@keyframes or-invincible-flicker {
    0%, 33% {
        color: var(--or-fg);
        text-shadow: 1px 1px 0 var(--or-mid);
    }
    34%, 66% {
        color: var(--or-mid);
        text-shadow: 1px 1px 0 var(--or-bg);
    }
    67%, 100% {
        color: var(--or-bg);
        text-shadow: 1px 1px 0 var(--or-fg);
    }
}
@keyframes or-diamond-pulse {
    from {
        color: var(--or-fg);
        text-shadow: 1px 1px 0 var(--or-mid);
    }
    to {
        color: var(--or-bg);
        text-shadow: 1px 1px 0 var(--or-fg);
    }
}

.or-hud {
    position: absolute;
    top: clamp(4px, calc(var(--or-cell) * 0.3), 10px);
    z-index: 4;
    display: flex;
    align-items: baseline;
    /* Fixed horizontal gap between items — doesn't depend on the
       digit widths of the values, so the group doesn't shuffle when
       LEVEL or SCORE grows. */
    gap: clamp(10px, calc(var(--or-cell) * 0.8), 22px);
    font-size: clamp(14px, calc(var(--or-cell) * 1.0), 26px);
    color: var(--or-fg);
    text-shadow: 1px 1px 0 var(--or-mid);
    font-weight: bold;
    letter-spacing: 0.1em;
    pointer-events: none;
}
.or-hud-center {
    /* Single centered group: ♥x## ◉x## LEVEL # SCORE #. */
    left: 50%;
    transform: translateX(-50%);
}
.or-hud-item {
    font-size: 0.8em;
    white-space: nowrap;
}
/* Tabular figures across every numeric span so digit widths are
   consistent. Each numeric span also gets a min-width so a smaller
   value (e.g., level "1") reserves the space of a wider one (e.g.,
   "10"), preventing the group from re-centering each tick. */
.or-hud-value,
#or-lives,
#or-coins {
    font-variant-numeric: tabular-nums;
    display: inline-block;
    text-align: left;
}
#or-lives, #or-coins { min-width: 2ch; }
#or-level { min-width: 2ch; }
#or-score { min-width: 6ch; }

/* Floating "+50" / "+100" point-popup over the sprite layer. Smaller font
   than the HUD, bold, fades out as it rises. Override the .or-sprite line-
   height so the single-line text doesn't get tall. */
.or-sprite.or-points {
    font-family: "Lucida Console", "Lucida Sans Typewriter", "Courier New", Courier, monospace;
    font-size: clamp(9px, calc(var(--or-cell) * 0.55), 14px);
    font-weight: bold;
    letter-spacing: 0.05em;
    line-height: 1;
    color: var(--or-fg);
    text-shadow: 1px 1px 0 var(--or-mid);
    white-space: nowrap;
    z-index: 3;
}

/* GAME OVER / LEVEL CLEAR sit high in the sky as boxless text so the
   player remains visible below; the action button (TRY AGAIN / CONTINUE)
   sits low, below the player. PAUSED keeps its boxed treatment because
   it's a menu state, not a celebratory moment. */
.or-game-over-text {
    position: absolute;
    left: 50%;
    top: 32%;
    transform: translate(-50%, -50%);
    z-index: 5;
    font-family: inherit;
    font-size: clamp(14px, calc(var(--or-cell) * 0.95), 22px);
    font-weight: bold;
    line-height: 1.2;
    text-align: center;
    color: var(--or-fg);
    text-shadow: 1px 1px 0 var(--or-mid);
    pointer-events: none;
}
.or-game-over-text[hidden] {
    display: none;
}
#or-pause-text {
    top: 50%;
    background: var(--or-bg);
    border: 2px solid var(--or-fg);
    box-shadow: var(--or-shadow);
    padding: 1.5em 3em;
    max-width: 90%;
}

.or-restart-btn {
    position: absolute;
    left: 50%;
    top: 68%;
    transform: translate(-50%, -50%);
    z-index: 5;
    /* Match the .or-game-over-text font size so TRY AGAIN reads as
       equally weighted with the GAME OVER / LEVEL CLEAR message. */
    font-size: clamp(14px, calc(var(--or-cell) * 0.95), 22px);
    padding: clamp(6px, calc(var(--or-cell) * 0.55), 14px) clamp(12px, calc(var(--or-cell) * 0.9), 22px);
    white-space: nowrap;
}
.or-restart-btn:active {
    transform: translate(calc(-50% + 4px), calc(-50% + 4px));
}
.or-restart-btn[hidden] {
    display: none;
}

.or-controls {
    align-self: stretch;
    width: 0;
    min-width: 100%;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    gap: clamp(16px, 2.5vw, 28px);
}
.or-dpad {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    aspect-ratio: 1;
    width: 100%;
    min-width: 0;
    gap: 0;
}
.or-dpad-btn {
    width: 100%;
    height: 100%;
    font-size: clamp(16px, 3.5vw, 24px);
    /* position: relative anchors the ::after hit-zone extension that
       reaches into the dead center cell — see below. */
    position: relative;
}
.or-dpad-center {
    background: var(--or-fg);
    grid-column: 2;
    grid-row: 2;
    /* The center cell is purely decorative; it forwards no events of
       its own. Hit-tests in this cell are absorbed by the left and
       right buttons' ::after extensions, which stack above it because
       they belong to actual button elements. Disabling pointer-events
       here also prevents the ::after on the right button (which is
       rendered AFTER the center span in DOM order) from being
       hit-shadowed by it. */
    pointer-events: none;
    /* Match the positioned context of the surrounding buttons so the
       paint order remains tree order (up, left, center, right, down).
       Without this, the center span paints BEFORE the now-positioned
       buttons and their shadows leak visibly into the center cell;
       restoring the original paint order lets the center cover those
       shadow tails the way it did before the ::after extensions were
       added. */
    position: relative;
}
.or-dpad-up    { grid-column: 2; grid-row: 1; }
.or-dpad-left  { grid-column: 1; grid-row: 2; }
.or-dpad-right { grid-column: 3; grid-row: 2; }
.or-dpad-down  { grid-column: 2; grid-row: 3; }
/* Invisible hit-zone extensions: the left button's hit area reaches
   into the LEFT half of the center cell; the right button's reaches
   into the RIGHT half. The buttons themselves stay visually
   identical — these are pure pseudo-element overlays that count as
   part of the parent for pointer-event dispatch, so existing
   pointerdown / touchstart / setPointerCapture flow on the button
   handles them automatically with no JS changes. */
.or-dpad-left::after,
.or-dpad-right::after {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    width: 50%;
    background: transparent;
}
.or-dpad-left::after  { left: 100%; }
.or-dpad-right::after { right: 100%; }

.or-ab {
    width: 100%;
    display: flex;
    gap: clamp(8px, 1.5vw, 16px);
    transform: rotate(-18deg);
    align-items: center;
    justify-content: flex-end;
    container-type: inline-size;
}
.or-ab-btn {
    flex: 1 1 0;
    min-width: 0;
    aspect-ratio: 1;
    height: auto;
    border-radius: 50%;
    font-size: clamp(14px, 15cqw, 36px);
    text-shadow: 1px 1px 0 var(--or-mid);
}

/* Playfield wrapper — contains bg/world/sprites only. The shake target so
   the screen frame, HUD, and overlay text stay still while only the world
   contents rattle. */
.or-playfield {
    position: absolute;
    inset: 0;
}

/* Hurt-shake / ground-pound-impact — applied briefly to the playfield. */
@keyframes or-shake {
    0%, 100% { transform: translate(0, 0); }
    20%      { transform: translate(-4px, 0); }
    40%      { transform: translate( 4px, 0); }
    60%      { transform: translate(-3px, 0); }
    80%      { transform: translate( 3px, 0); }
}
.or-playfield.or-shake { animation: or-shake 280ms linear; }
