/* ui/styles/base.css */
@font-face {
    font-family: "Space Grotesk";
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url("/assets/SpaceGrotesk-Variable-8e085aa43809.woff2")
        format("woff2");
}

/* Design tokens. Use these instead of new color-mix percentages. */
:root {
    --color-text: #fff;
    --color-bg: #111;
    --color-accent: #dc7430;
    --color-danger: #f25555;
    --color-success: #22c55e;

    --color-text-muted:
        color-mix(in srgb, var(--color-text) 75%, transparent);
    --color-text-subtle:
        color-mix(in srgb, var(--color-text) 65%, transparent);
    --color-border-subtle:
        color-mix(in srgb, var(--color-text) 15%, transparent);
    --color-border: color-mix(in srgb, var(--color-text) 18%, transparent);
    /* At least 3:1 against the page and cards, as WCAG asks of the
       edges that show a control's shape. */
    --color-border-strong:
        color-mix(in srgb, var(--color-text) 35%, transparent);
    --color-border-hover:
        color-mix(in srgb, var(--color-text) 60%, transparent);
    --color-surface: color-mix(in srgb, var(--color-text) 4%, var(--color-bg));
    --color-surface-hover:
        color-mix(in srgb, var(--color-text) 7%, var(--color-bg));
    /* Navigation chrome, the desktop menu and the action bar: halfway
       between black and the page, so it recedes behind the content. */
    --color-chrome: color-mix(in srgb, #000 35%, var(--color-bg));
    --color-accent-tint:
        color-mix(in srgb, var(--color-accent) 12%, transparent);
    --color-accent-tint-strong:
        color-mix(in srgb, var(--color-accent) 18%, transparent);
    --color-danger-tint:
        color-mix(in srgb, var(--color-danger) 12%, transparent);
    /* Opaque, because the toast floats over content. */
    --color-success-surface: #004d00;
    --color-success-border:
        color-mix(in srgb, var(--color-success) 25%, #004d00);
    --color-danger-surface: #5c0a0a;
    --color-danger-border:
        color-mix(in srgb, var(--color-danger) 25%, #5c0a0a);
    /* The bezel around a canvas or preview shown as a screen. */
    --color-screen-frame: #202020;
    /* The stage behind a canvas or preview shown as a screen. */
    --color-stage: #30343a;
    --color-backdrop: rgb(0 0 0 / 65%);
    --shadow-overlay: 0 1rem 3rem rgb(0 0 0 / 45%);

    --control-radius: 0.375rem;
    --surface-radius: 0.75rem;
    --control-height: 2.75rem;
    /* Every page shares this width, so the content and the action bar,
       which is derived from it, keep their width from page to page. */
    --page-width: 52rem;
    /* The desktop menu drawer's width. The content column, the
       ActionBar, and toasts move aside by it while the drawer is open. */
    --menu-width: 20rem;
    --focus-ring: 2px solid var(--color-accent);
    --transition-fast: 150ms ease;
}

body {
    margin: 0;
    font-family: "Space Grotesk", sans-serif;
    color: var(--color-text);
    background: var(--color-bg);
    line-height: 1.5;
}

/* Zero specificity, so a component can still adjust its own offset. */
:where(a, button, input, select, textarea, summary):focus-visible {
    outline: var(--focus-ring);
    outline-offset: 2px;
}

.app-page {
    box-sizing: border-box;
    width: min(100%, var(--page-width));
    margin: 0 auto;
    padding: 2rem 1.25rem 6rem 1.25rem;
}

.app-page :where(h1) {
    min-width: 0;
    margin: 0;
    font-size: clamp(1.75rem, 4vw, 2.25rem);
    line-height: 1.2;
    overflow-wrap: anywhere;
}

.app-page :where(h2) {
    font-size: 1.15rem;
    line-height: 1.3;
}

/* Keeps text available to assistive technology without showing it. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ui/styles/buttons.css */
.app-button {
    display: inline-flex;
    box-sizing: border-box;
    min-height: var(--control-height);
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--control-radius);
    color: inherit;
    background: transparent;
    font: inherit;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
}

.app-button:hover:not(:disabled) {
    border-color: var(--color-border-hover);
    background: var(--color-surface-hover);
}

.app-button:disabled {
    cursor: default;
    opacity: 0.55;
}

.app-button.primary {
    border-color: var(--color-accent);
    color: var(--color-bg);
    background: var(--color-accent);
    font-weight: 650;
}

.app-button.primary:hover:not(:disabled) {
    border-color: var(--color-accent);
    background: var(--color-accent);
}

.app-button.danger {
    border-color: color-mix(in srgb, var(--color-danger) 60%, transparent);
    color: var(--color-danger);
}

.app-button.danger:hover:not(:disabled) {
    border-color: var(--color-danger);
    background: var(--color-danger-tint);
}

.app-button.dashed {
    border-style: dashed;
    color: var(--color-accent);
}

.app-button.dashed:hover:not(:disabled) {
    border-color: var(--color-accent);
    background: var(--color-accent-tint);
}

.app-button.block {
    width: 100%;
}

.app-icon-button {
    display: grid;
    box-sizing: border-box;
    width: var(--control-height);
    min-width: var(--control-height);
    height: var(--control-height);
    min-height: var(--control-height);
    flex: none;
    padding: 0;
    place-items: center;
    border: 0;
    border-radius: var(--control-radius);
    color: inherit;
    background: transparent;
    cursor: pointer;
}

.app-icon-button:hover {
    background: var(--color-surface-hover);
}

/* A row of buttons on a bordered surface, shared by the Layout editor
   toolbar and the action bar. Icon-only buttons are square; buttons with a
   text label get side padding around it. */
.app-toolbar {
    display: flex;
    box-sizing: border-box;
    align-items: center;
    gap: 0.25rem;
    padding: 0.5rem;
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--control-radius);
    background: var(--color-bg);
}

.app-toolbar .app-button {
    flex: none;
    min-width: var(--control-height);
    padding: 0.5rem;
}

.app-toolbar .app-button:has(> span) {
    padding-right: 0.875rem;
    padding-left: 0.75rem;
}

.app-link {
    color: var(--color-accent);
    font-weight: 600;
    text-underline-offset: 0.15em;
}

/* ui/styles/cards.css */
.app-surface {
    box-sizing: border-box;
    border: 1px solid var(--color-border);
    border-radius: var(--control-radius);
    background: var(--color-surface);
}

/* Inner spacing for a surface that holds content directly. */
.app-panel {
    min-width: 0;
    padding: 1.25rem;
    overflow-wrap: anywhere;
}

.app-page-top {
    display: grid;
    gap: 1rem;
    margin-bottom: 1.5rem;
}

.app-page-top > p {
    margin: 0;
    color: var(--color-text-muted);
    overflow-wrap: anywhere;
}

.app-list {
    display: grid;
    gap: 0.75rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* A list of rows divided by rules, such as on a surface. */
.app-rows {
    margin: 0;
    padding: 0;
    list-style: none;
}

.app-rows > li + li {
    border-top: 1px solid var(--color-border-subtle);
}

/* A heading followed by badges, wrapping on narrow screens. */
.app-title-row {
    display: flex;
    min-width: 0;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
}

.app-title-row > :is(h1, h2, h3, strong) {
    margin: 0;
    overflow-wrap: anywhere;
}

/* A trailing section set apart by a rule, such as a delete form. */
.app-separated {
    margin-top: 2rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--color-border-subtle);
}

.app-eyebrow {
    color: var(--color-text-subtle);
    font-size: 0.75rem;
    font-weight: 650;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.app-data-list {
    display: grid;
    margin: 0;
}

.app-data-list > div {
    display: grid;
    min-width: 0;
    gap: 0.15rem;
}

.app-data-list dt {
    color: var(--color-text-subtle);
}

.app-data-list dd {
    min-width: 0;
    margin: 0;
    overflow-wrap: anywhere;
}

.app-card-link {
    display: grid;
    min-width: 0;
    gap: 0.85rem;
    padding: 1.25rem;
    color: inherit;
    text-decoration: none;
    transition:
        background var(--transition-fast),
        border-color var(--transition-fast);
}

.app-card-details {
    display: grid;
    min-width: 0;
    gap: 0.1rem;
    overflow-wrap: anywhere;
}

.app-card-title {
    font-size: 1.15rem;
    font-weight: 650;
    line-height: 1.3;
}

.app-card-meta {
    color: var(--color-text-muted);
}

.app-card-link:hover {
    border-color: color-mix(in srgb, var(--color-accent) 70%, transparent);
    background: var(--color-surface-hover);
}

.app-card-link:focus-visible {
    outline-offset: 3px;
}

.app-card-action {
    display: inline-flex;
    box-sizing: border-box;
    min-height: var(--control-height);
    align-items: center;
    justify-self: start;
    padding: 0.5rem 0.9rem;
    border: 1px solid
        color-mix(in srgb, var(--color-accent) 65%, transparent);
    border-radius: var(--control-radius);
    color: var(--color-accent);
    font-weight: 650;
}

.app-card-link:hover .app-card-action,
.app-card-link:focus-visible .app-card-action {
    border-color: var(--color-accent);
}

.app-empty-state {
    margin: 0;
    padding: 2rem 1.25rem;
    border: 1px dashed
        color-mix(in srgb, var(--color-text) 25%, transparent);
    border-radius: var(--control-radius);
    color: var(--color-text-muted);
    text-align: center;
}

@media (min-width: 40rem) {
    .app-panel {
        padding: 1.5rem;
    }

    .app-card-link {
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: center;
    }

    .app-data-list > div {
        grid-template-columns: 10rem minmax(0, 1fr);
        gap: 1rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .app-card-link {
        transition: none;
    }
}

/* ui/styles/forms.css */
/* Native controls share one look. :where() keeps these defaults at zero
   specificity, so component classes override them without !important. */
:where(
    input:not(
        [type="radio"],
        [type="checkbox"],
        [type="file"],
        [type="hidden"]
    ),
    select,
    textarea
) {
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    min-height: var(--control-height);
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--control-radius);
    color: inherit;
    background: var(--color-bg);
    font: inherit;
}

/* Textareas fill their container's width, so only their height resizes. */
:where(textarea) {
    resize: vertical;
}

:where(input[readonly]) {
    color: var(--color-text-muted);
}

:where(input:disabled, select:disabled, textarea:disabled) {
    border-color: var(--color-border);
    color: var(--color-text-muted);
    cursor: not-allowed;
}

:where(input[type="radio"], input[type="checkbox"]) {
    accent-color: var(--color-accent);
}

:where(label) {
    display: grid;
    min-width: 0;
    gap: 0.35rem;
}

:where(label > span:first-child) {
    font-weight: 600;
}

.app-form {
    display: grid;
    max-width: 32rem;
    gap: 1rem;
}

.app-form > :is(.app-button, a) {
    justify-self: start;
}

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

.app-fieldset > legend {
    margin-bottom: 0.5rem;
    padding: 0;
    font-weight: 600;
}

.app-field-row {
    display: grid;
    gap: 1rem;
}

/* A radio or checkbox drawn as a selectable card. */
.app-choice {
    display: flex;
    box-sizing: border-box;
    min-height: var(--control-height);
    align-items: center;
    gap: 0.65rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--control-radius);
    font-weight: 600;
    cursor: pointer;
}

.app-choice:has(:checked) {
    border-color: var(--color-accent);
    background: var(--color-accent-tint);
}

.app-choice:has(:focus-visible) {
    outline: var(--focus-ring);
    outline-offset: 2px;
}

/* A choice card with a title and a muted description under it, for
   options whose differences need explaining. The radio stays visible and
   lines up with the title's first line. */
.app-choice.app-choice-described {
    align-items: start;
    padding: 0.75rem 0.9rem;
}

.app-choice-described input {
    margin-top: 0.3rem;
}

.app-choice-text {
    display: grid;
    gap: 0.15rem;
}

.app-choice-text > :last-child {
    color: var(--color-text-muted);
    font-size: 0.875rem;
    font-weight: 400;
}

/* Radio choices in equal columns, read as one segmented control. The
   native radios stay in the labels for keyboard and screen-reader use but
   are not drawn. */
.app-choice-group {
    display: grid;
    grid-auto-columns: minmax(0, 1fr);
    grid-auto-flow: column;
    gap: 0.5rem;
}

.app-choice-group .app-choice {
    position: relative;
    justify-content: center;
}

.app-choice-group .app-choice:has(:checked) {
    color: var(--color-accent);
}

.app-choice-group input[type="radio"] {
    position: absolute;
    width: 1px;
    height: 1px;
    min-height: 0;
    margin: 0;
    padding: 0;
    opacity: 0;
}

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

@media (min-width: 32rem) {
    .app-field-row {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* ui/styles/layout-canvas.css */
.layout-viewport {
    position: relative;
    overflow: hidden;
}

.layout-canvas {
    position: absolute;
    top: 0;
    left: 0;
    transform-origin: top left;
}

.layout-element {
    position: absolute;
    box-sizing: border-box;
    overflow: hidden;
}

.layout-element.text-element {
    display: flex;
    align-items: center;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.layout-element.text-align-left {
    justify-content: flex-start;
    text-align: left;
}

.layout-element.text-align-center {
    justify-content: center;
    text-align: center;
}

.layout-element.text-align-right {
    justify-content: flex-end;
    text-align: right;
}

.layout-element.image-element img,
.layout-element.video-element :is(img, video) {
    display: block;
    width: 100%;
    height: 100%;
}

.layout-element.media-fit-contain :is(img, video) {
    object-fit: contain;
}

.layout-element.media-fit-cover :is(img, video) {
    object-fit: cover;
}

/* ui/components/AccountDelete/AccountDelete.css */
.account-delete > p {
    max-width: 40rem;
    margin: 0 0 1rem;
}

.account-delete-workspaces {
    display: grid;
    gap: 0.25rem;
    margin: 0 0 1rem;
    padding-left: 1.25rem;
}

.account-delete-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

/* ui/components/ActionBar/ActionBar.css */
/* The bar floats at the bottom of the content column and extends 0.75rem
   past the content on each side: it sits 0.5rem inside the page box,
   whose padding is 1.25rem. With left and right set, auto margins center
   it once max-width, the page width less that inset, limits its width.
   Its chrome background, a step darker than the page, matches the
   desktop menu and sets it apart from the cards under it. */
.action-bar {
    position: fixed;
    z-index: 9;
    right: 0.5rem;
    bottom: max(0.5rem, env(safe-area-inset-bottom));
    left: 0.5rem;
    max-width: calc(var(--page-width) - 1rem);
    margin-right: auto;
    margin-left: auto;
    border-color: var(--color-border);
    background: var(--color-chrome);
    transition: left 180ms ease;
}

/* Save and Delete sit at the right end, Delete last. */
.action-bar .action-bar-menu {
    margin-right: auto;
}

/* Mobile always shows the menu icon: the open sheet covers the bar, so
   the button only ever opens the drawer. */
.action-bar .action-bar-menu .lucide-panel-left-close {
    display: none;
}

.action-bar .action-bar-actions {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

/* The primary weight stays fixed so Save keeps its width when the form
   turns dirty. */
.action-bar .action-bar-save {
    font-weight: 650;
}

.action-bar .action-bar-save .lucide-loader-circle,
.action-bar .action-bar-save:disabled :is(.lucide-save, .lucide-plus) {
    display: none;
}

.action-bar .action-bar-save:disabled .lucide-loader-circle {
    display: inline-block;
    animation: action-bar-spin 800ms linear infinite;
}

@keyframes action-bar-spin {
    to {
        transform: rotate(1turn);
    }
}

/* Toasts rise above the bar: its offset, height, and a 0.5rem gap. */
.layout:has(.action-bar) .toast-region {
    bottom: calc(
        max(0.5rem, env(safe-area-inset-bottom)) +
            var(--control-height) + 1.5rem + 2px
    );
}

/* From 48rem the bar keeps its inset beside the open drawer. The menu
   button is the drawer's toggle and shows a close icon while the drawer
   is open. The drawer's class is already right on the first paint: it
   starts open, without closed. */
@media (min-width: 48rem) {
    .layout > .menu-drawer:not(.closed) + .content .action-bar {
        left: calc(var(--menu-width) + 0.5rem);
    }

    .layout > .menu-drawer:not(.closed) + .content .action-bar-menu
        .lucide-menu {
        display: none;
    }

    .layout > .menu-drawer:not(.closed) + .content .action-bar-menu
        .lucide-panel-left-close {
        display: inline-block;
    }
}

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

    .action-bar .action-bar-save:disabled .lucide-loader-circle {
        animation: none;
    }
}

/* ui/components/AlertDialog/AlertDialog.css */
/* The action link is shown only when the alert has one. */
.alert-dialog [hidden] {
    display: none;
}

/* ui/components/AppShell/AppShell.css */
.layout > .content {
    transition: margin-left 180ms ease;
}

.layout > .content > header {
    padding: 1rem;
    text-align: right;
}

@media (min-width: 48rem) {
    .layout > .menu-drawer:not(.closed) + .content {
        margin-left: var(--menu-width);
    }
}

@media (prefers-reduced-motion: reduce) {
    .layout > .content {
        transition: none;
    }
}

/* ui/components/AuthPage/AuthPage.css */
/* Signed-out pages: one centered card. */
.auth {
    display: grid;
    box-sizing: border-box;
    min-height: 100dvh;
    padding: 1.25rem;
    place-items: center;
}

.auth .auth-card {
    width: min(100%, 26rem);
    overflow-wrap: anywhere;
}

.auth .auth-card.wide {
    width: min(100%, 32rem);
}

.auth h1 {
    margin-top: 0;
    font-size: clamp(2rem, 8vw, 3rem);
    line-height: 1.05;
}

.auth form {
    display: grid;
    gap: 1.25rem;
}

.auth form > .app-hint {
    margin-top: -0.75rem;
}

.auth .language-switcher {
    margin-top: 2rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border-subtle);
}

/* ui/components/Avatar/Avatar.css */
/* Consumers set another size with width and height. */
.avatar {
    display: grid;
    width: 2.5rem;
    height: 2.5rem;
    flex: none;
    place-items: center;
    border-radius: 50%;
    object-fit: cover;
}

.avatar.avatar-initial {
    color: var(--color-accent);
    background: var(--color-accent-tint-strong);
    font-weight: 650;
}

/* ui/components/Badge/Badge.css */
.badge {
    display: inline-block;
    padding: 0.2rem 0.55rem;
    border: 1px solid var(--color-border);
    border-radius: var(--control-radius);
    color: var(--color-text-muted);
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1.3;
}

.badge.accent {
    border-color: color-mix(in srgb, var(--color-accent) 60%, transparent);
    color: var(--color-accent);
}

.badge.danger {
    border-color: var(--color-danger);
    color: var(--color-danger);
}

/* ui/components/BottomSheet/BottomSheet.css */
.bottom-sheet {
    --bottom-sheet-inline-padding: 1rem;
    --bottom-sheet-max-height: min(78dvh, 42rem);

    position: fixed;
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    box-sizing: border-box;
    width: 100%;
    max-width: none;
    max-height: var(--bottom-sheet-max-height);
    margin: 0;
    padding:
        0 var(--bottom-sheet-inline-padding)
        max(1rem, env(safe-area-inset-bottom));
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior-y: contain;
    border: 1px solid var(--color-border);
    border-radius: 1rem 1rem 0 0;
    color: inherit;
    background: var(--color-bg);
    visibility: visible;
    transition:
        transform 180ms ease,
        visibility 0s;
}

/* In-page panels stay rendered and close with the closed class, so CSS
   slides them both ways. initial suppresses the first-paint transition
   for panels that start open on some widths. */
.bottom-sheet.closed,
.bottom-sheet.initial {
    visibility: hidden;
    transform: translateY(100%);
}

.bottom-sheet.closed {
    transition:
        transform 180ms ease,
        visibility 0s 180ms;
}

.bottom-sheet.initial {
    transition: none;
}

.bottom-sheet-backdrop,
dialog.bottom-sheet::backdrop {
    background: var(--color-backdrop);
    opacity: 1;
    transition: opacity 180ms ease;
}

.bottom-sheet-backdrop {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    visibility: visible;
}

.bottom-sheet-backdrop.closed,
.bottom-sheet-backdrop.initial {
    visibility: hidden;
    opacity: 0;
}

.bottom-sheet-backdrop.closed {
    transition:
        opacity 180ms ease,
        visibility 0s 180ms;
}

.bottom-sheet-backdrop.initial {
    pointer-events: none;
    transition: none;
}

.bottom-sheet .bottom-sheet-handle {
    position: sticky;
    z-index: 2;
    top: 0;
    right: auto;
    bottom: auto;
    left: auto;
    display: flex;
    height: 2.75rem;
    flex: 0 0 2.75rem;
    width: 100%;
    min-height: 2.75rem;
    padding: 0;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 0;
    color: inherit;
    background: var(--color-bg);
    font: inherit;
    cursor: pointer;
    touch-action: none;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}

.bottom-sheet .bottom-sheet-handle::before {
    width: 2.5rem;
    height: 0.25rem;
    border-radius: 1rem;
    background-color: currentColor;
    content: "";
    opacity: 0.4;
}

.bottom-sheet .bottom-sheet-handle:active {
    color: var(--color-accent);
}

.bottom-sheet .bottom-sheet-handle:active::before {
    opacity: 0.7;
}

.bottom-sheet .bottom-sheet-handle:focus-visible {
    outline-offset: -2px;
}

dialog.bottom-sheet {
    transform: translateY(100%);
    transition: transform 180ms ease;
}

dialog.bottom-sheet[open] {
    transform: translateY(0);
}

dialog.bottom-sheet[open].bottom-sheet-dismissing {
    transform: translateY(100%);
}

dialog.bottom-sheet[open].bottom-sheet-dismissing::backdrop {
    opacity: 0;
}

@starting-style {
    dialog.bottom-sheet[open] {
        transform: translateY(100%);
    }

    dialog.bottom-sheet[open]::backdrop {
        opacity: 0;
    }
}

/* A sheet's title row. Its close button uses bottom-sheet-close in
   dialogs, or the consumer's own rule for in-page panels. */
.bottom-sheet-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.bottom-sheet-header h2 {
    min-width: 0;
    margin: 0;
    font-size: 1.15rem;
    line-height: 1.3;
    overflow-wrap: anywhere;
}

/* The header close button replaces the handle in the desktop dialog. */
.bottom-sheet-close {
    display: none;
}

/* Desktop dialogs are centered modals that close without sliding. The
   dismissing state is listed so its transform never applies here. */
@media (min-width: 48rem) {
    dialog.bottom-sheet,
    dialog.bottom-sheet[open],
    dialog.bottom-sheet[open].bottom-sheet-dismissing {
        right: 50%;
        bottom: 50%;
        left: auto;
        width: min(calc(100% - 2rem), 48rem);
        max-height: 90dvh;
        border-radius: var(--surface-radius);
        transform: translate(50%, 50%);
        transition: none;
    }

    dialog.bottom-sheet .bottom-sheet-handle {
        display: none;
    }

    dialog.bottom-sheet .bottom-sheet-close {
        display: grid;
    }
}

@media (prefers-reduced-motion: reduce) {
    .bottom-sheet,
    .bottom-sheet.closed,
    .bottom-sheet-backdrop,
    .bottom-sheet-backdrop.closed,
    dialog.bottom-sheet,
    dialog.bottom-sheet::backdrop {
        transition: none;
    }
}

/* ui/components/Dialog/Dialog.css */
/* Shared look for the small centered alert and confirmation dialogs. */
.dialog {
    box-sizing: border-box;
    width: min(calc(100% - 2rem), 30rem);
    padding: 1.25rem;
    border: 1px solid var(--color-border);
    border-radius: var(--surface-radius);
    color: var(--color-text);
    background: var(--color-bg);
    box-shadow: var(--shadow-overlay);
}

.dialog::backdrop {
    background: var(--color-backdrop);
}

.dialog h2 {
    margin-top: 0;
}

.dialog .dialog-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.75rem;
    margin-top: 1.5rem;
}

/* ui/components/Feedback/Feedback.css */
/* The wrapper creates no box, so an empty slot adds no gap in grid and
   flex forms, and its notice lays out as a direct child. */
.feedback {
    display: contents;
}

:is(form, .app-form, .app-panel) > .feedback > .notice {
    margin: 0;
}

/* ui/components/FileInput/FileInput.css */
/* The label holds the visually hidden native input, so it is positioned
   to keep the input, and the browser's validation bubble, beside it. */
.file-input {
    position: relative;
    cursor: pointer;
}

/* The chosen name sits below the button, which keeps its own width. */
.file-input .file-input-control {
    display: grid;
    min-width: 0;
    justify-items: start;
    gap: 0.35rem;
}

.file-input:hover .file-input-button {
    border-color: var(--color-border-hover);
    background: var(--color-surface-hover);
}

/* Keyboard focus is on the hidden input; show it on the visible button. */
.file-input:has(input:focus-visible) .file-input-button {
    outline: var(--focus-ring);
    outline-offset: 2px;
}

/* Long file names wrap and are clamped to two lines. */
.file-input .file-input-name {
    display: -webkit-box;
    min-width: 0;
    overflow: hidden;
    overflow-wrap: anywhere;
    color: var(--color-text-muted);
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
}

/* ui/components/Icon/Icon.css */
.icon {
    display: inline-block;
    width: 1.25rem;
    height: 1.25rem;
    flex: none;
    vertical-align: -0.25em;
}

/* ui/components/InvitationLink/InvitationLink.css */
.invitation-link {
    display: grid;
    gap: 0.5rem;
}

.invitation-link > label {
    font-weight: 600;
}

.invitation-link .invitation-link-controls {
    display: grid;
    min-width: 0;
    gap: 0.75rem;
}

@media (min-width: 40rem) {
    .invitation-link .invitation-link-controls {
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: center;
    }
}

/* ui/components/LanguageSwitcher/LanguageSwitcher.css */
.language-switcher {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-top: 1rem;
    font-size: 1rem;
}

.language-switcher .language-icon {
    width: 2.5rem;
    height: 2.5rem;
    flex: 0 0 2.5rem;
    object-fit: contain;
    filter: brightness(0) invert(1);
}

.language-switcher a {
    color: var(--color-text);
    text-decoration: none;
}

.language-switcher [aria-current] {
    color: var(--color-accent);
}

/* ui/components/LinkTabs/LinkTabs.css */
.link-tabs {
    display: inline-flex;
    margin-bottom: 1.25rem;
    padding: 0.2rem;
    border: 1px solid var(--color-border);
    border-radius: var(--control-radius);
}

.link-tabs a {
    display: inline-flex;
    align-items: center;
    min-height: 2.35rem;
    padding: 0 1rem;
    border-radius: calc(var(--control-radius) - 0.2rem);
    color: inherit;
    text-decoration: none;
}

.link-tabs a:hover {
    background: var(--color-surface-hover);
}

.link-tabs a[aria-current="page"] {
    color: var(--color-bg);
    background: var(--color-text);
}

/* ui/components/LoadMore/LoadMore.css */
/* The form exists only while another batch exists, so the empty container
   adds no space. */
.load-more form {
    display: flex;
    justify-content: center;
    margin-top: 1rem;
}

@media (min-width: 40rem) {
    .load-more .app-button {
        width: auto;
        min-width: 12rem;
    }
}

/* ui/components/MediaPreview/MediaPreview.css */
.media-preview {
    position: relative;
    display: block;
    aspect-ratio: 16 / 9;
    background: #000;
}

.media-preview :is(img, video) {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}

/* The preview never takes clicks from the card or button around it. */
.media-preview video {
    pointer-events: none;
}

.media-preview .media-preview-badge {
    position: absolute;
    right: 0.4rem;
    bottom: 0.4rem;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
    color: #fff;
    background: rgb(0 0 0 / 70%);
    font-size: 0.8rem;
    font-variant-numeric: tabular-nums;
    line-height: 1.4;
}

.media-preview .media-preview-badge .icon {
    width: 1rem;
    height: 1rem;
}

/* ui/components/MediaUploads/MediaUploads.css */
.media-uploads {
    margin-bottom: 0.75rem;
}

.media-uploads[hidden] {
    display: none;
}

.media-uploads .media-upload-item {
    display: grid;
    gap: 0.5rem;
}

.media-uploads .media-upload-item p {
    margin: 0;
}

.media-uploads .media-upload-remove-form {
    justify-self: start;
}

/* ui/components/MenuDrawer/MenuDrawer.css */
.menu-backdrop {
    z-index: 9;
}

.menu-drawer {
    z-index: 10;
    --bottom-sheet-inline-padding: 1.5rem;
    --bottom-sheet-max-height: min(70dvh, 32rem);

    display: flex;
    flex-direction: column;
    border-top-color: var(--color-border-subtle);
    border-right: 0;
    border-bottom: 0;
    border-left: 0;
}

.menu-drawer header {
    font-size: 1.2rem;
}

.menu-drawer h2 {
    margin: 0;
}

.menu-drawer .home {
    color: inherit;
    text-decoration: none;
}

.menu-drawer .home:hover {
    color: var(--color-accent);
}

.menu-drawer .routes {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    gap: 0.375rem;
    margin-top: 1rem;
    margin-bottom: 2rem;
}

.menu-drawer .workspace-switcher,
.menu-drawer .menu-super-admin {
    display: grid;
    gap: 0.75rem;
    margin-top: 1rem;
    padding-bottom: 0.75rem;
    border-bottom: 1px solid var(--color-border-subtle);
}

.menu-drawer .workspace-switcher .app-link {
    text-decoration: none;
}

.menu-drawer .workspace-switcher .app-link:hover {
    text-decoration: underline;
}

.menu-drawer .route {
    display: flex;
    box-sizing: border-box;
    min-height: var(--control-height);
    padding: 0.625rem 0.875rem;
    align-items: center;
    border-radius: 0.625rem;
    color: inherit;
    font-size: 1.125rem;
    font-weight: 550;
    text-decoration: none;
    transition:
        background-color 120ms ease,
        color 120ms ease;
}

.menu-drawer .route:hover {
    background-color: var(--color-surface-hover);
}

.menu-drawer .route[aria-current="page"] {
    color: var(--color-accent);
    background-color: var(--color-accent-tint);
}

.menu-drawer .account {
    margin-top: auto;
    padding-top: 1rem;
    padding-bottom: 1rem;
    border-top: 1px solid var(--color-border-subtle);
    border-bottom: 1px solid var(--color-border-subtle);
}

.menu-drawer .profile {
    display: grid;
    grid-template-columns: 2.5rem minmax(0, 1fr) auto;
    align-items: center;
    gap: 0.75rem;
    color: inherit;
    text-decoration: none;
}

.menu-drawer .profile:hover strong {
    color: var(--color-accent);
}

.menu-drawer .profile-details {
    display: flex;
    min-width: 0;
    flex-direction: column;
    line-height: 1.25;
}

.menu-drawer .profile-details > span {
    overflow: hidden;
    color: var(--color-text-subtle);
    font-size: 0.8rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (min-width: 48rem) {
    .menu-backdrop {
        display: none;
    }

    .menu-drawer {
        top: 0;
        right: auto;
        bottom: 0;
        left: 0;
        width: var(--menu-width);
        padding-top: 2rem;
        max-height: none;
        overflow: hidden;
        border-top: 0;
        border-right: 1px solid var(--color-border-subtle);
        border-radius: 0;
        background: var(--color-chrome);
    }

    .menu-drawer .routes {
        min-height: 0;
        flex: 1 1 auto;
        overflow-y: auto;
        overscroll-behavior-y: contain;
    }

    .menu-drawer .bottom-sheet-handle {
        display: none;
    }

    .menu-drawer.closed {
        transform: translateX(-100%);
    }

    .menu-drawer.initial {
        transform: none;
        visibility: visible;
    }

    /* The focused editor's drawer overlays the canvas instead of pushing
       it aside, which would refit the canvas on every frame, so the
       backdrop shows to close it. */
    .focused-editor > .menu-backdrop {
        display: block;
    }

    .layout.focused-editor > .menu-drawer:not(.closed) + .content {
        margin-left: 0;
    }
}

/* ui/components/MessageField/MessageField.css */
.message-field {
    display: grid;
    gap: 0.35rem;
}

/* Browsers without field-sizing keep a resizable textarea of four rows. */
.message-field textarea {
    min-height: 7.5rem;
    max-height: 60svh;
    field-sizing: content;
    line-height: 1.5;
}

.message-field-hint {
    display: none;
}

/* A message is longer than the other fields, so its form spans the card. */
.app-form:has(> .message-field) {
    max-width: none;
}

@media (hover: hover) and (pointer: fine) {
    .message-field-hint {
        display: block;
    }
}

/* ui/components/Notice/Notice.css */
.notice {
    margin: 0 0 1rem;
    padding: 0.75rem 1rem;
    border-left: 0.25rem solid var(--color-accent);
    background: var(--color-accent-tint);
}

.notice.error {
    border-left-color: var(--color-danger);
    background: var(--color-danger-tint);
}

.notice > p {
    margin: 0;
}

.notice > p + p {
    margin-top: 0.75rem;
}

/* Grid and flex parents space their children with gap. */
:is(form, .app-form, .app-panel) > .notice {
    margin: 0;
}

/* ui/components/PairForm/PairForm.css */
.pair-form-scanned-code {
    display: grid;
    gap: 0.35rem;
}

.pair-form-scanned-code strong {
    color: var(--color-accent);
    font-size: 2rem;
    letter-spacing: 0.12em;
}

/* ui/components/PasswordInput/PasswordInput.css */
.password-input {
    display: grid;
    min-width: 0;
    gap: 0.35rem;
}

.password-input > label {
    font-weight: 600;
}

.password-input .password-input-control {
    position: relative;
    min-width: 0;
}

.password-input .password-input-control input {
    padding-right: 3.25rem;
}

.password-input .password-input-toggle {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    display: grid;
    width: var(--control-height);
    padding: 0;
    place-items: center;
    border: 0;
    border-radius: var(--control-radius);
    color: var(--color-text);
    background: transparent;
    cursor: pointer;
}

.password-input .password-input-toggle[hidden] {
    display: none;
}

.password-input .password-input-toggle:hover {
    color: var(--color-accent);
}

.password-input .password-input-toggle:focus-visible {
    outline-offset: -3px;
}

/* ui/components/PreviewControls/PreviewControls.css */
.preview-controls {
    position: relative;
    gap: 0.125rem;
    padding: 0.25rem;
    overflow: hidden;
}

.preview-controls .app-button {
    min-width: 2.5rem;
}

.preview-controls :is(
    .preview-controls-position,
    .preview-controls-duration
) {
    padding: 0 0.5rem;
    font-variant-numeric: tabular-nums;
}

.preview-controls .preview-controls-duration {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: var(--color-text-subtle);
}

/* The line fills over the item's duration, which the script sets.
   Scaling keeps the animation off the main thread on slow phones. It runs
   along the bottom edge inside the rounded surface. */
.preview-controls .preview-controls-progress {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 2px;
    background: var(--color-accent);
    transform-origin: left;
    animation: preview-controls-progress linear forwards;
}

.preview-controls :is(
    .preview-controls-duration,
    .preview-controls-progress
)[hidden] {
    display: none;
}

@keyframes preview-controls-progress {
    from {
        transform: scaleX(0);
    }
}

@media (min-width: 40rem) {
    .preview-controls {
        gap: 0.25rem;
        padding: 0.5rem;
    }

    .preview-controls .app-button {
        min-width: var(--control-height);
    }
}

/* ui/components/SearchField/SearchField.css */
/* The global input style draws the field; this adds the icon and the
   space before the list it filters. */
.search-field {
    display: block;
    margin-bottom: 1rem;
}

.search-field label {
    position: relative;
}

/* Top and bottom 0 with auto margins center the fixed-height icon. */
.search-field .icon {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0.75rem;
    margin: auto 0;
    color: var(--color-text-subtle);
    pointer-events: none;
}

.search-field .search-field-input {
    padding-left: 2.5rem;
}

/* ui/components/Toast/Toast.css */
/* Toasts center on the content column, which the open desktop drawer
   moves aside. ActionBar raises them above the bar on menu pages. */
.toast-region {
    position: fixed;
    z-index: 11;
    right: 1rem;
    bottom: max(1rem, env(safe-area-inset-bottom));
    left: 1rem;
    display: grid;
    justify-items: center;
    gap: 0.5rem;
    pointer-events: none;
    transition: left 180ms ease;
}

.toast {
    display: flex;
    box-sizing: border-box;
    max-width: 100%;
    align-items: center;
    gap: 0.75rem;
    padding: 0.25rem 0.25rem 0.25rem 1rem;
    border: 1px solid var(--color-success-border);
    border-radius: var(--control-radius);
    background: var(--color-success-surface);
    box-shadow: var(--shadow-overlay);
    pointer-events: auto;
    animation: toast 4s ease forwards;
}

.toast[hidden] {
    display: none;
}

/* An error only slides in, so it stays until dismissed or replaced. */
.toast.error {
    border-color: var(--color-danger-border);
    background: var(--color-danger-surface);
    animation: toast-in 160ms ease;
}

/* The shared grey hover surface looks muddy on green. */
.toast > .app-icon-button:hover {
    background: var(--color-success-border);
}

.toast.error > .app-icon-button:hover {
    background: var(--color-danger-border);
}

.toast > .icon {
    color: var(--color-text);
}

.toast > p {
    margin: 0;
}

.toast:hover,
.toast:focus-within {
    animation-play-state: paused;
}

/* The final keyframe hides the toast so it cannot take focus or clicks. */
@keyframes toast {
    from {
        opacity: 0;
        transform: translateY(0.5rem);
    }

    4%,
    90% {
        opacity: 1;
        transform: none;
    }

    to {
        opacity: 0;
        visibility: hidden;
    }
}

@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateY(0.5rem);
    }
}

@keyframes toast-fade {
    from {
        opacity: 0;
    }

    4%,
    90% {
        opacity: 1;
    }

    to {
        opacity: 0;
        visibility: hidden;
    }
}

@media (min-width: 48rem) {
    .toast-region {
        bottom: 1.5rem;
    }

    .toast {
        max-width: 24rem;
    }

    /* Beside the open drawer, toasts center on the content column like
       the page and the ActionBar. The editor's drawer overlays the
       canvas, so its toasts stay centered on the window. */
    .layout:not(.focused-editor) > .menu-drawer:not(.closed)
        ~ .toast-region {
        left: calc(var(--menu-width) + 1rem);
    }
}

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

    .toast {
        animation-name: toast-fade;
    }

    .toast.error {
        animation: none;
    }
}

/* ui/pages/ErrorPage/ErrorPage.css */
.error-page {
    min-height: 100vh;
    display: grid;
    place-items: center;
}

.error-page-content {
    max-width: 40rem;
    padding: 2rem 1rem;
    text-align: center;
}

.error-page-content h1 {
    color: var(--color-accent);
}

/* ui/pages/Home/Home.css */
.home h1 {
    margin-bottom: 1rem;
    color: var(--color-accent);
}

.home .personal-workspace {
    margin-top: 2rem;
}

.home .personal-workspace a {
    color: inherit;
    font-weight: 650;
}

.home .home-invitation {
    display: grid;
    gap: 1rem;
}

.home .home-invitation h3 {
    margin: 0;
}

.home .home-invitation p {
    margin: 0.5rem 0 0;
}

@media (min-width: 40rem) {
    .home .home-invitation {
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: center;
    }
}

/* ui/pages/Invitation/Invitation.css */
.invitation .invitation-card {
    max-width: 40rem;
}

.invitation-card > .app-button,
.invitation-card > form {
    margin-top: 1.5rem;
}

.invitation-card > form {
    display: grid;
    justify-items: start;
    gap: 0.75rem;
}

/* ui/pages/LayoutEditor/LayoutEditor.css */
.focused-editor,
.focused-editor > .content {
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
}

.layout-editor-page {
    display: block;
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
}

/* Only the canvas zooms. iOS Safari ignores the viewport's zoom limits,
   so every box allows panning but not page zoom, including the root,
   dialogs, toasts, and dialog backdrops. A touch-action only reaches up
   to the nearest scroll container, so each element needs its own value.
   Elements that handle their own gestures set none. */
:root:has(> body.focused-editor),
.focused-editor,
.focused-editor :where(*),
.focused-editor :where(*)::backdrop {
    touch-action: pan-x pan-y;
}

/* The editor listens for clicks on the body so iOS does not zoom on
   double taps; the body itself shows no tap highlight. */
.focused-editor {
    -webkit-tap-highlight-color: transparent;
}

.focused-editor :where(*) {
    -webkit-tap-highlight-color: initial;
}

/* If the page zooms anyway, a pinch outside the canvas zooms it back. */
.focused-editor.page-zoomed :where(*),
.focused-editor.page-zoomed :where(*)::backdrop {
    touch-action: pan-x pan-y pinch-zoom;
}

.layout-editor-page .layout-editor-form {
    position: relative;
    display: grid;
    height: 100%;
    grid-template-rows: auto minmax(0, 1fr);
}

/* Name inputs read as titles on the canvas. */
.layout-editor-page .layout-name-input {
    min-width: 0;
    min-height: 2.25rem;
    padding: 0.25rem 0;
    border: 0;
    background: transparent;
    box-shadow: none;
    caret-color: currentColor;
    cursor: text;
    font-weight: 650;
    text-align: left;
    text-shadow: 0 1px 3px #000;
    text-overflow: ellipsis;
}

.layout-editor-page .layout-name-input:focus-visible {
    outline: none;
}

/* A Layout's name, and a playlist's name above its current layout's name
   and badges, follow the canvas; the component sets their position and
   the playlist heading's width. */
.layout-editor-page :is(.editor-name, .editor-item-heading) {
    position: absolute;
    z-index: 3;
    top: max(0.5rem, env(safe-area-inset-top));
    left: 50%;
    width: min(22rem, calc(100% - 1rem));
    transform: translateX(-50%);
    transform-origin: top left;
}

/* The names wait for the component to place them, so they do not show in
   the corner first. Hidden names keep their size for its measurements. */
bataatti-layout-editor:not(:defined)
    :is(.editor-name, .editor-item-heading) {
    visibility: hidden;
}

.layout-editor-page .editor-item-heading {
    display: grid;
}

/* The playlist's name reads as a small caption over its layout's name.
   Its box fits the text, so it sits close to the layout's name. */
.layout-editor-page .editor-playlist-name {
    min-height: 1rem;
    padding: 0;
    color: var(--color-text-subtle);
    font-size: 0.8rem;
    font-weight: 500;
}

.layout-editor-page .editor-item-label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.layout-editor-page .editor-item-name {
    min-width: 0;
    flex: 1;
}

.layout-editor-page .editor-item-name:read-only {
    cursor: default;
}

/* Stacked field labels stay quiet so the values stand out. One-control
   settings rows keep normal text. */
.layout-editor-page .settings-size label > span:first-child,
.layout-editor-page .settings-sheet legend,
.layout-editor-page .layout-usage h3,
.layout-editor-page .element-properties label > span:first-child,
.layout-editor-page .element-properties legend {
    color: var(--color-text-subtle);
    font-size: 0.75rem;
    font-weight: 400;
}

/* Editor buttons share one compact control style. */
.layout-editor-page button {
    box-sizing: border-box;
    min-height: var(--control-height);
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--control-radius);
    color: inherit;
    background: var(--color-bg);
    font: inherit;
    cursor: pointer;
}

.layout-editor-page button,
.layout-editor-page .editor-viewport {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}

.layout-editor-page button:disabled {
    opacity: 0.4;
}

.layout-editor-page .editor-workspace,
.layout-editor-page .editor-stage,
.layout-editor-page .editor-viewport {
    min-width: 0;
    min-height: 0;
}

.layout-editor-page .editor-workspace {
    position: relative;
    display: grid;
    grid-row: 2;
}

.layout-editor-page .editor-stage {
    position: relative;
    display: grid;
    overflow: hidden;
}

.layout-editor-page .editor-viewport {
    position: relative;
    overflow: hidden;
    background-color: var(--color-stage);
    overscroll-behavior: contain;
    touch-action: none;
}

/* The board holds the current layout's canvas; the component letterboxes
   a linked layout of another size inside it and transforms the board for
   pan and zoom. */
/* The board is the playlist's canvas and carries its frame. A linked
   layout of another shape is letterboxed inside it. */
.layout-editor-page .layout-board {
    position: absolute;
    top: 0;
    left: 0;
    transform-origin: top left;
}

/* The frame is in canvas pixels, so it scales with the screen. */
.layout-editor-page :is(.layout-board, .editor-preview-screen) {
    box-shadow:
        0 0 0 16px var(--color-screen-frame),
        0 24px 56px rgb(0 0 0 / 60%);
}

/* The grid properties do not inherit. A zoom changes them on the canvas,
   and inheritance would restyle every element as well. */
@property --editor-grid-size {
    syntax: "<length>";
    inherits: false;
    initial-value: 20px;
}

@property --editor-grid-x {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}

@property --editor-grid-y {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}

@property --editor-grid-dot-size {
    syntax: "<length>";
    inherits: false;
    initial-value: 1px;
}

.layout-editor-page .layout-canvas {
    background-image: radial-gradient(
        circle,
        var(--editor-grid-dot-color, rgb(255 255 255 / 55%))
            var(--editor-grid-dot-size, 1px),
        transparent var(--editor-grid-dot-size, 1px)
    );
    background-size:
        var(--editor-grid-size, 20px)
        var(--editor-grid-size, 20px);
    background-position:
        var(--editor-grid-x, 0)
        var(--editor-grid-y, 0);
    box-shadow:
        inset 0 0 0 calc(var(--editor-grid-size, 20px) / 2)
            var(--editor-canvas-color, transparent);
}

.layout-editor-page.snap-disabled .layout-canvas,
.layout-editor-page .layout-canvas.grid-hidden {
    background-image: none;
}

.layout-editor-page .layout-element {
    cursor: move;
    touch-action: none;
}

.layout-editor-page .layout-element.empty-text {
    outline: 1px dashed var(--color-accent);
    outline-offset: -1px;
}

.layout-editor-page .layout-element.selected {
    overflow: visible;
    outline: var(--editor-outline-size, 2px) solid var(--color-accent);
    outline-offset: calc(var(--editor-outline-size, 2px) * -1);
}

.layout-editor-page .layout-element.selected > img {
    overflow: hidden;
}

.layout-editor-page .editor-text-content {
    display: flex;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    align-items: center;
    overflow: hidden;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.layout-editor-page .editor-text-input {
    display: block;
    width: 100%;
    min-height: 0;
    max-height: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    outline: none;
    background: transparent;
    box-shadow: none;
    caret-color: currentColor;
    cursor: text;
    overflow: auto;
    resize: none;
    touch-action: pan-x pan-y;
    -webkit-touch-callout: default;
    -webkit-user-select: text;
    user-select: text;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.layout-editor-page .editor-text-input:focus-visible {
    outline: none;
}

.layout-editor-page .resize-handle {
    position: absolute;
    z-index: 2;
    width: var(--editor-handle-size, 24px);
    min-width: 0;
    height: var(--editor-handle-size, 24px);
    min-height: 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    touch-action: none;
}

.layout-editor-page .resize-handle::after {
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--editor-handle-dot-size, 14px);
    height: var(--editor-handle-dot-size, 14px);
    border-radius: 50%;
    background: var(--color-accent);
    box-shadow: 0 0 0 var(--editor-outline-size, 2px) #000;
    content: "";
    transform: translate(-50%, -50%);
}

.layout-editor-page.space-panning .editor-viewport,
.layout-editor-page.space-panning .layout-element {
    cursor: grab;
}

.layout-editor-page.is-panning .editor-viewport,
.layout-editor-page.is-panning .layout-element {
    cursor: grabbing;
}

.layout-editor-page .resize-nw {
    top: calc(var(--editor-handle-size, 24px) / -2);
    left: calc(var(--editor-handle-size, 24px) / -2);
    cursor: nwse-resize;
}

.layout-editor-page .resize-ne {
    top: calc(var(--editor-handle-size, 24px) / -2);
    right: calc(var(--editor-handle-size, 24px) / -2);
    cursor: nesw-resize;
}

.layout-editor-page .resize-sw {
    bottom: calc(var(--editor-handle-size, 24px) / -2);
    left: calc(var(--editor-handle-size, 24px) / -2);
    cursor: nesw-resize;
}

.layout-editor-page .resize-se {
    right: calc(var(--editor-handle-size, 24px) / -2);
    bottom: calc(var(--editor-handle-size, 24px) / -2);
    cursor: nwse-resize;
}

.layout-editor-page .editor-guide {
    position: absolute;
    z-index: 3;
    pointer-events: none;
    background: var(--color-accent);
}

.layout-editor-page .editor-guide.vertical {
    top: 0;
    bottom: 0;
    width: var(--editor-guide-size, 2px);
    margin-left: calc(var(--editor-guide-size, 2px) / -2);
}

.layout-editor-page .editor-guide.horizontal {
    right: 0;
    left: 0;
    height: var(--editor-guide-size, 2px);
    margin-top: calc(var(--editor-guide-size, 2px) / -2);
}

/* The shared app-toolbar class supplies the surface and button sizes. */
.layout-editor-page .editor-toolbar {
    position: absolute;
    z-index: 5;
    top: auto;
    right: auto;
    bottom: max(0.25rem, env(safe-area-inset-bottom));
    left: 50%;
    width: max-content;
    transform: translateX(-50%);
}

/* Phones fit eight buttons, 344px, in a 360px screen: they are narrower
   but keep the full control height. The preview's controls share these
   sizes. */
.layout-editor-page .editor-toolbar {
    gap: 0.125rem;
    padding: 0.25rem;
}

.layout-editor-page .editor-toolbar .app-button {
    min-width: 2.5rem;
}

/* Preview fits in the toolbar from 22.5rem. Narrower phones find it in
   the More sheet. */
.layout-editor-page .editor-toolbar .toolbar-preview {
    display: none;
}

@media (min-width: 22.5rem) {
    .layout-editor-page .editor-toolbar .toolbar-preview {
        display: inline-flex;
    }

    .layout-editor-page .settings-row.settings-preview {
        display: none;
    }
}

.layout-editor-page .toolbar-zoom {
    display: none;
    align-items: center;
    gap: 0.25rem;
}

@media (min-width: 40rem) {
    .layout-editor-page .editor-toolbar {
        gap: 0.25rem;
        padding: 0.5rem;
    }

    .layout-editor-page .editor-toolbar .app-button {
        min-width: var(--control-height);
    }

    /* The zoom group has its own Fit. */
    .layout-editor-page .editor-toolbar .toolbar-fit {
        display: none;
    }

    .layout-editor-page .toolbar-zoom {
        display: flex;
    }

    .layout-editor-page .settings-row.settings-zoom {
        display: none;
    }
}

.layout-editor-page [data-layout-save] .lucide-loader-circle,
.layout-editor-page [data-layout-save] .lucide-circle-check,
.layout-editor-page [data-save-state="saving"] .lucide-save,
.layout-editor-page [data-save-state="saved"] .lucide-save {
    display: none;
}

.layout-editor-page [data-save-state="saving"] .lucide-loader-circle {
    display: inline-block;
    animation: layout-editor-save-spin 800ms linear infinite;
}

.layout-editor-page [data-save-state="saved"] .lucide-circle-check {
    display: inline-block;
}

.layout-editor-page [data-save-state="saved"]:disabled {
    opacity: 1;
}

@keyframes layout-editor-save-spin {
    to {
        transform: rotate(360deg);
    }
}

.layout-editor-page .editor-toolbar button[aria-pressed="true"] {
    border-color: var(--color-accent);
    color: var(--color-accent);
    background: var(--color-accent-tint);
}

.layout-editor-page .element-properties {
    z-index: 12;
    --bottom-sheet-max-height: min(58dvh, 34rem);

    box-shadow: 0 -1rem 3rem rgb(0 0 0 / 50%);
}

.layout-editor-page .bottom-sheet-header {
    padding: 0 0 0.75rem;
    background: var(--color-bg);
}

.layout-editor-page .editor-sheet > header button {
    width: 2.75rem;
    padding: 0;
    border: 0;
    font-size: 1.5rem;
}

.layout-editor-page .editor-sheet:focus {
    outline: none;
}

/* The panel's groups share one rhythm. */
.layout-editor-page .element-properties > :not(.bottom-sheet-handle, header) {
    margin: 0 0 1rem;
}

.layout-editor-page .element-properties > :last-child {
    margin-bottom: 0;
}

.layout-editor-page .element-properties [hidden] {
    display: none;
}

.layout-editor-page .element-properties legend {
    margin-bottom: 0.35rem;
}

.layout-editor-page .element-properties h2 .icon {
    margin-right: 0.35rem;
    color: var(--color-accent);
}

.layout-editor-page .element-properties h2 {
    flex: 1;
}

.layout-editor-page .element-properties > header {
    gap: 0.25rem;
}

/* Icon buttons stay borderless under the editor's button style. */
.layout-editor-page .app-icon-button {
    padding: 0;
    border: 0;
}

.layout-editor-page .element-properties .properties-remove {
    color: var(--color-danger);
}

.layout-editor-page .element-properties .properties-remove:hover {
    background: var(--color-danger-tint);
}

.layout-editor-page .element-properties .properties-close {
    display: none;
}

.layout-editor-page .properties-media {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.layout-editor-page .properties-media > span {
    min-width: 0;
    flex: 1;
    overflow: hidden;
    color: var(--color-text-muted);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.layout-editor-page .properties-media button {
    flex: none;
}

.layout-editor-page .properties-row {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: end;
    gap: 0.75rem;
}

.layout-editor-page .properties-row input[type="color"] {
    padding: 0.25rem;
}

/* Icon choices keep the control height without the text padding. */
.layout-editor-page .element-properties .app-choice-group {
    gap: 0.25rem;
}

.layout-editor-page .element-properties .app-choice {
    padding-right: 0.25rem;
    padding-left: 0.25rem;
    font-weight: 400;
}

.layout-editor-page .properties-geometry {
    border-top: 1px solid var(--color-border-subtle);
    border-bottom: 1px solid var(--color-border-subtle);
}

/* The readout sits under its label, so it fits the narrow side panel
   with four-digit values. */
.layout-editor-page .properties-geometry summary {
    display: grid;
    min-height: var(--control-height);
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 0.75rem;
    padding: 0.25rem 0;
    line-height: 1.3;
    cursor: pointer;
    list-style: none;
}

.layout-editor-page .properties-geometry summary::-webkit-details-marker {
    display: none;
}

.layout-editor-page .properties-geometry [data-geometry-summary] {
    color: var(--color-text-subtle);
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
}

.layout-editor-page .properties-geometry summary .icon {
    grid-row: 1 / 3;
    grid-column: 2;
    color: var(--color-text-subtle);
    transition: transform var(--transition-fast);
}

.layout-editor-page .properties-geometry[open] summary .icon {
    transform: rotate(90deg);
}

.layout-editor-page .properties-geometry .properties-row {
    padding: 0.25rem 0 1rem;
}

.layout-editor-page .element-properties .properties-row .app-button {
    padding-right: 0.5rem;
    padding-left: 0.5rem;
}

.layout-editor-page .editor-sheet {
    --bottom-sheet-max-height: min(78dvh, 42rem);
}

/* The More sheet's groups are split by subtle lines. */
.layout-editor-page .settings-group {
    display: grid;
    gap: 0.75rem;
    margin: 0;
    padding: 1rem 0;
    border-top: 1px solid var(--color-border-subtle);
}

.layout-editor-page .bottom-sheet-header + .settings-group {
    padding-top: 0;
    border-top: 0;
}

.layout-editor-page .settings-group:last-child {
    padding-bottom: 0;
}

/* A one-control setting: its name at the left, the control at the
   right. */
.layout-editor-page .settings-row {
    display: flex;
    min-height: var(--control-height);
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.layout-editor-page .settings-row span {
    font-weight: inherit;
}

.layout-editor-page .settings-row input[type="color"] {
    width: 4rem;
    padding: 0.25rem;
}

.layout-editor-page label.settings-row {
    cursor: pointer;
}

/* Apply turns primary only while a size change is pending. */
.layout-editor-page .settings-size .app-button:disabled {
    border-color: var(--color-border-strong);
    color: inherit;
    background: transparent;
}

.layout-editor-page .settings-zoom > div {
    display: flex;
    gap: 0.25rem;
}

/* Elements opens another sheet, so it reads as a link row. */
.layout-editor-page .settings-link {
    width: 100%;
    padding: 0;
    border: 0;
    background: transparent;
    text-align: left;
}

.layout-editor-page .settings-link span {
    flex: 1;
}

.layout-editor-page .settings-link .lucide-chevron-right {
    color: var(--color-text-subtle);
}

.layout-editor-page .layout-usage h3 {
    margin: 0;
}

.layout-editor-page .layout-usage ul {
    display: grid;
    gap: 0.25rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.layout-editor-page .layout-usage a {
    overflow-wrap: anywhere;
}

/* The usage section above already draws the separator. */
.layout-editor-page .layout-usage + .layout-delete-form {
    padding-top: 0;
    border-top: 0;
}

.layout-editor-page .add-options {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.75rem;
}

.layout-editor-page .add-options button {
    display: grid;
    min-height: 7rem;
    place-items: center;
    gap: 0.25rem;
}

.layout-editor-page .add-options button .icon {
    width: 2rem;
    height: 2rem;
    color: var(--color-accent);
}

.layout-editor-page .media-picker-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.65rem;
    margin: 0 0 1rem;
}

.layout-editor-page .media-picker-grid button {
    display: grid;
    height: auto;
    padding: 0;
    overflow: hidden;
    text-align: left;
}

.layout-editor-page .media-picker-grid .media-picker-name {
    padding: 0.45rem 0.55rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.layout-editor-page #editor-media-picker:has(.media-picker-grid > *)
    .media-picker-empty {
    display: none;
}

.layout-editor-page .editor-media-upload {
    display: grid;
    gap: 0.65rem;
    margin: 1rem 0 0;
    padding: 0.75rem;
    border: 1px solid var(--color-border);
    border-radius: var(--surface-radius);
}

.layout-editor-page .editor-media-upload p {
    min-height: 1.5rem;
    margin: 0;
    color: var(--color-accent);
}

.layout-editor-page .editor-layers {
    display: flex;
    flex-direction: column-reverse;
    gap: 0.5rem;
    margin: 0;
    padding: 0 0 0 1.75rem;
}

.layout-editor-page .editor-layers button {
    width: 100%;
    overflow: hidden;
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.layout-editor-page .editor-layers button[aria-current="true"] {
    border-color: var(--color-accent);
    color: var(--color-accent);
}

/* Dialog sheets become centered modals at BottomSheet's breakpoint. */
@media (min-width: 48rem) {
    .layout-editor-page .editor-sheet > header {
        position: sticky;
        z-index: 1;
        top: 0;
        padding-top: 0.75rem;
    }

    /* Its short controls would stretch across the default width. The
       open state matches BottomSheet's specificity. */
    .layout-editor-page dialog.settings-sheet[open] {
        width: min(calc(100% - 2rem), 28rem);
    }
}

@media (min-width: 64rem) {
    .layout-editor-page .media-picker-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    .layout-editor-page .element-properties .bottom-sheet-handle {
        display: none;
    }

    .layout-editor-page .element-properties .properties-close {
        display: grid;
    }
}

@media (min-width: 64rem) {
    .layout-editor-page .element-properties {
        position: absolute;
        z-index: 1;
        top: 0;
        right: 0;
        bottom: 0;
        left: auto;
        width: 20rem;
        max-height: none;
        padding: 0 1rem 1rem;
        border-top: 0;
        border-left: 1px solid var(--color-border);
        border-radius: 0;
        box-shadow: none;
        transition: none;
    }

    /* A closed side panel is only invisible; it does not slide. */
    .layout-editor-page .element-properties.closed {
        transform: none;
    }

    .layout-editor-page .element-properties > header {
        position: sticky;
        z-index: 1;
        top: 0;
        padding-top: 0.75rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .layout-editor-page [data-save-state="saving"] .lucide-loader-circle {
        animation: none;
    }

    .layout-editor-page *,
    .layout-editor-page *::before,
    .layout-editor-page *::after {
        scroll-behavior: auto;
    }
}

.layout-editor-page .layout-custom-dimensions:not([hidden]) {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    min-width: 0;
    gap: 0.75rem;
    margin: 0;
    padding: 0;
    border: 0;
}

.layout-editor-page .layout-custom-dimensions legend {
    margin-bottom: 0.35rem;
    padding: 0;
}

.layout-editor-page .layout-custom-dimensions p {
    grid-column: 1 / -1;
}

/* Linked layouts may have another size, so they show no grid. */
.layout-editor-page .layout-canvas.read-only {
    background-image: none;
}

.layout-editor-page .layout-canvas.read-only .layout-element {
    cursor: default;
}

/* The item bar follows the canvas like the name above it. It has no
   surface of its own, so it reads as a caption rather than a second
   toolbar, and smaller controls and text keep it secondary to the toolbar.
   The component places its top center under the canvas's center and
   scales it from there; the translation keeps it centered as it grows. */
.layout-editor-page .editor-item-bar {
    --control-height: 2.25rem;
    position: absolute;
    z-index: 3;
    top: 0;
    left: 0;
    display: flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.875rem;
    white-space: nowrap;
    transform-origin: top center;
}

.layout-editor-page .editor-item-bar .icon {
    width: 1rem;
    height: 1rem;
}

.layout-editor-page .editor-item-bar[hidden] {
    display: none;
}

.layout-editor-page .editor-item-bar > .app-button {
    flex: none;
    min-width: var(--control-height);
    padding: 0.5rem;
}

/* A minimum width keeps the next button in place while the position gains
   digits. */
.layout-editor-page .editor-item-bar > .editor-item-title {
    display: flex;
    min-width: 6rem;
    padding: 0.25rem 0.6rem;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
}

.layout-editor-page .editor-item-position {
    flex: none;
    font-variant-numeric: tabular-nums;
}

/* The Linked and aspect-ratio badges hold only an icon. */
.layout-editor-page .editor-item-flag {
    display: inline-flex;
    flex: none;
    padding: 0.2rem;
}

.layout-editor-page .editor-item-flag .icon {
    width: 0.95rem;
    height: 0.95rem;
}

.layout-editor-page :is(.editor-item-heading, .playlist-item) [hidden] {
    display: none;
}

/* The empty playlist offers the add buttons where the canvas would be. */
.layout-editor-page .playlist-empty {
    position: absolute;
    top: 50%;
    right: 1rem;
    left: 1rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.5rem;
    color: #fff;
    text-align: center;
    transform: translateY(-50%);
}

.layout-editor-page .playlist-empty[hidden] {
    display: none;
}

.layout-editor-page .playlist-empty p {
    margin: 0 0 0.5rem;
}

.layout-editor-page :is(.playlist-empty, .editor-sheet-footer) button {
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: 0.5rem;
    white-space: nowrap;
}

.layout-editor-page .playlist-items {
    display: grid;
    gap: 0.75rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.layout-editor-page .playlist-items-empty {
    display: none;
    margin: 0;
    padding: 2rem 1rem;
    border: 1px dashed var(--color-border-strong);
    border-radius: var(--control-radius);
    color: var(--color-text-muted);
    text-align: center;
}

.layout-editor-page .playlist-items:empty + .playlist-items-empty {
    display: block;
}

/* A row is a card: the drag handle spans the line with the number, name
   and badges, and the line under it with the duration and actions. The
   row is the positioning parent of its drop line. The title shows the
   item, and the current item's card is marked. */
.layout-editor-page .playlist-item {
    position: relative;
    display: grid;
    grid-template-columns: 1.5rem minmax(0, 1fr);
    gap: 0.5rem 0.25rem;
    padding: 0.5rem 0.375rem 0.5rem 0;
    border: 1px solid var(--color-border-subtle);
    border-radius: var(--control-radius);
    background: var(--color-surface);
}

.layout-editor-page .playlist-item:hover {
    background: var(--color-surface-hover);
}

.layout-editor-page .playlist-item[aria-current="true"] {
    border-color: var(--color-accent);
    background: var(--color-accent-tint);
}

/* Only the handle starts a drag, so the rest of the list scrolls
   natively. */
.layout-editor-page .playlist-item-drag {
    display: grid;
    grid-row: span 2;
    align-self: stretch;
    min-height: 0;
    padding: 0;
    border: 0;
    place-items: center;
    color: var(--color-text-subtle);
    background: transparent;
    cursor: grab;
    touch-action: none;
}

.layout-editor-page .playlist-item-drag:hover {
    color: var(--color-text);
}

/* The title is the row's widest target. A long name shrinks in its own
   line, so it never pushes the badges or the controls. */
.layout-editor-page .playlist-item-title {
    display: flex;
    min-width: 0;
    min-height: 2.25rem;
    align-items: center;
    gap: 0.5rem;
    padding: 0;
    border: 0;
    color: inherit;
    background: transparent;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.layout-editor-page .playlist-item-number {
    flex: none;
    min-width: 1.25rem;
    color: var(--color-text-muted);
    font-size: 0.875rem;
    font-variant-numeric: tabular-nums;
}

.layout-editor-page .playlist-item-name {
    min-width: 0;
    flex: 1;
    overflow: hidden;
    font-weight: 500;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The duration and the actions are compact, so the name stays the row's
   main line. */
.layout-editor-page .playlist-item-controls {
    --control-height: 2.25rem;
    display: flex;
    flex-wrap: wrap;
    grid-column: 2;
    align-items: center;
    gap: 0.375rem;
    font-size: 0.8125rem;
}

.layout-editor-page .playlist-item-actions {
    display: flex;
    gap: inherit;
}

/* The Delete button's form only carries the confirmation. */
.layout-editor-page .playlist-item-remove-form {
    display: contents;
}

.layout-editor-page .playlist-item-controls .app-button {
    min-height: var(--control-height);
    padding: 0 0.55rem;
    font-weight: 500;
    white-space: nowrap;
}

/* Save to Layouts and Unlink share a width, so the line does not shift
   between them. */
.layout-editor-page .playlist-item-controls .playlist-item-link {
    min-width: 7.5rem;
}

.layout-editor-page .playlist-item-controls .playlist-item-icon {
    width: var(--control-height);
    padding: 0;
}

/* The duration reads as one control: the timer icon, the number and its
   unit share a border, and the field shows the focus ring for the input
   inside it. The number's spin buttons are hidden; they crowd the short
   field, and phones show a numeric keypad. The number keeps 1rem text
   because iOS Safari zooms into a focused input with smaller text. */
.layout-editor-page .playlist-item-duration {
    display: flex;
    box-sizing: border-box;
    flex: none;
    height: var(--control-height);
    align-items: center;
    gap: 0.25rem;
    padding: 0 0.5rem;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--control-radius);
    color: var(--color-text-muted);
    background: var(--color-bg);
    cursor: text;
}

/* A single layout stays on screen without a timer, so its duration is
   disabled and dimmed like a disabled button. */
.layout-editor-page .playlist-item-duration:has(input:disabled) {
    cursor: default;
    opacity: 0.55;
}

.layout-editor-page .playlist-item-duration:focus-within {
    outline: var(--focus-ring);
    outline-offset: 2px;
}

.layout-editor-page .playlist-item-controls .icon {
    flex: none;
    width: 0.875rem;
    height: 0.875rem;
}

.layout-editor-page .playlist-item-duration input {
    width: 3ch;
    min-width: 0;
    min-height: 0;
    padding: 0;
    border: 0;
    color: var(--color-text);
    background: transparent;
    box-shadow: none;
    font: inherit;
    font-size: 1rem;
    font-variant-numeric: tabular-nums;
    text-align: right;
    appearance: textfield;
    -webkit-user-select: text;
    user-select: text;
}

.layout-editor-page .playlist-item-duration input::-webkit-inner-spin-button,
.layout-editor-page .playlist-item-duration input::-webkit-outer-spin-button {
    margin: 0;
    appearance: none;
}

.layout-editor-page .playlist-item-duration input:focus-visible {
    outline: none;
}

/* The dragged row follows the pointer on its own layer; a line on one
   other row shows where it will drop, drawn above the dragged row and
   centered in the gap between rows. Nothing else moves. */
.layout-editor-page .playlist-item.dragging {
    z-index: 1;
    border-color: var(--color-accent);
    background: var(--color-surface);
    will-change: transform;
}

.layout-editor-page .playlist-item.dragging .playlist-item-drag {
    color: var(--color-accent);
    cursor: grabbing;
}

.layout-editor-page .playlist-item:is(.drop-before, .drop-after)::after {
    content: "";
    position: absolute;
    right: 0;
    left: 0;
    z-index: 2;
    height: 2px;
    background: var(--color-accent);
}

.layout-editor-page .playlist-item.drop-before::after {
    top: calc(-0.375rem - 2px);
}

.layout-editor-page .playlist-item.drop-after::after {
    bottom: calc(-0.375rem - 2px);
}

/* A sheet footer's buttons stay at the bottom of the sheet while it
   scrolls. The footer takes over the sheet's bottom padding: the negative
   margin removes it below the content, and the negative offset lets the
   sticky footer cover it, since sticking stops at the padding. */
.layout-editor-page .editor-sheet-footer {
    position: sticky;
    z-index: 1;
    bottom: calc(-1 * max(1rem, env(safe-area-inset-bottom)));
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 0.75rem calc(-1 * var(--bottom-sheet-inline-padding))
        calc(-1 * max(1rem, env(safe-area-inset-bottom)));
    padding: 0.75rem var(--bottom-sheet-inline-padding)
        max(1rem, env(safe-area-inset-bottom));
    border-top: 1px solid var(--color-border-subtle);
    background: var(--color-bg);
}

/* On mobile, the More sheet's Menu button sits where the ActionBar puts
   Menu on other pages: the bar's offset plus its padding, inside the
   sheet's border. The last group's padding already spaces it. */
.layout-editor-page .settings-footer {
    margin-top: 0;
    padding: 0.5rem 1rem
        calc(max(0.5rem, env(safe-area-inset-bottom)) + 0.5rem);
}

.layout-editor-page .settings-footer .app-button {
    padding-right: 0.875rem;
    padding-left: 0.75rem;
}

/* The centered modal keeps its own edge spacing. */
@media (min-width: 48rem) {
    .layout-editor-page .settings-footer {
        padding: 0.75rem var(--bottom-sheet-inline-padding)
            max(1rem, env(safe-area-inset-bottom));
    }
}

.layout-editor-page .layout-picker-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.layout-editor-page .layout-picker-list button {
    display: flex;
    width: 100%;
    justify-content: space-between;
    gap: 0.75rem;
    text-align: left;
}

.layout-editor-page .layout-picker-list li + li {
    margin-top: 0.5rem;
}

/* The preview fills the window. The component fits the screen, framed
   like the editor's canvas, with the controls under it, and letterboxes
   the layout in the screen with bars in the layout's background colour,
   as the player does. */
.layout-editor-page .editor-preview {
    box-sizing: border-box;
    width: 100%;
    max-width: none;
    height: 100%;
    max-height: none;
    margin: 0;
    padding: 0;
    border: 0;
    overflow: hidden;
    /* A dialog's default colour is the browser's, not the page's. */
    color: var(--color-text);
    background: var(--color-stage);
}

.layout-editor-page .editor-preview:focus {
    outline: none;
}

.layout-editor-page .editor-preview-screen {
    position: absolute;
    top: 0;
    left: 0;
    overflow: hidden;
    transform-origin: top left;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}

/* Empty text shows nothing on a screen, so it has no outline here. */
.layout-editor-page .editor-preview .layout-element {
    outline: none;
}

/* The component sets the top under the screen. */
.layout-editor-page .editor-preview-controls {
    position: absolute;
    top: 0;
    left: 50%;
    width: max-content;
    transform: translateX(-50%);
}

/* ui/pages/LayoutsNew/LayoutsNew.css */
.layouts-new-page .layout-custom-dimensions {
    display: grid;
    gap: 1rem;
}

.layouts-new-page .layout-custom-dimensions legend {
    margin-bottom: 0.75rem;
}

/* ui/pages/Legal/Legal.css */
/* Long text reads better wider than the signed-out form cards. */
.auth .auth-card.legal {
    width: min(100%, 42rem);
}

/* ui/pages/Media/Media.css */
.media-page .media-upload {
    margin-bottom: 1.5rem;
}

.media-page .media-upload h2 {
    margin: 0;
}

.media-page .media-upload.dragging {
    border: 1px dashed var(--color-accent);
    background: var(--color-accent-tint);
}

/* fetch reports no upload progress, so the bar is indeterminate. */
.media-page .media-upload-progress {
    display: grid;
    gap: 0.35rem;
    margin: 0;
}

.media-page .media-upload-progress progress {
    width: 100%;
    accent-color: var(--color-accent);
}

/* Touch-only devices cannot drag files, so the hint shows only with a
   precise hovering pointer. */
.media-page .media-drop-hint {
    display: none;
}

@media (hover: hover) and (pointer: fine) {
    .media-page .media-drop-hint {
        display: block;
    }
}

/* The two views are links, so the URL and history follow the switch. */
.media-page .search-field {
    max-width: 32rem;
}

/* Two columns on phones; the thumbnail size in media_thumbnail.go covers
   the widest card either rule allows. */
.media-page .media-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
    gap: 0.75rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.media-page .media-card {
    min-width: 0;
    overflow: hidden;
}

.media-page .media-card:hover {
    border-color: var(--color-border-hover);
}

.media-page .media-card-open {
    display: block;
    color: inherit;
    text-decoration: none;
}

.media-page .media-card-open:focus-visible {
    outline-offset: -2px;
}

.media-page .media-card-name {
    display: -webkit-box;
    padding: 0.5rem 0.6rem;
    overflow: hidden;
    overflow-wrap: anywhere;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
}

@media (min-width: 48rem) {
    .media-page .media-grid {
        grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    }
}

/* A solid outline keeps the empty state from reading as a drop zone next to
   the upload form. */
.media-page .media-empty {
    border-style: solid;
    border-color: var(--color-border);
}

/* The server always renders the message; it shows only when no card or
   converting video remains, including after the last visible card is deleted
   in place. */
.media-page #media-list:has(.media-card, .media-upload-item) .media-empty {
    display: none;
}

/* ui/pages/MediaDetail/MediaDetail.css */
.media-detail-page .media-detail-media,
.media-detail-page .media-detail-section {
    margin-bottom: 1rem;
}

/* The stage spans the card's width and letterboxes the media in the
   editor's stage colour, like a screen. The card clips its corners. */
.media-detail-page .media-detail-media {
    overflow: hidden;
}

.media-detail-page .media-detail-stage {
    display: grid;
    place-items: center;
    background: var(--color-stage);
}

/* The width and height attributes set the aspect ratio. The media shows
   at most at its own size, shrinking to the card's width and to a
   height that keeps the details below it in view. */
.media-detail-page .media-detail-preview {
    display: block;
    max-width: 100%;
    height: auto;
    max-height: 70dvh;
    object-fit: contain;
}

/* Every card fills the page width. The name keeps the usual form width,
   since names are short; the description takes the whole card. */
.media-detail-page .media-detail-form {
    max-width: none;
}

.media-detail-page .media-detail-name {
    max-width: 32rem;
}

/* The media's facts follow the fields under a rule. */
.media-detail-page .media-detail-details {
    gap: 0.75rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--color-border-subtle);
}

.media-detail-page .media-detail-section h2 {
    margin: 0 0 0.5rem;
}

/* Each document is a row: its link, then whether it is a Layout or a
   Playlist. */
.media-detail-page .media-detail-usage {
    margin-bottom: 0.75rem;
}

.media-detail-page .media-detail-usage > li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.25rem 1rem;
    padding: 0.75rem 0;
}

/* ui/pages/PairScreen/PairScreen.css */
.pair-screen-page {
    display: grid;
    min-height: 100vh;
    min-height: 100dvh;
    place-items: center;
}

.pair-screen-page .pair-screen-card {
    width: min(100%, 32rem);
}

.pair-screen-page h1 {
    margin-bottom: 0.5rem;
}

/* ui/pages/Player/Player.css */
/* A display shows no pointer, selection, or pull-to-refresh. A class
   instead of :has(), which Chromium 94 lacks, scopes the root rule. */
.player-root {
    overscroll-behavior: none;
}

.player-page {
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
    overscroll-behavior: none;
    background: #000;
    cursor: none;
    -webkit-user-select: none;
    user-select: none;
}

.player-page .player-status {
    display: grid;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    padding: 2rem;
    place-items: center;
    text-align: center;
}

.player-page .pairing-code {
    display: block;
    margin-top: 1rem;
    color: var(--color-accent);
    font-size: clamp(2rem, 7vmin, 6rem);
    letter-spacing: 0.08em;
    white-space: nowrap;
}

.player-page .pairing-qr {
    display: block;
    width: clamp(12rem, 30vmin, 24rem);
    height: auto;
    margin: 1.5rem auto 0 auto;
}

.player-page .pairing-help {
    margin: 1rem 0 0 0;
    font-size: clamp(1rem, 2.5vmin, 1.5rem);
}

.player-page .player-viewport {
    width: 100%;
    height: 100%;
}

.player-page .player-playback,
.player-page .player-stage {
    width: 100%;
    height: 100%;
}

/* Datastar owns the hidden candidate; the runner owns this visible stage. */
.player-page #player-stage {
    width: 100%;
    height: 100%;
}

/* ui/pages/Profile/Profile.css */
.profile-page .app-page-top strong {
    color: var(--color-text);
}

.profile-page .profile-section {
    margin-bottom: 0.75rem;
}

.profile-page h2 {
    margin: 0 0 1rem;
}

.profile-page .profile-picture {
    display: grid;
    align-items: start;
    gap: 1.25rem;
}

.profile-page .profile-picture > .avatar {
    width: 6rem;
    height: 6rem;
    font-size: 2.25rem;
}

.profile-page .profile-sessions-heading {
    justify-content: space-between;
    margin-bottom: 0.5rem;
}

.profile-page .profile-sessions-heading h2 {
    margin: 0;
}

/* Each device is a row: its name and times, then its button. */
.profile-page .profile-session {
    display: grid;
    gap: 0.75rem;
    padding: 1rem 0;
}

.profile-page .profile-session:last-child {
    padding-bottom: 0;
}

.profile-page .profile-session h3 {
    font-size: 1rem;
}

.profile-page .profile-session .app-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0 1rem;
    margin: 0.25rem 0 0;
    font-size: 0.875rem;
}

.profile-page .profile-session form {
    justify-self: start;
}

@media (min-width: 40rem) {
    .profile-page .profile-picture {
        grid-template-columns: 6rem minmax(0, 1fr);
    }

    .profile-page .profile-session {
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: center;
    }
}

/* ui/pages/Screen/Screen.css */
.screen-page .screen-form {
    display: grid;
    gap: 1rem;
}

.screen-page .screen-playlist-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.screen-page .screen-diagnostics {
    padding: 0.25rem 1.25rem;
}

.screen-page .screen-diagnostics > div {
    padding: 1rem 0;
}

.screen-page .screen-diagnostics > div + div {
    border-top: 1px solid var(--color-border);
}

/* Rows keep their height while a value does not apply. */
.screen-page .screen-diagnostics dd {
    min-height: 1lh;
}

.screen-page .diagnostic-error {
    color: var(--color-danger);
}

.screen-page .screen-preview-live[hidden],
.screen-page .screen-preview-back[hidden] {
    display: none;
}

.screen-page .screen-layout {
    display: grid;
    align-items: start;
    gap: 1rem;
}

/* The settings and the diagnostics stack in the left half, and the
   preview fills the right half. */
@media (min-width: 64rem) {
    .screen-page .screen-layout {
        gap: 1.5rem;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        grid-template-areas:
            "form preview"
            "diagnostics preview";
        grid-template-rows: auto 1fr;
    }

    .screen-page .screen-form {
        grid-area: form;
    }

    .screen-page .screen-preview-card {
        grid-area: preview;
    }

    .screen-page .screen-diagnostics {
        grid-area: diagnostics;
        align-self: start;
    }
}

.screen-page .screen-preview-card {
    display: grid;
    gap: 1rem;
}

.screen-page .screen-preview-heading {
    justify-self: stretch;
}

/* Back to live is a button of the Live badge's size, so they take turns
   without moving anything. Its icon fits the badge's line. */
.screen-page .screen-preview-back {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    color: var(--color-text);
    background: none;
    font-family: inherit;
    cursor: pointer;
}

.screen-page .screen-preview-back:hover {
    border-color: var(--color-border-hover);
    background: var(--color-surface-hover);
}

.screen-page .screen-preview-back .icon {
    width: 1em;
    height: 1em;
}

.screen-page .screen-preview .app-hint {
    justify-self: stretch;
    text-align: center;
}

/* The fit note and a pick's Save hint share one cell, so the slot is as
   tall as the taller one and picking moves nothing. Hidden notes keep
   their space; visibility also hides them from assistive technology. */
.screen-page .screen-preview-notes {
    display: grid;
    justify-self: stretch;
}

.screen-page .screen-preview-notes > p {
    grid-area: 1 / 1;
    margin: 0;
}

.screen-page .screen-preview-notes[data-picked] .screen-preview-fit,
.screen-page .screen-preview-notes:not([data-picked])
    .screen-preview-pick-hint {
    visibility: hidden;
}

/* The screen's own actions, set apart from the preview. The buttons sit
   side by side when both labels fit and stack otherwise, so Pause and
   Resume do not move them. The pause form hides without a Playlist. */
.screen-page .screen-preview-actions {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
    gap: 0.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--color-border-subtle);
}

.screen-page .screen-preview-actions form {
    display: grid;
}

.screen-page .screen-preview-actions form:has(> [hidden]) {
    display: none;
}

.screen-page .screen-preview {
    display: grid;
    justify-items: center;
    gap: 0.75rem;
}

/* The frame's content box takes the screen's shape inside the bezel. Its
   height leaves room for the page heading, the card's heading, controls,
   note, and screen actions, and the action bar, so the whole card fits in
   the window. The cap keeps it reasonable on tall monitors. */
.screen-page .screen-preview-frame {
    --screen-bezel: 0.5rem;
    --screen-preview-height: clamp(12rem, 100dvh - 34rem, 36rem);
    width: min(
        100% - 2 * var(--screen-bezel),
        calc(var(--screen-preview-height) * var(--screen-width) /
            var(--screen-height))
    );
    aspect-ratio: var(--screen-width) / var(--screen-height);
    border: var(--screen-bezel) solid var(--color-screen-frame);
    background: #000;
}

/* The script sets the stage colour to the layout's background, which
   fills the letterbox bars as on a screen. */
.screen-page .screen-preview-stage {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
}

.screen-page .screen-preview-ready {
    display: grid;
    box-sizing: border-box;
    height: 100%;
    margin: 0;
    padding: 1rem;
    place-items: center;
    color: var(--color-text-muted);
    text-align: center;
}

/* In the card the open dialog and its screen add no boxes, so the frame
   and the controls lay out as the preview's own children. They still
   pass on the dialog's colour, which is the browser's, not the page's. */
.screen-page .screen-preview-dialog[open]:not(:modal),
.screen-page .screen-preview-dialog:not(:modal) .screen-preview-screen {
    display: contents;
    color: inherit;
}

/* Full screen fills the window like the editor's preview: the framed
   screen as large as fits, with the controls under it. */
.screen-page .screen-preview-dialog:modal {
    display: grid;
    box-sizing: border-box;
    /* The heading, the screen, the controls, and the notes. */
    grid-template-rows: auto minmax(0, 1fr) auto auto;
    justify-items: center;
    gap: 0.75rem;
    width: 100%;
    max-width: none;
    height: 100%;
    max-height: none;
    margin: 0;
    padding: 1rem;
    border: 0;
    /* A dialog's default colour is the browser's, not the page's. */
    color: var(--color-text);
    background: var(--color-stage);
}

.screen-page .screen-preview-dialog:focus {
    outline: none;
}

/* The screen's box is the space left for the frame, which keeps its
   bezel and the screen's shape. */
.screen-page .screen-preview-dialog:modal .screen-preview-screen {
    display: grid;
    justify-self: stretch;
    place-items: center;
    container-type: size;
}

.screen-page .screen-preview-dialog:modal .screen-preview-frame {
    --screen-preview-height: calc(100cqh - 2 * var(--screen-bezel));
}

.screen-page .screen-preview-dialog:not(:modal) [data-close-dialog],
.screen-page .screen-preview-dialog:modal [data-preview-fullscreen] {
    display: none;
}

/* ui/pages/Screens/Screens.css */
.screens-page .screen-counts {
    display: grid;
    width: min(100%, 28rem);
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin: 0 0 1.5rem;
}

.screens-page .screen-counts > div {
    min-width: 0;
    padding: 0.75rem;
}

.screens-page .screen-counts > div + div {
    border-left: 1px solid var(--color-border);
}

.screens-page .screen-counts dd {
    margin: 0.25rem 0 0;
    font-size: 1.75rem;
    font-weight: 650;
    line-height: 1.1;
}

.screens-page .screen-counts > .has-offline dd {
    color: var(--color-danger);
}

.screens-page .screen-content {
    display: grid;
    gap: 0.1rem;
    min-width: 0;
    color: var(--color-text-muted);
    overflow-wrap: anywhere;
}

/* The action spans the title and assignment rows. */
@media (min-width: 40rem) {
    .screens-page .screen-card > :not(.app-card-action) {
        grid-column: 1;
    }

    .screens-page .screen-card > .app-card-action {
        grid-column: 2;
        grid-row: 1 / 3;
    }
}

/* ui/pages/SuperAdminDashboard/SuperAdminDashboard.css */
.super-admin-dashboard-page .super-admin-dashboard-stats {
    display: grid;
    gap: 1rem;
}

.super-admin-dashboard-page .super-admin-dashboard-title {
    margin: 0;
    font-size: 1.125rem;
    font-weight: 650;
    line-height: 1.3;
}

.super-admin-dashboard-page .super-admin-dashboard-total {
    margin: 0.25rem 0 0;
    font-size: 2.25rem;
    font-weight: 650;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.super-admin-dashboard-page .super-admin-dashboard-parts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1.5rem;
    margin: 0.5rem 0 0;
}

.super-admin-dashboard-page .super-admin-dashboard-parts > div {
    display: flex;
    gap: 0.4rem;
}

.super-admin-dashboard-page .super-admin-dashboard-parts dt {
    color: var(--color-text-muted);
}

.super-admin-dashboard-page .super-admin-dashboard-parts dd {
    margin: 0;
    font-weight: 650;
    font-variant-numeric: tabular-nums;
}

.super-admin-dashboard-page .super-admin-dashboard-parts .has-alert dd {
    color: var(--color-danger);
}

/* ui/pages/SuperAdminUser/SuperAdminUser.css */
.super-admin-user-page .super-admin-user-section {
    margin-bottom: 0.75rem;
}

.super-admin-user-page h2 {
    margin: 0 0 0.5rem;
}

.super-admin-user-page .super-admin-user-details {
    gap: 0.75rem;
}

/* Each workspace is a row: its link, then its kind and the role. */
.super-admin-user-page .super-admin-user-workspaces > li {
    display: grid;
    gap: 0.15rem;
    padding: 0.75rem 0;
}

.super-admin-user-page .super-admin-user-workspaces > li:last-child {
    padding-bottom: 0;
}

/* ui/pages/SuperAdminWorkspace/SuperAdminWorkspace.css */
.super-admin-workspace-page .super-admin-workspace-details {
    gap: 0.75rem;
}

.super-admin-workspace-page .super-admin-workspace-open {
    margin: 1.25rem 0 0;
}

/* ui/pages/Support/Support.css */
/* An issue's number follows its subject, muted, on the cards and in the
   issue page's heading. */
.support-issue-number {
    color: var(--color-text-subtle);
    font-weight: 400;
}

/* ui/pages/SupportIssue/SupportIssue.css */
.support-issue-page .support-issue-details {
    gap: 0.75rem;
}

.support-issue-page .support-issue-details-card {
    margin-bottom: 0.75rem;
}

.support-issue-page .support-thread {
    margin-bottom: 0.75rem;
}

.support-issue-page .support-replies {
    display: grid;
    gap: 0.75rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* The message that opened the issue reads as its description: a stronger
   edge and larger text than the replies. */
.support-issue-page .support-original {
    border-color: var(--color-border-strong);
}

.support-issue-page .support-original .support-message-body {
    font-size: 1.05rem;
}

.support-issue-page .support-replies-heading {
    margin: 1.5rem 0 0.75rem;
}

.support-issue-page .support-message {
    display: grid;
    min-width: 0;
    gap: 0.5rem;
    padding: 1rem 1.25rem;
}

/* An accent rule on the leading edge sets support's replies apart. */
.support-issue-page .support-message.from-support {
    border-left: 3px solid var(--color-accent);
}

.support-issue-page .support-message-header {
    display: grid;
    justify-items: start;
    gap: 0.15rem;
}

.support-issue-page .support-message-author {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.75rem;
}

.support-issue-page .support-message-header time {
    color: var(--color-text-subtle);
    font-size: 0.875rem;
}

/* Messages are plain text; their line breaks and long words stay. */
.support-issue-page .support-message-body {
    margin: 0;
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.support-issue-page .support-reopen-hint {
    margin-bottom: 1rem;
}

@media (min-width: 40rem) {
    .support-issue-page .support-message {
        padding: 1.25rem 1.5rem;
    }
}

/* ui/pages/WorkspaceForm/WorkspaceForm.css */
.workspace-form :is(.workspace-form-tier, .workspace-form-custom) {
    display: grid;
    align-content: start;
    gap: 0.5rem;
}

.workspace-form :is(.workspace-form-tier, .workspace-form-custom)
    :is(h2, p) {
    margin: 0;
}

/* Both cards fill the page width; the name field keeps the usual form
   width, since names are short. */
.workspace-form .app-form {
    max-width: none;
}

.workspace-form .app-form > label {
    max-width: 32rem;
}

.workspace-form .workspace-form-custom {
    margin-top: 0.75rem;
}

/* ui/pages/WorkspaceInvite/WorkspaceInvite.css */
.workspace-invite .workspace-invite-card {
    display: grid;
    gap: 1rem;
}

.workspace-invite .workspace-invite-form {
    max-width: 24rem;
}

.workspace-invite .workspace-invite-roles {
    display: grid;
    gap: 0.5rem;
}

/* ui/pages/WorkspaceSettings/WorkspaceSettings.css */
.workspace-settings .workspace-settings-card {
    margin: 0 0 0.75rem;
}

.workspace-settings h2 {
    margin: 0 0 1rem;
}

/* ui/pages/WorkspaceUsers/WorkspaceUsers.css */
.workspace-users h2 {
    margin: 0 0 1rem;
}

.workspace-users .workspace-users-pending {
    margin-top: 2rem;
}

/* One row per person. On narrow screens the name takes the first line and
   the controls the second; wider screens fit everything on one line. The
   remove column keeps its width when a row has no remove button, so the
   role selects stay aligned. */
.workspace-users .workspace-users-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--control-height);
    grid-template-areas:
        "identity identity"
        "action remove";
    align-items: center;
    gap: 0.5rem 0.75rem;
    padding: 0.75rem 1rem;
}

.workspace-users .workspace-users-identity {
    display: grid;
    grid-area: identity;
    min-width: 0;
}

.workspace-users .workspace-users-identity > * {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.workspace-users .workspace-users-identity > span {
    color: var(--color-text-muted);
    font-size: 0.875rem;
}

.workspace-users .workspace-users-action {
    display: flex;
    grid-area: action;
    flex-wrap: wrap;
    gap: 0.5rem;
    justify-self: start;
}

.workspace-users .workspace-role-form select {
    width: 10rem;
}

.workspace-users .workspace-users-remove {
    grid-area: remove;
}

.workspace-users .workspace-users-remove button:hover:not(:disabled) {
    color: var(--color-danger);
    background: var(--color-danger-tint);
}

@media (min-width: 40rem) {
    .workspace-users .workspace-users-item {
        grid-template-columns:
            minmax(0, 1fr)
            auto
            var(--control-height);
        grid-template-areas: "identity action remove";
    }
}

