/* Tango Lite — phone first.
 *
 * Two rules run through all of this. The page never scrolls as a whole
 * (a scroll gesture that starts on the D-pad must not drag the app
 * around), so `.app` is exactly one viewport tall and only the content
 * panes scroll inside it. And every control is at least 44px in its
 * short axis, because that is the smallest thing a thumb hits reliably.
 */

/* The palette is desktop Tango's dark theme: Tango green (#4caf50 —
 * theme-independent there, the default of the accent picker) on its
 * neutral dark greys, rather than a scheme of this build's own. */
:root {
    --bg: #0e1011;
    --panel: #16191a;
    --panel-2: #1e2223;
    --line: #2b3032;
    --text: #eceeed;
    --dim: #8f9694;
    --accent: #4caf50;
    /* The wash behind a selected row, and the pressed state on the pad. */
    --accent-soft: rgba(76, 175, 80, 0.3);
    --accent-dim: #1b3a1e;
    /* Green is light enough that dark ink out-reads white on it. */
    --on-accent: #08210b;
    --good: #4caf50;
    --bad: #ff5252;
    --pad: 14px;
    --radius: 12px;
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left: env(safe-area-inset-left, 0px);
    --safe-right: env(safe-area-inset-right, 0px);
}

* {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
    height: 100%;
    overflow: hidden;
    overscroll-behavior: none;
    background: var(--bg);
    color: var(--text);
    font: 15px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    /* Long-press on a button should not offer to select or copy it. */
    -webkit-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
}

/* …but a field you're meant to type in has to be selectable, or iOS
 * won't put a caret in it. */
input,
textarea {
    -webkit-user-select: text;
    user-select: text;
}

/* The app root is pinned to the viewport rather than sized against
 * `body`. In a standalone PWA on iOS, `height: 100%` on html/body
 * resolves short of the screen, and `body`'s `overflow: hidden` then
 * *clips* whatever the app does about its own height — which is the
 * dead band under the tab bar, and why setting `.app`'s height alone
 * didn't move it. A fixed box with `inset: 0` is the viewport by
 * definition, which is the same mechanism the play screen already
 * relies on. */
#main {
    position: fixed;
    inset: 0;
}

.boot {
    display: grid;
    place-items: center;
    height: 100%;
    color: var(--dim);
}

/* No safe-area padding here on purpose. Padding the whole app pushes
 * the chrome inward and leaves the inset strip showing bare background
 * — under a home indicator that reads as a dead band below the tab bar.
 * The bars run to the screen edges instead and inset their own
 * contents, which is what a native app does. */
.app {
    display: flex;
    flex-direction: column;
    /* Of `#main`, which is the viewport — see above. */
    height: 100%;
}

/* ---------------------------------------------------------------- bars */

.topbar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: calc(10px + var(--safe-top)) calc(var(--pad) + var(--safe-right)) 10px
        calc(var(--pad) + var(--safe-left));
    border-bottom: 1px solid var(--line);
    background: var(--panel);
    flex: none;
}

.topbar h1 {
    font-size: 16px;
    font-weight: 650;
    letter-spacing: 0.02em;
    margin: 0;
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Game names are real titles now ("Mega Man Battle Network 6: Cybeast
 * Falzar"), so the subtitle has to be allowed to lose its tail rather
 * than push the heading off the bar. */
.topbar .sub {
    color: var(--dim);
    font-size: 13px;
    font-weight: 400;
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tabs {
    display: flex;
    flex: none;
    border-top: 1px solid var(--line);
    background: var(--panel);
    /* The bar's background reaches the bottom of the screen; its
     * buttons stop above the home indicator. */
    padding-bottom: var(--safe-bottom);
}

.tabs button {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 52px;
    background: none;
    border: 0;
    border-top: 2px solid transparent;
    color: var(--dim);
    font: inherit;
    font-weight: 600;
}

.tabs button[aria-selected="true"] {
    color: var(--accent);
    border-top-color: var(--accent);
}

/* ------------------------------------------------------------- content */

.pane {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: var(--pad) calc(var(--pad) + var(--safe-right)) var(--pad) calc(var(--pad) + var(--safe-left));
    display: flex;
    flex-direction: column;
    gap: var(--pad);
}

.card {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--pad);
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.card > h2 {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--dim);
    margin: 0;
}

.row {
    display: flex;
    align-items: center;
    gap: 10px;
}

.row .grow {
    flex: 1;
    min-width: 0;
}

.muted {
    color: var(--dim);
    font-size: 13px;
}

.error {
    color: var(--bad);
    font-size: 13px;
}

.empty {
    color: var(--dim);
    text-align: center;
    padding: 24px 8px;
    font-size: 14px;
}

/* ------------------------------------------------------------ controls */

button,
select,
input {
    font: inherit;
    color: var(--text);
}

/* Centred explicitly rather than relying on the default, because `.btn`
 * is worn by both `<button>` (which centres its text) and `<label>`
 * (which doesn't) — the file pickers are labels, and they sat
 * left-aligned next to identical-looking buttons. Same reason the
 * alignment is stated on every button-ish class below rather than
 * inherited from whatever the element happens to be. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 46px;
    padding: 0 18px;
    border-radius: 10px;
    border: 1px solid var(--line);
    background: var(--panel-2);
    font-weight: 600;
    text-align: center;
    line-height: 1.2;
}

.btn:disabled {
    opacity: 0.4;
}

.btn.primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--on-accent);
}

.btn.danger {
    color: var(--bad);
}

.btn.wide {
    width: 100%;
}

/* A card is a flex column, so a button dropped straight into one would
 * stretch to its width while the same button inside a `.row` sits at
 * its natural size — which is why "Add ROM" and "Add save" didn't
 * match. Natural width is the default; `.wide` is how a full-width
 * action asks for it. */
.card > .btn:not(.wide) {
    align-self: flex-start;
}

.btn.small {
    min-height: 36px;
    padding: 0 12px;
    font-size: 13px;
    font-weight: 500;
}

input[type="text"],
select {
    min-height: 46px;
    width: 100%;
    padding: 0 12px;
    border-radius: 10px;
    border: 1px solid var(--line);
    background: var(--panel-2);
}

/* A file input styled as a button: the native control is unusable on a
 * phone, but its click behaviour is the only way to open the picker.
 * The layout comes from `.btn`; this only has to trap the input. */
.file {
    position: relative;
    overflow: hidden;
}

.file input {
    position: absolute;
    inset: 0;
    opacity: 0;
    font-size: 100px;
}

/* --------------------------------------------------------------- lists */

.list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.item {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 52px;
    padding: 8px 12px;
    border-radius: 10px;
    border: 1px solid var(--line);
    background: var(--panel-2);
    text-align: left;
    width: 100%;
}

.item[aria-selected="true"] {
    border-color: var(--accent);
    background: linear-gradient(0deg, var(--accent-dim) -60%, var(--panel-2) 70%);
}

.item .title {
    font-weight: 600;
}

.item .meta {
    color: var(--dim);
    font-size: 12px;
}

.item .stack {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    flex: 1;
    min-width: 0;
}

/* Meta lines truncate; a game's title is allowed a second line rather
 * than being cut off mid-word. */
.item .stack > * {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.item .stack > .title {
    white-space: normal;
    overflow-wrap: anywhere;
}

/* A button that is the row's tap target rather than a control of its
 * own — it has to stay a <button> for keyboard and screen readers, but
 * it must not look like one. */
.bare {
    background: none;
    border: 0;
    padding: 0;
    text-align: left;
    min-height: 36px;
}

.pill {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 3px 8px;
    border-radius: 999px;
    border: 1px solid var(--line);
    color: var(--dim);
    flex: none;
}

.pill.on {
    color: var(--good);
    border-color: currentColor;
}

.pill.off {
    color: var(--bad);
    border-color: currentColor;
}

.bar {
    height: 4px;
    border-radius: 2px;
    background: var(--line);
    overflow: hidden;
}

.bar > div {
    height: 100%;
    background: var(--accent);
}

/* -------------------------------------------------------------- lobby */

.versus {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 8px;
}

.versus .side {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.versus .side.them {
    text-align: right;
    align-items: flex-end;
}

.versus .vs {
    color: var(--dim);
    font-size: 12px;
    font-weight: 700;
}

.versus .name {
    font-weight: 650;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

/* --------------------------------------------------------------- play */

/* The session owns the viewport: the stage fills it and everything else
 * floats on top, so the picture is as large as the device can show. */
.session {
    position: fixed;
    inset: 0;
    background: #000;
    overflow: hidden;
}

.stage {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
}

canvas.screen {
    display: block;
    /* Integer scaling: the GBA is 240x160, and a fractional factor makes
     * some pixel rows a row taller than their neighbours. Round down to
     * a whole multiple of 240 and letterbox the remainder. */
    width: max(240px, round(down, min(var(--app-vw, 100dvw), var(--app-vh, 100dvh) * 1.5), 240px));
    aspect-ratio: 240 / 160;
    /* Anything but nearest-neighbour turns pixel art into soup. */
    image-rendering: pixelated;
    background: #000;
}

/* Chips along the top. Above the pad, so the L shoulder can't eat the
 * Quit button. */
.session-hud {
    position: absolute;
    top: calc(6px + var(--safe-top));
    left: 50%;
    transform: translateX(-50%);
    z-index: 7;
    display: flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    padding: 0 6px;
}

.chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 28px;
    padding: 0 10px;
    border-radius: 999px;
    background: rgba(21, 25, 34, 0.72);
    border: 1px solid var(--line);
    color: var(--dim);
    font-size: 12px;
    white-space: nowrap;
}

button.chip {
    font-weight: 600;
    color: var(--text);
}

.chip input[type="range"] {
    width: 70px;
    accent-color: var(--accent);
}

/* Playback transport. Along the bottom, where the pad isn't: a replay
 * takes no input, so the thumbs are free. */
.transport {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(10px + var(--safe-bottom));
    z-index: 7;
    display: flex;
    align-items: center;
    gap: 8px;
    width: min(560px, calc(100% - 24px));
    padding: 6px 10px;
    border-radius: 999px;
    background: rgba(21, 25, 34, 0.78);
    border: 1px solid var(--line);
}

.transport .chip {
    background: none;
    border: 0;
    padding: 0 4px;
    flex: none;
}

.transport button.chip {
    min-width: 34px;
    justify-content: center;
    font-size: 14px;
}

/* The track is drawn here rather than by the input, so it can show
 * three things at once: played, prefetched, and the rest. Shading what
 * the keyframe pass has reached is the same promise a video player's
 * buffer bar makes — a seek inside it lands at once, one past it has to
 * re-simulate its way there. */
.scrub-wrap {
    position: relative;
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    height: 28px;
}

.scrub-wrap::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    height: 4px;
    border-radius: 2px;
    background: linear-gradient(
        to right,
        var(--accent) 0 var(--played),
        var(--accent-soft) var(--played) var(--buffered),
        var(--line) var(--buffered) 100%
    );
}

.scrub {
    position: relative;
    flex: 1;
    min-width: 0;
    width: auto;
    min-height: 0;
    margin: 0;
    touch-action: none;
    /* The custom track above replaces the native one. */
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
}

.scrub::-webkit-slider-runnable-track {
    height: 4px;
    background: transparent;
}

.scrub::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 14px;
    height: 14px;
    margin-top: -5px;
    border-radius: 50%;
    background: var(--accent);
    border: 0;
}

.scrub::-moz-range-track {
    height: 4px;
    background: transparent;
}

.scrub::-moz-range-thumb {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--accent);
    border: 0;
}

/* Inert everywhere; a coarse-pointer screen (a phone or a tablet) turns
 * the pad on over the stage. A mouse gets the keyboard instead. */
.touch-controls {
    display: none;
}

@media (pointer: coarse) {
    .touch-controls {
        display: block;
        position: absolute;
        inset: 0;
        z-index: 6;
        /* Only the controls take touches; the stage between them stays
         * reachable. */
        pointer-events: none;
        touch-action: none;
        -webkit-touch-callout: none;
        color: var(--dim);
        font-size: 12px;
    }

    .touch-controls > * {
        pointer-events: auto;
        position: absolute;
        touch-action: none;
        display: flex;
        align-items: center;
        justify-content: center;
        background: rgba(36, 40, 59, 0.4);
        border: 1px solid rgba(59, 66, 97, 0.6);
        font-weight: 600;
    }

    .tc-dpad {
        left: calc(14px + var(--safe-left));
        bottom: calc(16px + var(--safe-bottom));
        /* Mirrored by DPAD_SIZE in ui/touch.rs. */
        width: 132px;
        height: 132px;
        border-radius: 50%;
    }

    /* The cross glyph. Inert — the whole circle is one control, and the
     * arms are only there to say which way is which. */
    .tc-dpad::before,
    .tc-dpad::after {
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        background: rgba(232, 236, 244, 0.1);
        border-radius: 8px;
        pointer-events: none;
    }

    .tc-dpad::before {
        width: 34px;
        height: 100px;
    }

    .tc-dpad::after {
        width: 100px;
        height: 34px;
    }

    .tc-btn {
        border-radius: 50%;
    }

    .tc-dpad.held,
    .tc-btn.held {
        background: var(--accent-soft);
        border-color: var(--accent);
        color: var(--text);
    }

    /* B lower-left of A — the GBA's own diagonal. */
    .tc-a {
        right: calc(16px + var(--safe-right));
        bottom: calc(78px + var(--safe-bottom));
        width: 58px;
        height: 58px;
    }

    .tc-b {
        right: calc(82px + var(--safe-right));
        bottom: calc(22px + var(--safe-bottom));
        width: 58px;
        height: 58px;
    }

    /* Shoulders hug the top corners, like the real thing. */
    .tc-l,
    .tc-r {
        top: 0;
        width: 92px;
        height: 32px;
    }

    .tc-l {
        left: 0;
        border-radius: 0 0 12px 0;
        border-width: 0 1px 1px 0;
    }

    .tc-r {
        right: 0;
        border-radius: 0 0 0 12px;
        border-width: 0 0 1px 1px;
    }

    .tc-pill {
        border-radius: 999px;
        height: 26px;
        padding: 0 14px;
        font-size: 10px;
        letter-spacing: 0.08em;
        text-transform: uppercase;
    }

    .tc-start {
        left: 50%;
        transform: translateX(8px);
        bottom: calc(12px + var(--safe-bottom));
    }

    .tc-select {
        left: 50%;
        transform: translateX(calc(-100% - 8px));
        bottom: calc(12px + var(--safe-bottom));
    }
}

/* A phone held upright: the session never squeezes into portrait — it
 * renders landscape and is rotated onto the tall screen, so turning the
 * phone gives an upright picture and the pad's corners land where
 * fingers rest. The touch maths survives the rotation because
 * offsetX/offsetY map through the inverse transform. */
@media (pointer: coarse) and (orientation: portrait) {
    .session {
        /* inset:0 still pins top/left; the explicit size takes over
         * from right/bottom. */
        width: var(--app-vh, 100dvh);
        height: var(--app-vw, 100dvw);
        transform-origin: top left;
        transform: rotate(90deg) translateY(calc(-1 * var(--app-vw, 100dvw)));

        /* The transform turns the box but not `env(safe-area-inset-*)`,
         * which is defined against the *device* and stays put. So every
         * inset inside here lands on the wrong edge — which is why the
         * d-pad, sitting at what is now the screen's top, was reaching
         * into the notch with the (zero) left inset applied.
         *
         * The rotation maps this box's top edge onto the device's right,
         * its left onto the device's top, and so on round. Restating the
         * variables in those terms is what makes every `var(--safe-*)`
         * below mean the edge it looks like it means. */
        --safe-top: env(safe-area-inset-right, 0px);
        --safe-right: env(safe-area-inset-bottom, 0px);
        --safe-bottom: env(safe-area-inset-left, 0px);
        --safe-left: env(safe-area-inset-top, 0px);
    }

    /* The rotated session's axes are swapped relative to the viewport,
     * so the viewport-unit sizing above swaps with them. */
    canvas.screen {
        width: max(240px, round(down, min(var(--app-vh, 100dvh), var(--app-vw, 100dvw) * 1.5), 240px));
    }
}

/* ------------------------------------------------------------ overlay */

.overlay {
    position: fixed;
    inset: 0;
    display: grid;
    place-items: center;
    background: rgba(11, 13, 18, 0.86);
    z-index: 10;
    padding: var(--pad);
    text-align: center;
}

.overlay .box {
    display: flex;
    flex-direction: column;
    gap: 12px;
    max-width: 340px;
}

.spinner {
    width: 26px;
    height: 26px;
    margin: 0 auto;
    border: 3px solid var(--line);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

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