*,
*::before,
*::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}
html, body{
    max-width: 100%;
    overflow-x: hidden;
    /* The page still scrolls, its bar just isn't drawn. Not inherited, so the
       chat log keeps its own scrollbar — that one is the only hint the backlog
       goes further up. */
    scrollbar-width: none;
    -ms-overflow-style: none;
}

html::-webkit-scrollbar,
body::-webkit-scrollbar {
    display: none;
}
:root {
    /* Height of the strip the fixed account bar sits in. Page content is padded
       clear of it, so the bar never lands on the player or the chat head. */
    --user-bar-height: 3rem;
}

body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* `safe` matters: plain centring pushes the overflow of a too-tall page off
       the top edge, where it can't be scrolled back to. */
    justify-content: safe center;
    background: #ffffff;
    color: #1f3347;
    font-family: 'Segoe UI', system-ui, sans-serif;
}

/* The opening screen: the player, the title and the search bar have the first
   viewport to themselves, so the grid and the links start one scroll down. */
.hero {
    display: flex;
    flex-direction: column;
    align-items: center;
    /* `safe` for the same reason the body uses it: on a viewport too short for
       the player, the top has to stay reachable instead of centring off it. */
    justify-content: safe center;
    width: 100%;
    min-height: calc(100dvh - var(--user-bar-height));
}

/* Keep that screen whole on short displays. The player's height follows its
   width, so cap the width by whatever height is left once the header and the
   margins have taken their share — the 356px the chat column and the row's gap
   occupy doesn't scale with the video, so it comes off the top. */
@media (min-width: 861px) {
    .hero .live {
        width: min(1400px, 94vw, calc(356px + (100dvh - var(--user-bar-height) - 9rem) * 16 / 9));
    }
}

header {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 1rem;
    margin-bottom: 2rem;
}

header h1 {
    font-size: 1.6rem;
    font-weight: 400;
    letter-spacing: 0.08em;
    color: #5b7a94;
    white-space: nowrap;
    flex-shrink: 0;
}

header h1 span {
    color: #1f3347;
}

form {
    color: #5b7a94;
    text-align: center;
}

.grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 1rem;
    width: min(600px, 90vw);
    align-items: center;
}

a.card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    padding: 1.8rem 1rem;
    border: 1px solid #bfe0f5;
    border-radius: 10px;
    background: #f7fbfe;
    text-decoration: none;
    color: inherit;
    transition: border-color 0.2s, background 0.2s, transform 0.15s;
}

a.card:hover {
    border-color: #89cff0;
    background: #e8f4fc;
    transform: translateY(-2px);
}

a.card svg,
a.card img {
    width: 32px;
    height: 32px;
    opacity: 0.75;
}

a.card span {
    font-size: 0.95rem;
    letter-spacing: 0.04em;
}

footer {
    margin-top: 1.5em;
    font-size: 0.75rem;
    color: #8fb3cf;
}

body,
.search-bar {
    display: flex;
}

/* The second body rule, and the later one, so the page's padding is settled
   here: the top is the strip the fixed account bar occupies. */
body {
    background: #ffffff;
    /* min-height, not height: the player makes the page taller than the
       viewport on short screens, and a fixed height would clip it. */
    min-height: 100vh;
    padding: var(--user-bar-height) 0 2rem;
}

.search-bar input,
.search-btn,
.search-btn:before,
.search-btn:after {
    transition: all 0.25s ease-out;
}

.search-bar input,
.search-btn {
    width: 3em;
    height: 3em;
    /* Without this both halves keep the browser's default grey chrome, which
       reads as a hard outline around the field and doubles up at the seam
       between the two. The ring below is the only border they should have. */
    border: none;
}

.search-bar input:invalid:not(:focus),
.search-btn {
    cursor: pointer;
}

.search-bar,
.search-bar input:focus,
.search-bar input:valid {
    width: 100%;
}

.search-bar input:focus,
.search-bar input:not(:focus)+.search-btn:focus {
    outline: transparent;
}

.search-bar {
    padding: 0;
    justify-content: center;
    max-width: 20em;
}

.search-bar input {
    background: transparent;
    border-radius: 1.5em;
    box-shadow: 0 0 0 0.4em #89cff0 inset;
    padding: 0.75em;
    transform: translate(0.5em, 0.5em) scale(0.5);
    transform-origin: 100% 0;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    font-family: inherit;
}

.search-bar input::-webkit-search-decoration {
    -webkit-appearance: none;
}

.search-bar input:focus,
.search-bar input:valid {
    background: #fff;
    border-radius: 0.375em 0 0 0.375em;
    box-shadow: 0 0 0 0.1em #bfe0f5 inset;
    transform: scale(1);
}

.search-btn {
    background: #89cff0;
    border-radius: 0 0.75em 0.75em 0 / 0 1.5em 1.5em 0;
    padding: 0.75em;
    position: relative;
    transform: translate(0.25em, 0.25em) rotate(45deg) scale(0.25, 0.125);
    transform-origin: 0 50%;
}

.search-btn:before,
.search-btn:after {
    content: "";
    display: block;
    opacity: 0;
    position: absolute;
}

.search-btn:before {
    border-radius: 50%;
    box-shadow: 0 0 0 0.2em #ffffff inset;
    top: 0.75em;
    left: 0.75em;
    width: 1.2em;
    height: 1.2em;
}

.search-btn:after {
    background: #ffffff;
    border-radius: 0 0.25em 0.25em 0;
    top: 51%;
    left: 51%;
    width: 0.75em;
    height: 0.25em;
    transform: translate(0.2em, 0) rotate(45deg);
    transform-origin: 0 50%;
}

.search-btn span {
    display: inline-block;
    overflow: hidden;
    width: 1px;
    height: 1px;
}

/* Active state */
.search-bar input:focus+.search-btn,
.search-bar input:valid+.search-btn {
    background: #56aede;
    border-radius: 0 0.375em 0.375em 0;
    transform: scale(1);
}

.search-bar input:focus+.search-btn:before,
.search-bar input:focus+.search-btn:after,
.search-bar input:valid+.search-btn:before,
.search-bar input:valid+.search-btn:after {
    opacity: 1;
}

.search-bar input:focus+.search-btn:hover,
.search-bar input:valid+.search-btn:hover,
.search-bar input:valid:not(:focus)+.search-btn:focus {
    background: #3f9fd8;
}

.search-bar input:focus+.search-btn:active,
.search-bar input:valid+.search-btn:active {
    transform: translateY(1px);
}


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

.live-slash {
    color: #5b7a94;
}

/* The player block: a row of video + chat, with the settings below it. */
.live {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: min(1400px, 94vw);
    margin-bottom: 2rem;
}

/* Video and chat are siblings here rather than the chat sitting beside the
   whole column, so `stretch` sizes the chat to the player exactly — anything
   below the row (the settings line, the back link) doesn't lengthen it. */
.live-row {
    display: flex;
    align-items: stretch;
    gap: 1rem;
    width: 100%;
    min-height: 0;
}

/* Stack once there isn't room for a readable column of chat beside the video. */
@media (max-width: 860px) {
    .live-row {
        flex-direction: column;
    }

    .chat {
        flex: none;
        height: 300px;
    }
}

.live-frame {
    position: relative;
    /* Without min-width a flex child refuses to shrink below its content. */
    flex: 1 1 auto;
    min-width: 0;
    aspect-ratio: 16 / 9;
    border: 1px solid #bfe0f5;
    border-radius: 10px;
    /* Stays dark: this is the letterbox behind the video. By default this fill
       would also sit under the border, and a 1px curve antialiased over black
       comes out as a ragged dark corner instead of a border. Clipping it to the
       padding box keeps the corners crisp. */
    background: #0d0d0d;
    background-clip: padding-box;
    overflow: hidden;
    transition: background 0.2s;
    /* A <video> paints into its own compositing layer, which overflow:hidden
       alone does not clip — its square black corners spill out over the rounded
       border. Isolating the frame forces the clip to be honoured; the radius on
       the children below covers browsers that still skip it. */
    isolation: isolate;
}

/* While the placeholder is up there's no video to letterbox, so drop the dark
   fill. Leaving it there rings every corner in grey: the frame's rounded clip
   antialiases the panel and the fill against the same edge pixels, so a
   fraction of the black survives no matter how opaque the panel is. */
.live-frame:has(.live-offline:not([hidden])) {
    background: #f7fbfe;
}

/* Same reason, one layer down: the element has nothing to show while the
   placeholder is up, but its own letterbox fill still tints the corners
   through it. Scoped to offline — unconditionally, this hides the stream. */
.live-frame:has(.live-offline:not([hidden])) .live-video {
    opacity: 0;
}

.live-video {
    width: 100%;
    height: 100%;
    display: block;
    background: #0d0d0d;
    /* The frame's 10px outer curve, less its 1px border: matching it exactly
       means neither a square corner nor a sliver of page shows through. A
       <video> needs this of its own — it composites in a layer that the
       frame's overflow:hidden doesn't clip. */
    border-radius: 9px;
}

.live-offline {
    position: absolute;
    /* Must stay `0`, not a bare `-1`: an unitless non-zero length is invalid,
       so the whole declaration drops and the panel falls to its static
       position past the bottom of the frame, where overflow:hidden eats it. */
    inset: 0;
    z-index: 1;
    /* Deliberately square, clipped by the frame instead. Rounding it too would
       antialias its own curve against the dark letterbox behind it, ringing
       each corner in grey; covering the corners outright avoids that. */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    background: #f7fbfe;
    color: #5b7a94;
    letter-spacing: 0.04em;
}

.live-offline[hidden] {
    display: none;
}

.live-offline p {
    font-size: 1rem;
}

.live-offline small {
    font-size: 0.75rem;
    color: #8fb3cf;
}

.live-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #8fb3cf;
}

.live-badge {
    position: absolute;
    top: 0.75rem;
    left: 0.75rem;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    background: rgba(13, 13, 13, 0.75);
    color: #ffffff;
    font-size: 0.65rem;
    letter-spacing: 0.12em;
    pointer-events: none;
}

.live-badge[hidden] {
    display: none;
}

.live-viewers {
    /* Sits inside the dark badge, not on the page background. */
    color: #c8dcee;
}

/* Only draw the divider when there is a count to divide from. */
.live-viewers:not(:empty)::before {
    content: "";
    display: inline-block;
    width: 1px;
    height: 0.7em;
    margin: 0 0.5em -0.05em 0;
    background: #a7c4da;
}

.live-badge::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #e5484d;
    animation: live-pulse 2s ease-in-out infinite;
}

@keyframes live-pulse {

    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: 0.35;
    }
}

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

.live-back {
    align-self: flex-start;
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    color: #8fb3cf;
    text-decoration: none;
    transition: color 0.2s;
}

.live-back:hover {
    color: #5b7a94;
}

/* --- chat --- */

.chat {
    display: flex;
    flex-direction: column;
    flex: 0 0 340px;
    min-height: 0;
    border: 1px solid #bfe0f5;
    border-radius: 10px;
    background: #f7fbfe;
    overflow: hidden;
}

.chat-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid #bfe0f5;
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #5b7a94;
}

.chat-log {
    /* A zero basis, not `auto`: the backlog must contribute nothing to the
       panel's own height, or a busy chat stretches the row and drags the
       player taller with it instead of scrolling. */
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
    padding: 0.6rem 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    font-size: 0.85rem;
    line-height: 1.45;
}

.chat-msg {
    /* Long unbroken strings shouldn't stretch the panel. */
    overflow-wrap: anywhere;
}

.chat-who {
    color: #2570c7;
    margin-right: 0.4em;
}

.chat-who::after {
    content: ":";
    color: #8fb3cf;
}

.chat-text {
    color: #1f3347;
}

.chat-system {
    color: #6b8aa5;
    font-style: italic;
    font-size: 0.8rem;
}

.chat-form {
    display: flex;
    gap: 0.4rem;
    padding: 0.6rem;
    border-top: 1px solid #bfe0f5;
}

.chat-form input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.5rem 0.6rem;
    border: 1px solid #bfe0f5;
    border-radius: 6px;
    background: #ffffff;
    color: #1f3347;
    font-family: inherit;
    font-size: 0.85rem;
}

.chat-form input:focus {
    outline: none;
    border-color: #89cff0;
}

.chat-form input:disabled {
    color: #a7c4da;
}

.chat-form button {
    padding: 0.5rem 0.75rem;
    border: 1px solid #bfe0f5;
    border-radius: 6px;
    background: #e8f4fc;
    color: #1f3347;
    font-family: inherit;
    font-size: 0.8rem;
    cursor: pointer;
    transition: border-color 0.2s, background 0.2s;
}

.chat-form button:hover:not(:disabled) {
    border-color: #89cff0;
    background: #d9edfa;
}

.chat-form button:disabled {
    color: #a7c4da;
    cursor: default;
}

.chat-notice:empty {
    display: none;
}

.chat-notice {
    padding: 0 0.75rem 0.6rem;
    font-size: 0.75rem;
    color: #6b8aa5;
}

/* --- account --- */

/* Who you are, pinned to the top right of the viewport rather than riding along
   in the chat head. Fixed, so it stays put on the pages that scroll. */
.user-bar {
    position: fixed;
    top: 0.5rem;
    right: 0.75rem;
    /* Over the player and the chat, under a modal's backdrop. */
    z-index: 5;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.35rem 0.5rem;
    border: 1px solid #bfe0f5;
    border-radius: 8px;
    background: #f7fbfe;
    /* The typography the chat head used to lend it. */
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #5b7a94;
}

.account {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.account-name {
    color: #1f3347;
    text-transform: none;
    letter-spacing: 0.02em;
}

/* The name is the menu button; the caret says so. */
.account-name[aria-haspopup]::after {
    content: " ▾";
    color: #8fb3cf;
}

.account-menu-wrap {
    position: relative;
    display: flex;
}

/* Hangs off the name, right-aligned so it stays inside the viewport edge. */
.account-menu {
    position: absolute;
    top: calc(100% + 0.45rem);
    right: 0;
    display: flex;
    flex-direction: column;
    min-width: 9rem;
    padding: 0.25rem;
    border: 1px solid #bfe0f5;
    border-radius: 8px;
    background: #f7fbfe;
    box-shadow: 0 6px 16px rgba(31, 51, 71, 0.12);
}

.account-menu[hidden] {
    display: none;
}

.account-menu-item {
    padding: 0.4rem 0.5rem;
    border: 0;
    border-radius: 6px;
    background: none;
    color: #5b7a94;
    font-family: inherit;
    font-size: 0.7rem;
    letter-spacing: 0.04em;
    text-align: left;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.account-menu-item:hover {
    background: #e8f4fc;
    color: #1f3347;
}

.account-btn {
    padding: 0.3rem 0.5rem;
    border: 1px solid #bfe0f5;
    border-radius: 6px;
    background: #e8f4fc;
    color: #5b7a94;
    font-family: inherit;
    font-size: 0.7rem;
    letter-spacing: 0.04em;
    cursor: pointer;
    transition: border-color 0.2s, color 0.2s;
}

.account-btn:hover {
    border-color: #89cff0;
    color: #1f3347;
}

.account-btn--primary {
    background: #bfe0f5;
    color: #1f3347;
}

.account-dialog {
    border: 1px solid #bfe0f5;
    border-radius: 10px;
    background: #f7fbfe;
    color: #1f3347;
    padding: 1.25rem;
    width: min(340px, 92vw);
    /* The `* { margin: 0 }` reset above kills the UA stylesheet's `margin: auto`
       on dialog, which is what centres a modal inside its `inset: 0` box — put
       it back, or the dialog pins itself to a corner of the viewport. */
    position: fixed;
    inset: 0;
    margin: auto;
    max-height: calc(100dvh - 2rem);
    overflow: auto;
}

.account-dialog::backdrop {
    background: rgba(31, 51, 71, 0.35);
}

.account-dialog h2 {
    font-size: 1rem;
    font-weight: 400;
    letter-spacing: 0.04em;
    margin-bottom: 1rem;
}

.account-dialog label {
    display: block;
    margin-bottom: 0.75rem;
}

.account-dialog label[hidden] {
    display: none;
}

.account-dialog label span,
.color-recent>span {
    display: block;
    margin-bottom: 0.25rem;
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #5b7a94;
}

.account-dialog input {
    width: 100%;
    padding: 0.5rem 0.6rem;
    border: 1px solid #bfe0f5;
    border-radius: 6px;
    background: #ffffff;
    color: #1f3347;
    font-family: inherit;
    font-size: 0.9rem;
}

.account-dialog input:focus {
    outline: none;
    border-color: #89cff0;
}

.account-error:empty {
    display: none;
}

.account-error {
    margin-bottom: 0.75rem;
    font-size: 0.8rem;
    color: #e5484d;
}

.account-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

.account-actions>div {
    display: flex;
    gap: 0.4rem;
}

.account-link {
    border: 0;
    background: none;
    padding: 0;
    color: #6b8aa5;
    font-family: inherit;
    font-size: 0.75rem;
    text-decoration: underline;
    cursor: pointer;
}

.account-link:hover {
    color: #5b7a94;
}

/* The global `form` rule centres text; these want normal alignment. */
.chat-form,
.account-dialog form {
    text-align: left;
    color: inherit;
}

/* --- name color picker --- */

/* The signed-in name is a button now; keep it looking like a name. */
button.account-name {
    border: 0;
    background: none;
    padding: 0;
    font-family: inherit;
    font-size: inherit;
    cursor: pointer;
    text-decoration: none;
}

button.account-name:hover {
    text-decoration: underline;
}

.chat-who--me {
    cursor: pointer;
}

.chat-who--me:hover {
    text-decoration: underline;
}

/* The wheel: a native colour input, stripped of its chrome so the swatch it
   shows fills the same box the text fields use. */
/* Typed selector to outweigh `.account-dialog input`, which sets the padding
   the swatch needs to lose. */
input.color-wheel {
    height: 2.4rem;
    padding: 0.25rem;
    cursor: pointer;
}

.color-wheel::-webkit-color-swatch-wrapper {
    padding: 0;
}

.color-wheel::-webkit-color-swatch {
    border: 0;
    border-radius: 4px;
}

.color-wheel::-moz-color-swatch {
    border: 0;
    border-radius: 4px;
}

.color-recent {
    margin-bottom: 0.75rem;
}

.color-recent[hidden] {
    display: none;
}

.color-swatches {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 0.4rem;
}

.color-swatch {
    aspect-ratio: 1;
    border: 2px solid transparent;
    border-radius: 6px;
    cursor: pointer;
    transition: transform 0.1s, border-color 0.1s;
}

.color-swatch:hover {
    transform: scale(1.08);
}

.color-swatch--active {
    border-color: #1f3347;
}

.color-preview:empty {
    display: none;
}

.color-preview {
    margin-bottom: 0.75rem;
    padding: 0.5rem 0.6rem;
    border-radius: 6px;
    background: #ffffff;
    font-size: 0.9rem;
}

.color-preview::after {
    content: ": your name will look like this";
    color: #6b8aa5;
    font-size: 0.8rem;
}

/* --- theater mode --- */

.theater-btn {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    /* Above the offline placeholder, which is an opaque overlay across the
       whole frame — without this the button is only usable while live. */
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.35rem;
    border: 0;
    border-radius: 4px;
    background: rgba(13, 13, 13, 0.75);
    color: #5b7a94;
    cursor: pointer;
    transition: color 0.2s;
}

.theater-btn:hover {
    color: #ffffff;
}

.theater-btn[aria-pressed="true"] {
    color: #ffffff;
}

/* Player + chat fill the viewport below the account bar; everything else is
   pushed past the fold. The height has to discount the bar's strip, or the
   body's padding pushes the row past the bottom edge and the page scrolls. */
body.theater .live {
    width: 100vw;
    height: calc(100vh - var(--user-bar-height));
    padding: 0.75rem;
    margin-bottom: 2rem;
}

/* The row takes whatever height the settings line leaves, and both the frame
   and the chat stretch to it — so they stay exactly as tall as each other. */
body.theater .live-row {
    flex: 1 1 auto;
    min-height: 0;
    gap: 0.75rem;
}

body.theater .live-frame {
    /* Free the frame from 16:9 so it can use the full height; the video
       letterboxes itself inside. */
    aspect-ratio: auto;
    min-height: 0;
}

@media (max-width: 860px) {

    /* Stacked layout: give the video its shape back and let the page scroll. */
    body.theater .live {
        height: auto;
        min-height: calc(100vh - var(--user-bar-height));
    }

    body.theater .live-row {
        flex: none;
    }

    body.theater .live-frame {
        aspect-ratio: 16 / 9;
        flex: none;
    }
}

/* --- moderation --- */

.mod-dialog {
    width: min(440px, 94vw);
}

.mod-anon:empty {
    display: none;
}

.mod-anon {
    margin-bottom: 0.6rem;
    font-size: 0.75rem;
    color: #6b8aa5;
}

.mod-list {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    max-height: 300px;
    overflow-y: auto;
    margin-bottom: 0.75rem;
}

.mod-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.5rem;
    border: 1px solid #d9edfa;
    border-radius: 6px;
    background: #ffffff;
}

.mod-name {
    font-size: 0.85rem;
    color: #2570c7;
}

.mod-status {
    flex: 1 1 auto;
    font-size: 0.7rem;
    color: #6b8aa5;
}

.mod-controls {
    display: flex;
    align-items: center;
    gap: 0.3rem;
}

.mod-select {
    padding: 0.25rem;
    border: 1px solid #bfe0f5;
    border-radius: 6px;
    background: #ffffff;
    color: #1f3347;
    font-family: inherit;
    font-size: 0.7rem;
}

.mod-ban {
    color: #b91c1c;
}

.mod-empty {
    font-size: 0.8rem;
    color: #6b8aa5;
}
