/*
 * The session page. Three bands: the player stuck under the header, the channels
 * scrolling between, and the room as a bar stuck to the bottom that opens upward.
 */


/* --- page ------------------------------------------------------------------- */

/* Pinch and double-tap zoom off: iOS ignores user-scalable=no, and a stray zoom
   strands the sliders off-screen. Scrolling is left alone. */
html {
    touch-action: pan-x pan-y;
}

main.session {
    display: flex;
    flex-direction: column;
    max-width: none;
    min-height: calc(100dvh - var(--header));
    padding: 0;

    > .channels {
        width: 100%;
        max-width: var(--frame);
        margin-left: auto;
        margin-right: auto;
        padding-left: var(--frame-padding);
        padding-right: var(--frame-padding);
    }

    @media (width < 52rem) {
        > .channels {
            padding-left: var(--gutter);
            padding-right: var(--gutter);
        }
    }
}

fieldset {
    margin: 0;
    padding: 0;
    border: 0;
    min-width: 0;
}

.letter {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    height: 28px;
    padding: 0 .4rem;
    border-radius: var(--radius);
    background: var(--accent-soft);
    color: var(--accent);
    font-size: .85rem;
    font-weight: 700;
}

.row {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
    font-size: .9rem;
    color: var(--muted);

    input[type=number] {
        width: 4.5rem;
        height: 32px;
    }
}

/* A slider with its name and its value on one line above it. */
.field {
    display: flex;
    flex-direction: column;
    gap: .15rem;
    margin: 0;

    .name {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        font-size: .85rem;
        color: var(--muted);
    }

    .value {
        font-family: var(--mono);
        font-size: .85rem;
        color: var(--text);
        font-variant-numeric: tabular-nums;
    }

    &.row {
        flex-direction: row;
        align-items: center;
        gap: .75rem;

        .name {
            flex: none;
            width: 5rem;
        }
    }
}

.pending-note {
    font-size: .8rem;
    color: var(--muted);
}


/* --- player ----------------------------------------------------------------- */

.player {
    position: sticky;
    top: var(--header);
    z-index: 5;
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: .75rem 2rem 1rem;
    background: var(--card);
    border-bottom: 1px solid var(--edge);

    @media (width < 52rem) {
        gap: .5rem;
        padding: .5rem var(--gutter);
    }
}

.transport {
    display: flex;
    align-items: center;
    gap: .75rem;

    #transport,
    .record {
        min-width: 9.5rem;
    }

    #transport {
        svg {
            width: 14px;
            height: 14px;
            fill: currentColor;
        }

        .stop {
            display: none;
        }

        &[data-state=running] {
            background: var(--card);
            border-color: var(--edge);
            color: var(--text);

            .play {
                display: none;
            }

            .stop {
                display: block;
                color: var(--accent);
            }
        }
    }

    .gain {
        display: flex;
        align-items: center;
        gap: .75rem;
        width: 18rem;
        font-size: .85rem;
        color: var(--muted);

        input[type=range] {
            flex: 1;
            margin: 0;
        }

        .value {
            width: 3.2rem;
            text-align: right;
            font-family: var(--mono);
            color: var(--text);
        }
    }

    /* The record button and the time it has been running, behind a divider from the
       transport. Gone, divider and all, while neither shows. */
    .recording {
        display: flex;
        align-items: center;
        gap: .75rem;
        margin-left: .75rem;
        padding-left: 1.5rem;
        border-left: 1px solid var(--edge);

        &:not(:has(> :not([hidden]))) {
            display: none;
        }
    }

    /* The dot is the one thing that changes: grey at rest, the accent colour while
       recording. */
    .record {
        display: flex;
        align-items: center;
        gap: .5rem;

        .dot {
            width: .6rem;
            height: .6rem;
            border-radius: 50%;
            background: var(--muted);
        }

        &[data-state=recording] .dot {
            background: var(--accent);
            animation: recording-blink 1.2s ease-in-out infinite;
        }

        @media (prefers-reduced-motion: reduce) {
            &[data-state=recording] .dot {
                animation: none;
            }
        }
    }

    .elapsed {
        font-family: var(--mono);
        font-size: .85rem;
        color: var(--text);
    }

    /* The device control: a menu button, or the device's name and its strength. The
       menu drops below the button, over the timeline. */
    .device {
        display: flex;
        align-items: center;
        gap: .75rem;
        font-size: .85rem;
        color: var(--muted);


        .choose {
            position: relative;
        }

        .menu {
            position: absolute;
            top: calc(100% + .25rem);
            right: 0;
            z-index: 2;
            display: flex;
            flex-direction: column;
            min-width: 10rem;
            padding: .25rem;
            background: var(--card);
            border: 1px solid var(--edge);
            border-radius: var(--radius);
            box-shadow: 0 8px 24px rgb(0 0 0 / .35);


            button {
                justify-content: flex-start;
                background: none;
                border: none;
            }
        }

        .connected {
            display: flex;
            align-items: center;
            gap: .75rem;

        }

        .battery,
        .knobs {
            font-family: var(--mono);
            color: var(--muted);
        }

        .level {
            display: flex;
            align-items: center;
            gap: .75rem;
            width: 12rem;


            input[type=range] {
                flex: 1;
                margin: 0;
            }

            .value {
                width: 3.2rem;
                text-align: right;
                font-family: var(--mono);
                color: var(--text);
            }
        }

        .note:empty {
            display: none;
        }
    }

    @media (width < 52rem) {
        flex-wrap: wrap;

        #transport {
            flex: 1;
        }

        .spacer {
            display: none;
        }

        .gain,
        .device {
            width: 100%;
        }

        .device .connected,
        .device .level {
            flex: 1;
        }
    }
}

/* The device's settings, grown under the strip on a class, as the library drawer is. */
.device-drawer {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows .35s ease;

    &.open {
        grid-template-rows: 1fr;
    }

    > .device-settings {
        min-height: 0;
        overflow: hidden;
    }

    .coyote {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: .5rem 2rem;
        padding-bottom: .75rem;

        @media (width < 52rem) {
            grid-template-columns: minmax(0, 1fr);
        }
    }

    .two-b {
        padding-bottom: .75rem;
        font-size: .85rem;
        color: var(--muted);

        p {
            margin: 0 0 .25rem;
        }

        output {
            font-family: var(--mono);
            color: var(--text);
        }
    }

    .start-ramp {
        max-width: 24rem;
        padding-bottom: .75rem;

        p {
            margin: .25rem 0 0;
            font-size: .85rem;
            color: var(--muted);
        }
    }

    .swap {
        display: flex;
        align-items: center;
        gap: .4rem;
    }

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

@keyframes recording-blink {
    50% { opacity: .35; }
}

/* Stacked canvases: Canvas2D grid underneath, WebGL bars on top. Both are sized by
   CSS and scaled to device pixels in JS. Shorter on a phone, where the strip would
   otherwise take a third of the screen before any control appears. */
.timeline {
    position: relative;
    flex: none;
    width: 100%;
    height: 200px;
    border: 1px solid var(--edge);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--inset);

    canvas {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        display: block;
    }

    /* Why there is no sound, dimming the bars it stands in for. Clicks pass through
       to whatever the timeline does under it. */
    .engine {
        position: absolute;
        inset: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 1rem;
        background: rgb(0 0 0 / .55);
        color: #ffffff;
        font-size: .9rem;
        font-weight: 600;
        text-align: center;
        pointer-events: none;

        &:empty {
            display: none;
        }
    }

    @media (width < 52rem) {
        height: 96px;
    }
}


/* --- channels --------------------------------------------------------------- */

.channels {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.5rem;
    align-items: start;
    padding-top: 1.5rem;
    padding-bottom: 1.5rem;

    .tabs {
        display: none;
    }
}

.channel {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 1.25rem;
    background: var(--card);
    border: 1px solid var(--edge);
    border-radius: var(--radius);

    /* Names the panel for a reader; the bar shows the name. */
    > legend {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }
}

/* On a narrow screen the radios above the panels pick which one shows. The tab row
   and the panel share one border so they read as one card. */
@media (width < 52rem) {
    .channels {
        grid-template-columns: 1fr;
        gap: 0;
        padding-top: 1rem;

        .tabs {
            display: flex;
            background: var(--card);
            border: 1px solid var(--edge);
            border-bottom: 0;
            border-radius: var(--radius) var(--radius) 0 0;

            input {
                position: absolute;
                opacity: 0;
                pointer-events: none;
            }

            label {
                display: inline-flex;
                align-items: center;
                justify-content: center;
                gap: .5rem;
                flex: 1;
                height: 46px;
                margin: 0;
                font-size: .95rem;
                font-weight: 600;
                color: var(--muted);
                cursor: pointer;
            }

            input:checked + label {
                color: #ffffff;
                box-shadow: inset 0 -2px 0 var(--accent);
            }

            input:focus-visible + label {
                outline: 2px solid var(--accent);
                outline-offset: -2px;
            }

            .letter {
                min-width: 22px;
                height: 22px;
                font-size: .75rem;
            }
        }

        .channel {
            padding: 1rem;
            border-radius: 0 0 var(--radius) var(--radius);
        }

        /* The tab above already says which channel this is. */
        .channel > .bar > .letter,
        .channel > .bar > .name {
            display: none;
        }

        &:has(#tab-left:checked) [data-channel=right],
        &:has(#tab-right:checked) [data-channel=left] {
            display: none;
        }
    }
}

/* A follower's panels: not theirs to move, and still the most interesting thing on
   the page. They keep tracking whatever the driver is doing, so the treatment dims
   the affordances and leaves every readout at full strength. */
fieldset:disabled {
    opacity: 1;

    input,
    button,
    summary {
        opacity: .45;
        cursor: default;
    }

    /* Disabling a fieldset leaves its disclosures working; a locked panel's ramp is
       not opened from here either. */
    summary {
        pointer-events: none;
    }

    output,
    .figures,
    .kind,
    .hint {
        opacity: 1;
    }
}

/* A control a routine has taken over: still readable, not reachable. */
.channels [inert] {
    opacity: .45;
}

.group {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding-top: 1rem;
    border-top: 1px solid var(--edge);
}

/* The first group sits under the head's edge, which already divides them. The lock
   line between the two is in the markup always, shown or not. */
.sweep + .lock + .group {
    padding-top: 0;
    border-top: 0;
}

.group-head {
    display: flex;
    align-items: center;
    gap: .5rem;

    .count {
        margin-left: auto;
    }

    h3 {
        margin: 0;
        font-size: .7rem;
        font-weight: 600;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--muted);
    }
}

.count {
    font-family: var(--mono);
    font-size: .75rem;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

.pulse-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .25rem 1rem;
}

.add {
    width: 100%;
    height: 36px;
    font-size: .85rem;
    color: var(--muted);
    background: transparent;
    border-style: dashed;
    border-color: var(--track);

    &:hover:not(:disabled) {
        color: var(--text);
    }
}

/* The channel head's lower edge, always there and always this height, with a fade in
   flight drawn across it. Width is set from the engine's own frames. */
.sweep {
    flex: none;
    height: 2px;
    overflow: hidden;
    background: var(--edge);

    &::after {
        content: "";
        display: block;
        height: 100%;
        width: var(--progress, 0%);
        background: var(--accent);
    }
}


/* --- the carrier ------------------------------------------------------------ */

/* Why the panel is dimmed. Outside the dimmed controls, so it reads at full strength. */
.lock {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: 0;
    padding: .5rem .75rem;
    font-size: .85rem;
    color: var(--muted);
    background: var(--inset);
    border: 1px solid var(--edge);
    border-radius: var(--radius);

    &::before {
        content: "";
        width: 7px;
        height: 7px;
        border-radius: 50%;
        background: var(--ok);
    }
}

/* Radios drawn as the shapes they stand for. The input is kept for the keyboard and
   the script; the label is what shows. */
.waveforms {
    display: flex;
    gap: 6px;

    /* The label is the control; the radio is kept for the keyboard and clipped away,
       which the locked panel's dimming cannot undo as it would an opacity. */
    input {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: 0;
        overflow: hidden;
        clip-path: inset(50%);
        pointer-events: none;
    }

    label {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        flex: 1 1 0;
        height: 40px;
        margin: 0;
        color: var(--muted);
        background: var(--card);
        border: 1px solid var(--edge);
        border-radius: var(--radius);
        cursor: pointer;

        svg {
            width: 36px;
            height: 18px;
            fill: none;
            stroke: currentColor;
            stroke-width: 1.5;
            stroke-linejoin: round;
        }
    }

    input:checked + label {
        color: var(--accent);
        background: rgb(255 0 53 / .10);
        border-color: var(--accent);
    }

    input:focus-visible + label {
        outline: 2px solid var(--accent);
        outline-offset: 1px;
    }

    input:disabled + label {
        opacity: .45;
        cursor: default;
    }
}

/* A readout that takes typing: the value with a quiet underline, not a box. */
.value input {
    width: 4.5em;
    height: 22px;
    padding: 0 2px;
    font: inherit;
    font-family: var(--mono);
    color: var(--text);
    text-align: right;
    background: transparent;
    border: 0;
    border-bottom: 1px dotted var(--track);
    border-radius: 0;

    &:focus-visible {
        outline: 0;
        border-bottom-color: var(--accent);
    }
}

/* The frequency slider's scale, since a log track gives no clue where 200 Hz is. */
.ticks {
    position: relative;
    display: block;
    height: 14px;
    margin-top: -2px;

    span {
        position: absolute;
        transform: translateX(-50%);
        font-family: var(--mono);
        font-size: 10px;
        color: var(--faint);

        &:first-child {
            transform: none;
        }

        &:last-child {
            transform: translateX(-100%);
        }
    }
}

/* The amplitude field is a grid so the ramp's row can sit under the slider while its
   summary sits beside the label: the details element spreads its two parts into the
   grid with display: contents. */
.amplitude {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    column-gap: .6rem;
    row-gap: 4px;

    > .label {
        grid-area: 1 / 1;
        font-size: .85rem;
        color: var(--muted);
    }

    > .value {
        grid-area: 1 / 3;
        font-family: var(--mono);
        font-size: .85rem;
        color: var(--text);
    }

    > input[type=range] {
        grid-column: 1 / -1;
        grid-row: 2;
    }

    .ramp {
        display: contents;

        > summary {
            grid-area: 1 / 2;
            justify-self: start;
        }

        > .row {
            grid-column: 1 / -1;
            grid-row: 3;
        }
    }
}

/* A ramp is a disclosure beside what it moves: a small summary, and its row laid out
   by the grid it sits in. The row sits in the details' own content box, which has to
   step aside as well, or the row is placed as that box: in the grid's first cell.
   That box is also what hides a closed disclosure, so the row is hidden here instead. */
.ramp {
    &::details-content {
        display: contents;
    }

    &:not([open]) > .row {
        display: none;
    }

    > summary {
        display: inline-flex;
        align-items: center;
        gap: 4px;
        height: 22px;
        padding: 0 6px;
        list-style: none;
        font-size: 11px;
        font-weight: 600;
        letter-spacing: .04em;
        text-transform: uppercase;
        color: var(--muted);
        border: 1px solid var(--edge);
        border-radius: var(--radius);
        cursor: pointer;

        &::-webkit-details-marker {
            display: none;
        }

        &::after {
            content: "";
            width: 5px;
            height: 5px;
            border-right: 1.5px solid currentColor;
            border-bottom: 1.5px solid currentColor;
            transform: translateY(-1px) rotate(45deg);
        }

        &:hover {
            color: var(--text);
        }
    }

    &[open] > summary {
        color: var(--text);
        border-color: var(--track);
    }

    > .row {
        margin-top: 6px;
        padding: 10px 12px;
        border: 1px dashed var(--track);
        border-radius: var(--radius);
    }
}

/* Four sliders, one envelope, drawn beside them. */
.pulse {
    display: flex;
    gap: 14px;
    align-items: flex-start;

    .preview {
        flex: none;
        color: var(--accent);
        background: var(--inset);
        border: 1px solid var(--edge);
        border-radius: var(--radius);
    }

    .pulse-grid {
        flex: 1;
        min-width: 0;
    }
}

/* Something waits for Apply: the button turns red and the box gets an edge, so pending
   reads from across the room rather than in small grey. */
.channel [data-action=apply]:not(:disabled) {
    color: var(--bg);
    background: var(--accent);
    border-color: var(--accent);
    font-weight: 600;
}

.slot:has([data-action=apply]:not(:disabled)) {
    border-color: rgb(255 0 53 / .45);
    box-shadow: inset 2px 0 0 var(--accent);
}

.pending-note:not(:empty) {
    color: var(--accent);
}


/* --- modulation slots ------------------------------------------------------- */

/* A modulation collapsed to one line. Shape, rate and depth are the whole of one,
   so the summary loses nothing; opening it gives handles, not information. */
.slot {
    background: var(--inset);
    border: 1px solid var(--edge);
    border-radius: var(--radius);

    > summary {
        display: flex;
        align-items: center;
        gap: .75rem;
        height: 40px;
        padding: 0 .5rem 0 .75rem;
        list-style: none;
        cursor: pointer;

        &::-webkit-details-marker {
            display: none;
        }
    }

    &[open] > summary {
        border-bottom: 1px solid var(--edge);
    }

    /* The shape is stroked in the canvas's own colour, so this is what paints it. */
    canvas {
        display: block;
        flex: none;
        color: var(--accent);
    }

    .figures {
        font-family: var(--mono);
        font-size: .85rem;
        font-variant-numeric: tabular-nums;
    }

    .kind {
        font-family: var(--mono);
        font-size: .85rem;
        color: var(--muted);
        font-variant-numeric: tabular-nums;
    }

    .body {
        display: flex;
        flex-direction: column;
        gap: .6rem;
        padding: .75rem;
    }

    .waveforms label {
        height: 32px;
    }
}

.remove {
    width: 28px;
    height: 28px;
    padding: 0;
    font-size: 1.05rem;
    line-height: 1;
    color: var(--muted);
    background: none;
    border: 0;

    &:hover:not(:disabled) {
        background: var(--edge);
        color: var(--text);
    }
}


/* --- both channels ---------------------------------------------------------- */

/* Bordered boxes above and below the panels, so their padding is their own: the page
   gutter comes off their width instead, which keeps them flush with the panels at
   every size. */
main.session > .stereo,
main.session > .stereo-ramp {
    flex: none;
    width: calc(100% - 2 * var(--frame-padding));
    max-width: calc(var(--frame) - 2 * var(--frame-padding));
    margin-left: auto;
    margin-right: auto;
    padding: .75rem 1.25rem;
    background: var(--card);
    border: 1px solid var(--edge);
    border-radius: var(--radius);

    @media (width < 52rem) {
        width: calc(100% - 2 * var(--gutter));
        padding: .75rem 1rem;
    }
}

/* What holds both channels and the way out of it, and the library, which opens under
   them inside the block. */
main.session > .stereo {
    margin-top: 1.5rem;

    @media (width < 52rem) {
        margin-top: 1rem;
    }
}

/* The head of the L+R block and of each panel, one shape for all three: which
   channels, what holds them and the way out of it, and the library's button. */
.stereo > .bar,
.channel > .bar {
    display: flex;
    align-items: center;
    gap: .75rem;
    min-height: 32px;

    > .name {
        font-size: 1.05rem;
        font-weight: 600;
    }

    .now {
        flex: 1;
        display: flex;
        align-items: center;
        gap: .6rem;
        min-width: 0;
    }

    .state {
        min-width: 0;
        font-size: .9rem;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
}

/* The ramp over both amplitude sliders, under the panels it moves. */
main.session > .stereo-ramp {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .5rem;
    margin-bottom: 2rem;
    font-size: .9rem;
    color: var(--muted);

    > .letter {
        margin-right: .25rem;
    }

    input[type=number] {
        width: 4.5rem;
        height: 32px;
    }
}

/* A button that opens a library and closes it again. Its chevron turns with the
   drawer, at the drawer's pace, and the button is filled while the library is open. */
[data-action=openLibrary] {
    &::after {
        content: "";
        width: 6px;
        height: 6px;
        border-right: 1.5px solid currentColor;
        border-bottom: 1.5px solid currentColor;
        transform: translateY(-2px) rotate(45deg);
        transition: transform .35s ease;
    }

    &[aria-expanded=true] {
        color: #ffffff;
        background: var(--track);
        border-color: var(--track);

        &::after {
            transform: translateY(1px) rotate(225deg);
        }
    }
}


/* --- the library ------------------------------------------------------------ */

/* Grows from nothing to its content, as the room drawer does: a grid whose one row
   animates, so nothing has to know the content's height. session.js fills it before
   it opens and empties it once it has closed. */
.library-drawer {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows .35s ease;

    /* Empty, it is no item at all, so a panel spacing its items by a gap does not
       space this one too: what follows sits one gap under the bar, not two. */
    &:not(:has(*)) {
        display: none;
    }

    &.open {
        grid-template-rows: 1fr;
    }

    > .picker {
        min-height: 0;
        overflow: hidden;
    }
}

/* Inside the panel that opened it, so set off by a rule rather than a frame. The outline
   of a focused card needs room inside the clip. */
.picker {
    display: flex;
    flex-direction: column;
    gap: 1rem;

    &::before {
        content: "";
        display: block;
        margin-top: .75rem;
        border-top: 1px solid var(--edge);
    }

    .hint {
        margin: 0;
        font-size: .85rem;
        color: var(--muted);
    }

    .picks {
        padding: 3px;
        margin: -3px;
    }
}

.picker-bar {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-wrap: wrap;
}

.kinds {
    display: flex;
    gap: .5rem;
    flex-wrap: wrap;

    a {
        display: inline-flex;
        align-items: center;
        gap: .4rem;
        height: 34px;
        padding: 0 .9rem;
        font-size: .9rem;
        font-weight: 500;
        text-decoration: none;
        color: var(--muted);
        border: 1px solid var(--edge);
        border-radius: var(--radius);

        &[aria-current] {
            font-weight: 600;
            color: #ffffff;
            background: var(--track);
            border-color: var(--track);
        }
    }

    .count {
        font-size: .7rem;
    }
}

.picker .search {
    display: flex;
    align-items: center;
    gap: .5rem;
    width: 16rem;
    max-width: 100%;
    height: 34px;
    margin-left: auto;
    padding: 0 .75rem;
    background: var(--inset);
    border: 1px solid var(--edge);
    border-radius: var(--radius);
    color: var(--muted);

    &:focus-within {
        outline: 2px solid var(--accent);
        outline-offset: 1px;
    }

    svg {
        flex: none;
        width: 16px;
        height: 16px;
        fill: none;
        stroke: currentColor;
        stroke-width: 2;
    }

    input {
        flex: 1;
        min-width: 0;
        height: 30px;
        font: inherit;
        color: var(--text);
        background: none;
        border: 0;
        outline: none;
    }

    @media (width < 52rem) {
        width: 100%;
        margin-left: 0;
    }
}

.picker .empty {
    margin: 0;
    color: var(--muted);
}

.picker .shelf {
    display: flex;
    flex-direction: column;
    gap: .6rem;

    h3 {
        margin: 0;
        font-size: .7rem;
        font-weight: 600;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--muted);
    }
}

.picks {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: .75rem;

    @media (width < 52rem) {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: .6rem;
    }
}

/* One item. The whole card is the button: picking is the only thing to do with it. */
.pick {
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    height: auto;
    min-width: 0;
    padding: 0;
    text-align: left;
    overflow: hidden;

    &:focus-visible {
        outline: 2px solid var(--accent);
        outline-offset: 1px;
    }

    .art {
        display: flex;
        align-items: center;
        justify-content: center;
        aspect-ratio: 5;
        background: var(--inset);
        border-bottom: 1px solid var(--edge);
        color: var(--track);

        img {
            display: block;
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        svg {
            width: 24px;
            height: 24px;
            fill: none;
            stroke: currentColor;
            stroke-width: 2;
            stroke-linecap: round;
            stroke-linejoin: round;
        }
    }

    .body {
        display: flex;
        flex-direction: column;
        gap: .15rem;
        padding: .55rem .7rem;
    }

    .title {
        display: flex;
        align-items: center;
        gap: .4rem;
        min-width: 0;
    }

    .name {
        flex: 1;
        min-width: 0;
        font-size: .9rem;
        font-weight: 600;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .scope {
        flex: none;
        height: 18px;
        padding: 0 .4rem;
        font-size: .65rem;
        font-weight: 600;
        line-height: 16px;
        color: var(--muted);
        background: var(--inset);
        border: 1px solid var(--edge);
        border-radius: var(--radius);
    }

    .summary {
        font-size: .8rem;
        font-weight: 400;
        color: var(--muted);
    }
}


/* --- room bar --------------------------------------------------------------- */

/* Laid out bottom-up: the strip is the bar, the drawer stacks above it. Sticky to the
   bottom of the scrolling page, and pushed there by the page's own margin when the
   content is shorter than the screen. The drawer is a grid whose one row goes from
   nothing to its content, which is what animates height without knowing it. */
.room-bar {
    position: sticky;
    bottom: 0;
    z-index: 6;
    display: flex;
    flex-direction: column-reverse;
    margin-top: auto;
    background: var(--bar);
    border-top: 1px solid var(--edge);
    padding-bottom: env(safe-area-inset-bottom);

    > .strip {
        display: flex;
        align-items: center;
        gap: .6rem;
        width: 100%;
        height: 44px;
        padding: 0 1.5rem 0 2rem;
        border: 0;
        border-radius: 0;
        background: none;
        font-size: .85rem;
        font-weight: 400;
        text-align: left;
        cursor: pointer;

        &:hover:not(:disabled) {
            color: var(--text);
        }

        .dot {
            flex: none;
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: var(--track);
        }

        .idle {
            font-weight: 600;
        }

        .detail {
            margin-left: .4rem;
            font-weight: 400;
            color: var(--muted);
        }

        .note {
            min-width: 0;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;

            &[data-tone=notice] {
                font-weight: 600;
            }

            &[data-tone=error] {
                color: var(--accent);
            }
        }

        .live {
            display: flex;
            align-items: center;
            gap: .6rem;
            min-width: 0;

            strong {
                font-family: var(--mono);
                letter-spacing: .04em;
            }

            [data-room=roomStatus] {
                color: var(--muted);
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
            }
        }

        .chevron {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: 44px;
            height: 32px;
            border: 1px solid var(--edge);
            border-radius: var(--radius);
            background: var(--card);

            svg {
                width: 16px;
                height: 16px;
                fill: none;
                stroke: currentColor;
                stroke-width: 2;
                stroke-linecap: round;
                stroke-linejoin: round;
                transition: transform .35s ease;
            }
        }

        @media (width < 52rem) {
            height: 40px;
            padding: 0 .75rem 0 var(--gutter);
        }
    }

    &.open > .strip .chevron svg {
        transform: rotate(180deg);
    }

    /* In a room: the join region is hidden and the live span shown, by the script.
       The bar reads that off the markup rather than being told twice. */
    &:has([data-room=roomJoin][hidden]) > .strip {
        .idle {
            display: none;
        }

        .dot {
            background: var(--ok);
        }
    }

    /* Dims the page behind the sheet. A child of the bar so it stacks under the bar's
       content and over everything else, with no script placing it. */
    &::before {
        content: "";
        position: fixed;
        inset: var(--header) 0 0;
        z-index: -1;
        background: rgb(0 0 0 / .55);
        opacity: 0;
        visibility: hidden;
        transition: opacity .35s ease, visibility 0s linear .35s;
    }

    &.open::before {
        opacity: 1;
        visibility: visible;
        transition: opacity .35s ease;
    }
}

.drawer {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows .35s ease;

    .room-bar.open & {
        grid-template-rows: 1fr;
    }
}

/* No padding on the sheet itself: a collapsed grid row is zero tall, and padding
   would still stand. The inner box carries it and scrolls once the sheet has grown
   to the player strip. */
.sheet {
    min-height: 0;
    overflow: hidden;
    background: var(--card);
    border-bottom: 1px solid var(--edge);
    box-shadow: 0 -16px 40px rgb(0 0 0 / .45);

    .inner {
        max-width: var(--frame);
        max-height: calc(100dvh - var(--header) - 44px - 12rem);
        margin: 0 auto;
        padding: 1.25rem var(--frame-padding) 1.5rem;
        overflow-y: auto;
    }

    h3 {
        margin: 0;
        font-size: .7rem;
        font-weight: 600;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--muted);
    }

    @media (width < 52rem) {
        .inner {
            padding: 1rem var(--gutter);
        }
    }
}

@media (prefers-reduced-motion: reduce) {
    .drawer,
    .room-bar::before,
    .room-bar > .strip .chevron svg,
    .library-drawer,
    [data-action=openLibrary]::after {
        transition: none;
    }
}

/* Not in a room: the two ways in, side by side, and the device's name under both. */
.offers {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .75rem;

    .offer {
        display: flex;
        flex-direction: column;
        gap: .5rem;
        padding: 1rem;
        background: var(--inset);
        border: 1px solid var(--edge);
        border-radius: var(--radius);

        h3 {
            font-size: .95rem;
            font-weight: 600;
            letter-spacing: 0;
            text-transform: none;
            color: var(--text);
        }

        p {
            margin: 0;
            font-size: .85rem;
            line-height: 1.45;
            color: var(--muted);
        }

        .row {
            margin-top: .25rem;
        }

        .code {
            flex: 1;
            min-width: 6rem;
            font-family: var(--mono);
            letter-spacing: .08em;
        }
    }

    .device {
        grid-column: 1 / -1;
        display: flex;
        align-items: center;
        gap: .6rem;
        margin: 0;
        font-size: .85rem;
        color: var(--muted);

        input {
            height: 32px;
        }
    }

    @media (width < 52rem) {
        grid-template-columns: 1fr;
    }
}

.request {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
    padding: .6rem .75rem;
    font-size: .85rem;
    background: var(--accent-soft);
    border: 1px solid rgb(255 0 53 / .35);
    border-radius: var(--radius);

    span {
        flex: 1;
    }
}

.room-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
    gap: 2rem;
    align-items: start;

    /* Chat gone, the people take the width. */
    &:has(.chat[hidden]) {
        grid-template-columns: 1fr;
    }

    @media (width < 52rem) {
        grid-template-columns: 1fr;
        gap: 1.25rem;
    }
}

.people {
    .head {
        display: flex;
        align-items: center;
        gap: .5rem;
    }

    ul {
        list-style: none;
        margin: .5rem 0 0;
        padding: 0;
    }

    li {
        display: flex;
        align-items: center;
        gap: .5rem;
        min-height: 44px;
        border-top: 1px solid var(--edge);
        font-size: .9rem;
    }

    /* Dot, name and handle on one line; the device label, when there is one, under. */
    .who {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 1px .4rem;
        min-width: 0;
        margin-right: .25rem;
    }

    .dot {
        width: 8px;
        height: 8px;
        flex: none;
        border-radius: 50%;
        background: var(--colour, var(--track));
    }

    .handle {
        font-size: .75rem;
        color: var(--muted);
    }

    .device {
        flex-basis: 100%;
        padding-left: calc(8px + .4rem);
        font-size: .75rem;
        color: var(--muted);
    }

    /* The holder's actions on one row, behind one button. */
    .menu {
        margin-left: auto;

        > summary {
            width: 32px;
            height: 32px;
            border: 1px solid var(--edge);
            background: transparent;
            color: var(--muted);
            font-weight: 700;
            letter-spacing: -.1em;
        }
    }
}

.chat {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.chat-log {
    height: 8rem;
    margin: 0;
    padding: .5rem .75rem;
    list-style: none;
    overflow-y: auto;
    font-size: .85rem;
    background: var(--inset);
    border: 1px solid var(--edge);
    border-radius: var(--radius);

    li {
        padding: .1rem 0;
    }
}

.chat [data-room=chatMessage] {
    flex: 1;
    min-width: 0;
}



/* --- calibration ------------------------------------------------------------ */

/* The Coyote's calibration, one step at a time over the dimmed page. */
dialog.calibration {
    width: min(30rem, calc(100vw - 2 * var(--gutter)));
    padding: 1.25rem;
    color: var(--text);
    background: var(--card);
    border: 1px solid var(--edge);
    border-radius: var(--radius);

    &::backdrop {
        background: rgb(0 0 0 / .6);
    }

    &[open] {
        display: flex;
        flex-direction: column;
        gap: 1rem;
    }

    header {
        display: flex;
        align-items: flex-start;
        justify-content: space-between;
        gap: 1rem;
    }

    h2 {
        margin: .15rem 0 0;
        font-size: 1.05rem;
    }

    .progress {
        font-size: .7rem;
        font-weight: 600;
        letter-spacing: .08em;
        text-transform: uppercase;
        color: var(--muted);
    }

    section {
        display: flex;
        flex-direction: column;
        gap: .75rem;
    }

    label:has(> input[type=radio]),
    .row > label:has(> input[type=number]) {
        display: flex;
        align-items: center;
        gap: .35rem;
    }

    .hint {
        margin: 0;
        font-size: .85rem;
        color: var(--muted);
    }
}
