/* openMarquee UI — phone-first, no framework, no third-party styles. */

/* Bundled display fonts — shipped in ui/fonts/, served by the device's
   own origin so captive-portal clients don't need internet. All SIL
   OFL-1.1 or Apache-2.0 (redistributable). See ui/fonts/LICENSES.md.
   font-weight tells the browser what weights the file actually provides,
   so ctx.font in canvas matches a real glyph set instead of triggering
   synthesized fake-bold. Variable-axis fonts (Inter / Oswald / Cinzel)
   declare a range; single-weight files declare their exact weight. */
@font-face { font-family: "Inter";              src: url("fonts/inter.ttf") format("truetype"); font-weight: 100 900; font-display: swap; }
@font-face { font-family: "Oswald";             src: url("fonts/oswald.ttf") format("truetype"); font-weight: 200 700; font-display: swap; }
@font-face { font-family: "Bebas Neue";         src: url("fonts/bebas-neue.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Roboto Slab";        src: url("fonts/roboto-slab.ttf") format("truetype"); font-weight: 700; font-display: swap; }
@font-face { font-family: "Caveat Brush";       src: url("fonts/caveat-brush.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Permanent Marker";   src: url("fonts/permanent-marker.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Cinzel";             src: url("fonts/cinzel.ttf") format("truetype"); font-weight: 400 900; font-display: swap; }
@font-face { font-family: "UnifrakturCook";     src: url("fonts/unifrakturcook.ttf") format("truetype"); font-weight: 700; font-display: swap; }
@font-face { font-family: "Rye";                src: url("fonts/rye.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Pacifico";           src: url("fonts/pacifico.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Sedgwick Ave Display"; src: url("fonts/sedgwick-ave-display.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Bowlby One SC";      src: url("fonts/bowlby-one-sc.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Anton";              src: url("fonts/anton.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Archivo Black";      src: url("fonts/archivo-black.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Alfa Slab One";      src: url("fonts/alfa-slab-one.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Playfair Display";   src: url("fonts/playfair-display.ttf") format("truetype"); font-weight: 400 900; font-display: swap; }
@font-face { font-family: "DM Serif Display";   src: url("fonts/dm-serif-display.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "VT323";              src: url("fonts/vt323.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "JetBrains Mono";     src: url("fonts/jetbrains-mono.ttf") format("truetype"); font-weight: 100 800; font-display: swap; }
@font-face { font-family: "Space Mono";         src: url("fonts/space-mono.ttf") format("truetype"); font-weight: 400 700; font-display: swap; }
@font-face { font-family: "Caveat";             src: url("fonts/caveat.ttf") format("truetype"); font-weight: 400 700; font-display: swap; }
@font-face { font-family: "Reenie Beanie";      src: url("fonts/reenie-beanie.ttf") format("truetype"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Shadows Into Light"; src: url("fonts/shadows-into-light.ttf") format("truetype"); font-weight: 400; font-display: swap; }

:root {
    --bg: #0e0e10;
    --panel: #19191c;
    --panel-border: #2a2a2f;
    --text: #e9e9ec;
    --muted: #8b8b92;
    --accent: #ffb84d;
    --danger: #ff6b6b;
    --success: #5dd39e;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
    font-size: 16px;
    line-height: 1.4;
}

header {
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--panel-border);
    position: sticky;
    top: 0;
    background: var(--bg);
    z-index: 10;
}

header h1 {
    margin: 0;
    font-size: 1.1rem;
    font-weight: 600;
    letter-spacing: 0.02em;
}

.brand-sign-name {
    display: inline;
    margin-left: 0.5rem;
    color: var(--accent);
    font-weight: 500;
}

.brand-sign-name:empty {
    display: none;
}

.brand-sign-name::before {
    content: "— ";
    color: var(--muted);
    font-weight: 400;
}

.shell {
    display: flex;
    align-items: stretch;
    min-height: calc(100vh - 3.25rem);
}

.sidebar {
    flex: 0 0 12rem;
    background: var(--panel);
    border-right: 1px solid var(--panel-border);
    padding: 1rem 0.5rem;
    position: sticky;
    top: 3.25rem;
    align-self: flex-start;
    height: calc(100vh - 3.25rem);
    overflow-y: auto;
}

.nav-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.nav-link {
    display: block;
    padding: 0.55rem 0.75rem;
    color: var(--muted);
    text-decoration: none;
    border-radius: 0.35rem;
    font-size: 0.95rem;
    letter-spacing: 0.01em;
}

.nav-link:hover { color: var(--text); background: rgba(255, 255, 255, 0.03); }
.nav-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.nav-link.active {
    color: var(--text);
    background: rgba(255, 184, 77, 0.12);
    border-left: 2px solid var(--accent);
    padding-left: calc(0.75rem - 2px);
    font-weight: 600;
}

.nav-group-heading {
    padding: 0.6rem 0.75rem 0.2rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--muted);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.nav-child {
    padding-left: 1.5rem;
    font-size: 0.9rem;
}
.nav-child.active {
    padding-left: calc(1.5rem - 2px);
}

/* Legacy `main` rule retired: it set max-width: 720px + margin: 0 auto,
   which collided with the new `.om-main` shell from the Claude Design
   redesign. The om-* shell handles its own sizing via flex: 1 +
   min-width: 0; this bare-element rule was capping the main column to
   720px even on wide screens AND (combined with the auto-margins)
   forcing it to render at the cap width on narrow viewports, cropping
   the playlist track + Save button on the right. */

.panel { display: block; }
.panel[hidden] { display: none; }

/* Panels-as-section-roots: the legacy stacked layout gave each child
   component a top border + spacing to separate it from the one above.
   When a component is the top of its section, that separator is a
   stray rule. Reach through the slot wrapper to the component itself. */
.panel > :first-child > .playlists,
.panel > :first-child > .schedule,
.panel > :first-child > .settings-view {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
}

/* Narrow viewports: sidebar becomes a horizontal tab strip at the top.
   Captive-portal users are on phones; they get a tab bar, not a drawer. */
@media (max-width: 720px) {
    .shell { flex-direction: column; }
    .sidebar {
        flex: 0 0 auto;
        width: 100%;
        position: sticky;
        top: 3.25rem;
        height: auto;
        border-right: none;
        border-bottom: 1px solid var(--panel-border);
        padding: 0.25rem 0.5rem;
    }
    .nav-list { flex-direction: row; gap: 0.25rem; overflow-x: auto; }
    .nav-link { white-space: nowrap; padding: 0.5rem 0.75rem; }
    .nav-link.active { border-left: none; border-bottom: 2px solid var(--accent); padding-left: 0.75rem; padding-bottom: calc(0.5rem - 2px); }
}

/* --- playlist track editor --- */

.playlist-track {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.playlist-track-header {
    display: flex;
    align-items: center;
    gap: 1rem;
}

.playlist-track-heading {
    margin: 0;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--muted);
    letter-spacing: 0.02em;
    flex: 1;
}

.playlist-track-hint {
    margin: 0;
    font-size: 0.8rem;
    color: var(--muted);
    opacity: 0.85;
}

/* --- multi-playlist browser at top of the Playlists subpage --- */
.playlist-browser {
    margin-bottom: 0.75rem;
}
.playlist-browser-list {
    list-style: none;
    margin: 0;
    padding: 0.35rem 0;
    display: flex;
    flex-direction: row;
    gap: 0.5rem;
    overflow-x: auto;
}
.playlist-browser-tile {
    flex: 0 0 auto;
    width: 7rem;
    /* Match heights across the strip — the action <button> stretches to
       whatever the tallest sibling needs (thumb + name + meta), so the
       "+ New" tile no longer reads as a stub. */
    display: flex;
    flex-direction: column;
}
.playlist-browser-tile-action {
    flex: 1 1 auto;
}
.playlist-browser-tile-action {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    width: 100%;
    padding: 0.55rem 0.8rem;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 0.35rem;
    color: var(--text);
    font-family: inherit;
    font-size: 0.85rem;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}
.playlist-browser-tile-action:hover {
    border-color: var(--accent);
    background: #111115;
}
.playlist-browser-tile--selected .playlist-browser-tile-action {
    border-color: var(--accent);
    background: #161616;
    box-shadow: 0 0 0 2px rgba(255, 184, 77, 0.35);
}
.playlist-browser-tile-thumb {
    width: 100%;
    aspect-ratio: var(--device-aspect, 4 / 3);
    object-fit: cover;
    background: #000;
    border-radius: 0.25rem;
    image-rendering: pixelated;
    margin-bottom: 0.25rem;
}
.playlist-browser-tile-thumb--empty {
    background: repeating-linear-gradient(
        135deg,
        #1c1c20 0 6px,
        #16161a 6px 12px
    );
}
.playlist-browser-tile-name {
    font-weight: 600;
}
.playlist-browser-tile-meta {
    font-size: 0.7rem;
    color: var(--muted);
}
/* --- inline preview (Playlists panel client-side simulator) --- */
.inline-preview {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: stretch;
    margin-bottom: 0.75rem;
}
.inline-preview-stage {
    position: relative;
    width: 100%;
    max-width: 32rem;
    background: #000;
    border-radius: 0.4rem;
    overflow: hidden;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06);
}
.inline-preview-canvas {
    display: block;
    width: 100%;
    height: 100%;
    image-rendering: pixelated;
}
.inline-preview-auto-text {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: clamp(1.5rem, 5vw, 3.5rem);
    font-weight: 700;
    color: #fff;
    text-shadow: 0 0 6px rgba(0, 0, 0, 0.8),
        1px 1px 0 rgba(0, 0, 0, 0.9),
        -1px -1px 0 rgba(0, 0, 0, 0.9);
    pointer-events: none;
    letter-spacing: 0.02em;
}
.inline-preview-idle {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--muted);
    font-size: 0.9rem;
    text-align: center;
    padding: 1rem;
    pointer-events: none;
}
/* The display: flex above wins over the UA default for [hidden],
   so re-state the visibility intent. Same for the auto-text overlay. */
.inline-preview-idle[hidden],
.inline-preview-auto-text[hidden] {
    display: none;
}
.inline-preview-transport {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    max-width: 32rem;
}
.inline-preview-play {
    width: 2.4rem;
    height: 2.4rem;
    padding: 0;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 999px;
    color: var(--text);
    font-size: 0.9rem;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}
.inline-preview-play:hover {
    border-color: var(--accent);
    background: #111115;
}
.inline-preview-scrub {
    flex: 1;
    accent-color: var(--accent);
}
.inline-preview-time {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8rem;
    color: var(--muted);
    min-width: 6rem;
    text-align: right;
}

/* Legacy .playlist-track-scroll wrapper styling — superseded by the
   .om-card.om-card-tight wrapper the template now uses. The class
   stays on the element (handlers + tests reference it) but no
   intrinsic styling here anymore. */
.playlist-track-scroll {
    /* min-width: 0 so the card can shrink with its flex parent
       instead of growing to fit min-content of the widest block. */
    min-width: 0;
}

/* Playlist track — vertical stack of horizontal blocks (Claude Design
   "vertical" variant). Each block is a row: grip · thumb · meta · big
   duration numeral · remove ×. Replaces the earlier horizontal-tiles
   layout. Existing JS query selectors (.track-block, .track-remove,
   .track-block-duration, .track-block-transition) are preserved so
   handlers + tests don't change. */
.playlist-track-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-height: 7rem;
    position: relative;
}

.playlist-track-list:empty::before {
    content: attr(data-empty-hint);
    color: var(--om-text-fade, var(--muted));
    font-size: 0.85rem;
    font-style: italic;
    padding: 2rem 1rem;
    display: block;
    width: 100%;
    text-align: center;
}

.track-block {
    display: flex;
    align-items: stretch;
    background: var(--om-surface, var(--panel));
    border: 1px solid var(--om-line, var(--panel-border));
    border-radius: var(--om-radius, 0.5rem);
    overflow: hidden;
    position: relative;
    user-select: none;
    -webkit-user-select: none;
    cursor: grab;
    /* Without min-width:0, the row's min-content width (grip + thumb +
       dur + meta natural-content) can push past the scroll container,
       cropping the right edge. min-width:0 lets the meta column shrink
       and ellipsis the title instead. */
    min-width: 0;
    width: 100%;
    box-sizing: border-box;
    transition: transform .18s cubic-bezier(.2,.7,.3,1),
                box-shadow .12s, border-color .12s;
}
.track-block:active { cursor: grabbing; }
.track-block:hover { border-color: var(--om-line-2, var(--panel-border)); }

/* Sortable's drag clone gets a glow + accent border so the operator
   sees clearly which block is moving. */
.track-block.sortable-chosen {
    border-color: var(--om-accent);
    box-shadow: 0 16px 36px rgba(0,0,0,.4),
                0 0 0 1px var(--om-accent),
                0 0 24px var(--om-accent-glow, rgba(255,184,77,.4));
}

.track-grip {
    width: 28px;
    display: flex; align-items: center; justify-content: center;
    color: var(--om-text-fade);
    background: var(--om-surface-2, rgba(255,255,255,0.03));
    border-right: 1px solid var(--om-line);
    flex-shrink: 0;
    cursor: grab;
}
.track-grip-dots {
    font-size: 14px;
    line-height: 1;
    letter-spacing: -2px;
    transform: rotate(0deg);
}

.track-block-thumb-wrap {
    position: relative;
    width: 70px;
    aspect-ratio: 2/1;
    flex-shrink: 0;
    background: var(--om-led-bg, #000);
    margin: 8px 0 8px 8px;
    border-radius: 6px;
    overflow: hidden;
}
.track-block-thumb {
    width: 100%;
    height: 100%;
    object-fit: cover;
    image-rendering: pixelated;
}

.track-block-meta {
    flex: 1; min-width: 0;
    padding: 10px 12px;
    display: flex; flex-direction: column;
    justify-content: center;
    gap: 4px;
}
.track-block-name {
    font-size: 13.5px; font-weight: 600;
    color: var(--om-text, var(--text));
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.track-block-sub {
    font-family: var(--om-mono);
    font-size: 11px;
    color: var(--om-text-fade);
    letter-spacing: 0.04em;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Big duration numeral on the right — accent color, click anywhere to
   prompt for a new value. The whole cell is the button. */
.track-block-dur {
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    padding: 0 14px;
    border-left: 1px dashed var(--om-line);
    min-width: 64px;
    background: transparent;
    border-top: 0; border-right: 0; border-bottom: 0;
    cursor: pointer;
    font-family: var(--om-display);
    color: inherit;
}
.track-block-dur:hover { background: rgba(255,255,255,0.03); }
.track-block-num {
    font-size: 20px; font-weight: 700;
    color: var(--om-accent);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}
.track-block-unit {
    font-size: 9.5px; letter-spacing: 0.14em;
    color: var(--om-text-fade); text-transform: uppercase;
    margin-top: 2px;
}

/* Transition chip lives inside the meta column under the title. */
.track-block-transition {
    align-self: flex-start;
    font-size: 10.5px;
    font-family: var(--om-mono);
    background: var(--om-surface-2, rgba(255,255,255,0.04));
    color: var(--om-text-dim);
    border: 1px solid var(--om-line);
    border-radius: 999px;
    padding: 1px 8px;
    cursor: pointer;
    line-height: 1.4;
    text-transform: lowercase;
    letter-spacing: 0.04em;
}
.track-block-transition:hover {
    border-color: var(--om-accent);
    color: var(--om-text);
}

/* Remove × — top-right corner, always visible (unlike the old
   thumb-overlay design, this one's outside the thumb). */
.track-remove {
    position: absolute;
    top: 6px; right: 6px;
    background: rgba(0,0,0,0.55);
    color: #fff;
    border: none;
    border-radius: 50%;
    width: 22px; height: 22px;
    font-size: 14px;
    line-height: 1;
    padding: 0;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 2;
    opacity: 0;
    transition: opacity .15s ease;
}
.track-block:hover .track-remove,
.track-block:focus-within .track-remove { opacity: 1; }
.track-remove:hover { background: var(--om-danger, #e85a45); }

.track-ghost { opacity: 0.4; background: var(--accent); }

.playlist-pallet-heading {
    margin: 0.5rem 0 0;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--muted);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

/* Pallet drawer — Claude Design: horizontally scrolling row with
   snap-x cards, sits below the track. Each card is 110px wide with
   a 2:1 thumbnail above a name bar. */
.om-pallet {
    border: 1px solid var(--om-line);
    border-radius: var(--om-radius-lg, 0.6rem);
    background: var(--om-surface);
    display: flex; flex-direction: column;
    overflow: hidden;
}
.om-pallet-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 10px var(--om-pad);
    border-bottom: 1px solid var(--om-line);
}
.om-pallet-head h3 {
    margin: 0;
    font-family: var(--om-display);
    font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--om-text-dim);
    font-weight: 500;
}

.playlist-pallet {
    list-style: none;
    margin: 0;
    padding: 10px var(--om-pad);
    /* Grid layout — every pallet tile fits in a single scroll-to-fit
       view instead of a horizontal strip the operator has to arrow
       through. Same pattern as .slide-browser-list so the two grids
       look consistent across the editor. */
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));
    gap: 0.5rem;
}

.pallet-tile {
    background: var(--om-surface-2, var(--panel));
    border: 1px solid var(--om-line, var(--panel-border));
    border-radius: 8px;
    overflow: hidden;
    cursor: grab;
    position: relative;
    display: flex; flex-direction: column;
}
.pallet-tile:active { cursor: grabbing; }
.pallet-tile:hover { border-color: var(--om-line-2, var(--panel-border)); }

.pallet-tile-thumb-wrap {
    position: relative;
    aspect-ratio: 2/1;
    background: var(--om-led-bg, #000);
    overflow: hidden;
}

/* Mode-locked tiles: video stored for a different output_mode than the
   device's current mode — renders a warning badge + dims the thumbnail
   so it's obvious at a glance why this tile won't play. */
.pallet-tile--locked .pallet-tile-thumb,
.track-block--locked .track-block-thumb {
    opacity: 0.45;
    filter: grayscale(0.4);
}

.pallet-tile-lock,
.track-block-lock {
    position: absolute;
    top: 0.3rem;
    left: 0.4rem;
    font-size: 0.85rem;
    background: rgba(255, 180, 40, 0.95);
    color: #1a0f00;
    border-radius: 999px;
    padding: 0 0.35rem;
    line-height: 1.3rem;
    font-weight: 700;
    pointer-events: auto;
    cursor: help;
}

.pallet-tile-thumb {
    width: 100%;
    height: 100%;
    object-fit: cover;
    image-rendering: pixelated;
    display: block;
}

.pallet-tile-name {
    padding: 5px 8px;
    font-size: 11px;
    font-weight: 600;
    color: var(--om-text, var(--text));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pallet-tile-edit {
    position: absolute;
    bottom: 0.25rem;
    right: 0.3rem;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    border: none;
    border-radius: 50%;
    width: 1.4rem;
    height: 1.4rem;
    font-size: 0.75rem;
    line-height: 1;
    padding: 0;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s ease;
}
.pallet-tile:hover .pallet-tile-edit,
.pallet-tile:focus-within .pallet-tile-edit {
    opacity: 1;
}
.pallet-tile-edit:hover { background: var(--accent); color: #1a1a1d; }

/* Delete × sits in the top-right of the tile, hidden until hover/focus
   to keep the default tile look clean. Same hover-to-reveal pattern as
   the edit pencil. */
.pallet-tile-delete {
    position: absolute;
    top: 0.25rem;
    right: 0.3rem;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    border: none;
    border-radius: 50%;
    width: 1.4rem;
    height: 1.4rem;
    font-size: 0.9rem;
    line-height: 1;
    padding: 0;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s ease;
}
.pallet-tile:hover .pallet-tile-delete,
.pallet-tile:focus-within .pallet-tile-delete {
    opacity: 1;
}
.pallet-tile-delete:hover { background: var(--danger); color: #1a1a1d; }

.pallet-ghost { opacity: 0.4; }

.playlist-track-status {
    margin: 0;
    color: var(--muted);
    font-size: 0.85rem;
    min-height: 1.2em;
}

/* --- settings view --- */

.settings-view {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.settings-form {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.settings-save {
    width: 100%;
}
.settings-heading { margin: 0; font-size: 0.9rem; font-weight: 600; color: var(--muted); letter-spacing: 0.02em; }
.settings-hint { margin: 0; color: var(--muted); font-size: 0.8rem; opacity: 0.85; }
.settings-dl {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.35rem 1rem;
    margin: 0;
    font-size: 0.9rem;
}
.settings-dl dt { color: var(--muted); }
.settings-dl dd { margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.settings-status { margin: 0; color: var(--muted); font-size: 0.85rem; min-height: 1.2em; }

.settings-wifi-ap,
.settings-wifi-station {
    border: 1px solid var(--panel-border);
    border-radius: 0.4rem;
    padding: 0.5rem 0.75rem 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-top: 0.5rem;
    transition: opacity 0.15s ease;
}
.settings-wifi-ap legend,
.settings-wifi-station legend {
    padding: 0 0.35rem;
}
/* Dim just the body — not the legend — so the enable checkbox in the
   legend stays at full opacity and is visibly clickable even when the
   section is off. */
.settings-wifi-ap.is-disabled > :not(legend),
.settings-wifi-station.is-disabled > :not(legend) {
    opacity: 0.45;
}
.settings-wifi-ap.is-disabled input:not([type="checkbox"]),
.settings-wifi-station.is-disabled input:not([type="checkbox"]) {
    cursor: not-allowed;
}

.settings-tailscale {
    border: 1px solid var(--panel-border);
    border-radius: 0.4rem;
    padding: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-top: 0.5rem;
}
.settings-tailscale legend {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--muted);
    padding: 0 0.35rem;
}
/* Same body-opacity pattern as wifi-ap/station — the legend checkbox
   (its own enable toggle) stays visible when tailscale body is off. */
.settings-tailscale.is-disabled > :not(legend) {
    opacity: 0.45;
}

.field-inline {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.9rem;
    color: var(--text);
}

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

button {
    font: inherit;
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--panel-border);
    border-radius: 0.5rem;
    padding: 0.5rem 0.9rem;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

button:hover { border-color: var(--accent); }
button.primary { background: var(--accent); color: #1a1a1d; border-color: var(--accent); font-weight: 600; }
button.danger { color: var(--danger); border-color: var(--danger); background: transparent; }

/* --- editor --- */

.editor {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.preview-wrap {
    /* No background / padding so the preview outline hugs the sign's
       actual aspect ratio — avoids black letterbox bars when the
       device isn't 4:3. */
    display: flex;
    justify-content: center;
    align-items: center;
    border: 1px solid var(--panel-border);
    border-radius: 0.5rem;
    overflow: hidden;
}

/* Subpage title ("Text Slides" / "Image Slides" / "Video Slides") +
   horizontal slide browser that sits below it. */
.subpage-title {
    margin: 0 0 0.75rem;
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--text);
}

.slide-browser {
    margin-bottom: 1rem;
}
.slide-browser-list {
    list-style: none;
    margin: 0;
    padding: 0.35rem 0;
    /* Grid layout (same pattern as .playlist-pallet) so the operator sees
       every slide in a single scroll-to-fit view instead of a long
       horizontal strip they'd have to arrow through. */
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));
    gap: 0.5rem;
}
.slide-browser-tile {
    position: relative;
    /* Grid stretches the LI to the row's natural height, but the inner
       <button> wouldn't fill it on its own — so the "+ New" action
       reads visibly shorter than its siblings (thumb + name). Become a
       column flex so the action child can stretch to match. */
    display: flex;
    flex-direction: column;
}
.slide-browser-tile-action {
    flex: 1 1 auto;
}
/* Delete × in the top-right of a browser tile — same hover-to-reveal
   pattern as the pallet's edit/delete affordances. */
.slide-browser-tile-delete {
    position: absolute;
    top: 0.25rem;
    right: 0.3rem;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    border: none;
    border-radius: 50%;
    width: 1.3rem;
    height: 1.3rem;
    font-size: 0.85rem;
    line-height: 1;
    padding: 0;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s ease;
}
.slide-browser-tile:hover .slide-browser-tile-delete,
.slide-browser-tile:focus-within .slide-browser-tile-delete {
    opacity: 1;
}
.slide-browser-tile-delete:hover { background: var(--danger); color: #1a1a1d; }
/* Type badge in the browser strip. Delete × at top-right stays the
   focus visually; this sits top-left. */
.slide-browser-tile-type {
    position: absolute;
    top: 0.35rem;
    left: 0.45rem;
    font-size: 0.65rem;
    color: #fff;
    background: rgba(0, 0, 0, 0.5);
    border-radius: 999px;
    padding: 0 0.3rem;
    pointer-events: none;
}
.slide-browser-tile-action {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
    width: 100%;
    padding: 0.35rem;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 0.35rem;
    color: var(--text);
    font-family: inherit;
    font-size: 0.7rem;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}
.slide-browser-tile-action:hover {
    border-color: var(--accent);
    background: #111115;
}
.slide-browser-tile--selected .slide-browser-tile-action {
    border-color: var(--accent);
    background: #161616;
    box-shadow: 0 0 0 2px rgba(255, 184, 77, 0.35);
}
.slide-browser-tile-thumb {
    width: 100%;
    aspect-ratio: var(--device-aspect, 4 / 3);
    object-fit: cover;
    background: #000;
    border-radius: 0.2rem;
    image-rendering: pixelated;
}
.slide-browser-tile-name {
    width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-align: center;
}
.editor-canvas {
    /* Aspect ratio is set inline by the mount fn so it matches the
       device's configured display dims. The CSS var is a fallback for
       the first paint before JS runs — without it the canvas takes
       its intrinsic 300×150 default and looks visibly wrong. */
    display: block;
    width: 100%;
    max-width: 100%;
    height: auto;
    aspect-ratio: var(--device-aspect, 4 / 3);
    image-rendering: pixelated;
}

.controls {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    font-size: 0.85rem;
}
/* display: flex above wins over the UA default for [hidden]; re-state. */
.field[hidden] {
    display: none;
}

.field > span {
    color: var(--muted);
    font-size: 0.8rem;
}

.field input[type="text"],
.field input[type="number"],
.field select,
.field textarea {
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--panel-border);
    border-radius: 0.35rem;
    padding: 0.55rem 0.7rem;
    font: inherit;
    width: 100%;
    /* Safari-specific: in a column flex container, `width: 100%` on a
       textarea doesn't always resolve against the parent's inner width
       the way Chromium does — the placeholder render box can end up
       clipped at the textarea's intrinsic cols-based width. `fill-
       available` asks the browser to stretch to the computed parent
       inner width, which Safari honors reliably. */
    width: -webkit-fill-available;
    width: stretch;
    min-width: 0;
}

.field-duration-wrap {
    flex: 0 1 7rem;
}

/* `display: block` trumps the UA's inline-block default — Safari was
   honoring the intrinsic cols-based width for placeholder layout even
   with `width: 100%` set, causing the placeholder to visibly clip after
   a switch-then-new flow. Block display forces the textarea to size
   from its container rather than from its cols attribute. */
.field textarea {
    display: block;
    resize: vertical;
    min-height: 4em;
}

.field input[type="text"]:focus,
.field textarea:focus {
    outline: none;
    border-color: var(--accent);
}

.field input[type="color"] {
    width: 100%;
    height: 2.5rem;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 0.35rem;
    padding: 0.1rem;
    cursor: pointer;
}

.row { display: flex; gap: 0.75rem; }
.row .field { flex: 1 1 0; }

.presets {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.preset {
    /* 2.75rem ≈ 44pt = Apple HIG min tap target. */
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 0.35rem;
    border: 1px solid var(--panel-border);
    font-weight: 700;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.preset:hover {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.editor-status {
    margin: 0;
    color: var(--muted);
    font-size: 0.85rem;
    min-height: 1.2em;
}

/* Auto-save indicator. The auto-save helper toggles `data-state`
   between idle / saving / saved / error; we colorize accordingly. The
   element is intentionally subtle — operators glance at it to confirm
   the network heard their change, not a primary call to action. */
.om-save-status {
    margin: 6px 0 0;
    min-height: 1.4em;
    font-family: var(--om-mono);
    font-size: 11.5px;
    letter-spacing: 0.04em;
    color: var(--om-text-fade);
    text-align: center;
}
.om-save-status[data-state="saving"] { color: var(--om-text-dim); }
.om-save-status[data-state="saved"]  { color: var(--om-good, #4ec18b); }
.om-save-status[data-state="error"]  { color: var(--om-warn, #ff7a59); }

.field-hint {
    margin: 0;
    color: var(--muted);
    font-size: 0.8rem;
    opacity: 0.7;
}

.field-hint kbd {
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 0.2rem;
    padding: 0.05rem 0.35rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.75rem;
}

/* --- image uploader --- */

.image-upload {
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--panel-border);
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.video-upload-video {
    /* Native <video controls> — let the browser draw its play button +
       scrubber. Sized to the device aspect so the preview matches what
       plays. */
    display: block;
    width: 100%;
    max-width: 100%;
    height: auto;
    aspect-ratio: var(--device-aspect, 4 / 3);
    background: #000;
}

.image-upload-canvas,
.video-upload-canvas {
    /* Canvas bitmap dims are device W×H; the inline style.aspectRatio
       set by the mount fn drives the rendered shape. The CSS var is a
       fallback so first-paint (before JS runs) still uses the right
       proportion instead of the canvas's intrinsic 300×150 default. */
    display: block;
    width: 100%;
    max-width: 100%;
    height: auto;
    aspect-ratio: var(--device-aspect, 4 / 3);
    image-rendering: pixelated;
}

.image-upload-status {
    margin: 0;
    color: var(--muted);
    font-size: 0.85rem;
    min-height: 1.2em;
}

.field-file {
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--panel-border);
    border-radius: 0.35rem;
    padding: 0.4rem;
    font: inherit;
    width: 100%;
}

/* --- named-playlist manager --- */

.playlists {
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--panel-border);
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.playlists-heading {
    margin: 0;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--muted);
    letter-spacing: 0.02em;
}

.playlists-hint {
    margin: 0 0 0.25rem;
    color: var(--muted);
    font-size: 0.8rem;
    opacity: 0.8;
}

.playlists-hint em { font-style: italic; opacity: 0.9; }

.playlists-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.playlist-card {
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 0.5rem;
    padding: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.playlist-header {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.playlist-name {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    flex: 1;
}

.playlist-count {
    font-size: 0.8rem;
    color: var(--muted);
}

.playlist-items {
    list-style: none;
    padding: 0.25rem;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-height: 2.75rem;
    border: 1px dashed var(--panel-border);
    border-radius: 0.35rem;
    background: rgba(0, 0, 0, 0.05);
}

/* Empty-state hint surfaced via CSS so SortableJS never sees it as a
   draggable child. The attribute is set on the <ul> in the template. */
.playlist-items:empty::before {
    content: attr(data-empty-hint);
    color: var(--muted);
    font-size: 0.8rem;
    font-style: italic;
    padding: 0.35rem 0.5rem;
    display: block;
}

.playlist-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    padding: 0.3rem 0.5rem;
    background: var(--bg, #fff);
    border: 1px solid var(--panel-border);
    border-radius: 0.3rem;
    cursor: grab;
}

.playlist-item:active { cursor: grabbing; }

.playlist-item-handle {
    color: var(--muted);
    font-size: 0.75rem;
    letter-spacing: -0.1em;
}

.playlist-item-label {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.playlist-item-remove {
    background: none;
    border: none;
    color: var(--muted);
    font-size: 1rem;
    line-height: 1;
    padding: 0 0.35rem;
    /* Override .playlist-item's grab cursor so the × reads as clickable, not
       draggable. */
    cursor: pointer;
}

.playlist-item-remove:hover { color: var(--danger, #c33); }

.playlist-item-ghost {
    opacity: 0.4;
    background: var(--accent, #def);
}

.playlist-add {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.playlist-add label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.8rem;
    color: var(--muted);
}

.playlist-add-label {
    /* Visually redundant with the default <option> text; keep for a11y. */
    position: absolute;
    left: -9999px;
}

.playlist-add-select {
    font-size: 0.85rem;
    padding: 0.2rem 0.35rem;
}

.playlists-create {
    display: flex;
    align-items: end;
    gap: 0.5rem;
}

.playlists-create .field { flex: 1; }

.playlists-status {
    margin: 0;
    color: var(--muted);
    font-size: 0.85rem;
    min-height: 1.2em;
}

/* --- schedule editor --- */

.schedule {
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--panel-border);
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.schedule-heading {
    margin: 0;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--muted);
    letter-spacing: 0.02em;
}

.schedule-hint {
    margin: 0 0 0.25rem;
    color: var(--muted);
    font-size: 0.8rem;
    opacity: 0.8;
}

.schedule-hint em {
    font-style: italic;
    opacity: 0.7;
}

.schedule-rules {
    list-style: none;
    padding: 0;
    margin: 0.5rem 0;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.schedule-rule {
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 0.5rem;
    padding: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

/* "Add another playlist before scheduling" hint — surfaced when the
   only available playlist is the seeded default. Without this, the
   operator can configure rules with no behavioral effect (every rule
   plays the same playlist the no-rule fallback already plays). */
.schedule-empty-hint {
    background: var(--panel);
    border: 1px dashed var(--panel-border);
    border-radius: 0.4rem;
    color: var(--muted);
    font-size: 0.85rem;
    line-height: 1.4;
    margin: 0 0 12px;
    padding: 0.6rem 0.8rem;
}
.schedule-empty-hint a {
    color: var(--accent, #4ec18b);
    text-decoration: underline;
}

/* Per-card outline for rules whose form would 422 the server (e.g. no
   days checked, malformed HH:MM). Pairs with the field-level highlights
   below to point the operator at which input is the problem. */
.schedule-rule.rule-invalid {
    border-color: var(--om-warn, #ff7a59);
}

.rule-days-wrap.invalid {
    border-color: var(--om-warn, #ff7a59);
}

/* Per-input highlight for HH:MM fields whose value fails the pattern
   check. Browsers also paint `:invalid` styling for these, but only
   after the first interaction — this class fires immediately on mount
   so a stored bad value (rare, but possible if a prior schema accepted
   it) is visible right away. */
.schedule-rule .input-error {
    border-color: var(--om-warn, #ff7a59);
    outline: 1px solid var(--om-warn, #ff7a59);
    outline-offset: -1px;
}

.schedule-rule-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: end;
}

.schedule-rule-row .field { flex: 1 1 120px; }

.rule-days-wrap {
    border: 1px solid var(--panel-border);
    border-radius: 0.35rem;
    padding: 0.4rem 0.6rem;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.7rem;
    width: 100%;
}

.rule-days-wrap legend {
    font-size: 0.75rem;
    color: var(--muted);
    padding: 0 0.25rem;
}

.rule-day {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.85rem;
    cursor: pointer;
}

.schedule-rule-enabled {
    flex: 0 0 auto;
    align-self: end;
}

.schedule-rule-enabled span { font-size: 0.75rem; }

.schedule-add {
    align-self: flex-start;
    background: var(--panel);
    border: 1px dashed var(--panel-border);
    border-radius: 0.35rem;
    color: var(--text);
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    padding: 0.45rem 0.9rem;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}
.schedule-add:hover {
    border-color: var(--accent);
    background: #111115;
}

/* Current-time display at the top of the Schedule subpage. Ticks every
   second in the device's configured IANA timezone so the operator can
   eyeball whether a rule is about to fire. */
.schedule-now {
    display: inline-flex;
    align-items: baseline;
    gap: 0.6rem;
    margin-bottom: 0.75rem;
    padding: 0.45rem 0.8rem;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 0.35rem;
}
.schedule-now-label {
    font-size: 0.7rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--muted);
}
.schedule-now-value {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.95rem;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}

/* Small inline action buttons used by Settings (detect dims, rescan WiFi). */
.field-hint-btn {
    align-self: flex-start;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 0.3rem;
    color: var(--text);
    font-family: inherit;
    font-size: 0.8rem;
    padding: 0.3rem 0.7rem;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}
.field-hint-btn:hover {
    border-color: var(--accent);
    background: #111115;
}

.schedule-bulk {
    display: flex;
    gap: 0.4rem;
    flex-wrap: wrap;
}

.schedule-bulk button {
    font-size: 0.8rem;
    padding: 0.35rem 0.7rem;
}

.schedule-status {
    margin: 0;
    color: var(--muted);
    font-size: 0.85rem;
    min-height: 1.2em;
}


/* --- Flock panel --- */

.flock {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 1.25rem 1.5rem;
    max-width: 48rem;
}

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

.flock-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: 0.85rem;
}

.flock-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 0.5rem;
    padding: 0.6rem;
    color: var(--text);
    font: inherit;
    text-align: left;
    transition: border-color 120ms ease;
}

.flock-tile:hover {
    border-color: var(--accent);
}

/* Synced state is the same visual regardless of self-vs-peer — one
   "I'm part of the flock" signal the eye can scan across tiles. */
.flock-tile-synced {
    border-color: var(--accent);
    box-shadow: 0 0 0 1px var(--accent) inset;
}

.flock-tile-self {
    /* Self tile always gets the accent border (even when sync is off) so
       the "you are here" marker stays visible. */
    border-color: var(--accent);
}

.flock-tile-thumb-wrap {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    background: #05050a;
    border-radius: 0.3rem;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}

.flock-tile-thumb {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
    opacity: 0;
    transition: opacity 180ms ease;
}

.flock-tile-thumb.is-loaded {
    opacity: 1;
}

.flock-tile-thumb-empty {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--muted);
    font-size: 0.8rem;
    pointer-events: none;
    transition: opacity 180ms ease;
}

.flock-tile-thumb-empty.is-hidden {
    opacity: 0;
}

.flock-tile-body {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}

.flock-tile-name {
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.flock-tile-address {
    color: var(--muted);
    font-size: 0.85rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.flock-tile-seen {
    color: var(--muted);
    font-size: 0.75rem;
}

.flock-tile-controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    flex-wrap: wrap;
    border-top: 1px solid var(--panel-border);
    padding-top: 0.5rem;
}

.flock-tile-sync {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.85rem;
    cursor: pointer;
}

.flock-tile-sync-input {
    accent-color: var(--accent);
}

.flock-tile-open {
    color: var(--accent);
    text-decoration: none;
    font-size: 0.85rem;
}

.flock-tile-open:hover {
    text-decoration: underline;
}

/* Match the pallet-tile × for visual consistency: round, top-right,
   hidden until hover/focus of the tile, red on hover. */
.flock-tile-delete {
    position: absolute;
    top: 0.35rem;
    right: 0.4rem;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    border: none;
    border-radius: 50%;
    width: 1.4rem;
    height: 1.4rem;
    font-size: 0.9rem;
    line-height: 1;
    padding: 0;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s ease;
    z-index: 2;
}

.flock-tile:hover .flock-tile-delete,
.flock-tile:focus-within .flock-tile-delete {
    opacity: 1;
}

.flock-tile-delete:hover {
    background: var(--danger);
    color: #1a1a1d;
}

.flock-tile-new {
    align-items: center;
    justify-content: center;
    text-align: center;
    /* No min-height — the grid stretches every tile to the tallest
       sibling so "+ New" matches the peer tiles' overall footprint
       (thumbnail + body + controls) instead of looking like a stub. */
    cursor: pointer;
    border-style: dashed;
    color: var(--muted);
}

.flock-tile-new:hover {
    color: var(--accent);
}

.flock-tile-new-plus {
    font-size: 2rem;
    line-height: 1;
}

.flock-tile-new-label {
    font-size: 0.85rem;
}

.flock-status {
    margin: 0;
    color: var(--muted);
    font-size: 0.85rem;
    min-height: 1.2em;
}

.flock-status.error {
    color: var(--danger);
}

.flock-modal {
    border: 1px solid var(--panel-border);
    background: var(--panel);
    color: var(--text);
    border-radius: 0.5rem;
    padding: 1rem 1.25rem;
    max-width: 22rem;
}

.flock-modal::backdrop {
    background: rgba(0, 0, 0, 0.6);
}

.flock-modal-form {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.flock-modal-form h3 {
    margin: 0;
    font-size: 1rem;
}

.flock-modal-hint {
    margin: -0.25rem 0 0;
    color: var(--muted);
    font-size: 0.8rem;
}

.flock-modal-error {
    margin: 0;
    color: var(--danger);
    font-size: 0.85rem;
    min-height: 1.2em;
}

.flock-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
}


/* ═══════════════════════════════════════════════════════════════════
   NEW DESIGN SYSTEM — om-* prefix
   ───────────────────────────────────────────────────────────────────
   From the Claude Design handoff bundle (2026-04-25). Coexists with
   the legacy classes above; nothing in this section reuses an
   existing selector. Existing screens keep their old look until each
   is migrated to om-* in a follow-up commit.
   ═══════════════════════════════════════════════════════════════════ */

/* openMarquee device UI — design system
 * --------------------------------------
 * Drives every screen via CSS custom properties so the Tweaks panel can
 * swap theme / accent / density with a single root mutation. The look is
 * "marquee scoreboard" — dot-matrix accents, monospace chrome, big block
 * numerals, with humanist sans for content.
 *
 * Type strategy: zero web fonts (the device has no internet). All
 * families are system stacks — what's already on the phone is the
 * design.
 */

:root {
  /* — Type stacks ——————————————————————————————— */
  --om-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  --om-sans: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
  --om-display: var(--om-mono); /* the "scoreboard" face */

  /* — Density (set by tweaks: comfy | compact) ——— */
  --om-pad: 14px;
  --om-gap: 12px;
  --om-row: 52px;
  --om-radius: 10px;
  --om-radius-lg: 14px;

  /* — Theme tokens (filled by [data-theme]) ——————— */
  --om-bg:        #0b0d10;
  --om-surface:   #14181d;
  --om-surface-2: #1c2127;
  --om-line:      rgba(255,255,255,.08);
  --om-line-2:    rgba(255,255,255,.14);
  --om-text:      #e7eaee;
  --om-text-dim:  rgba(231,234,238,.62);
  --om-text-fade: rgba(231,234,238,.38);
  --om-shadow:    0 1px 0 rgba(255,255,255,.04) inset, 0 8px 24px rgba(0,0,0,.4);
  --om-led-bg:    #050608;
  --om-led-off:   rgba(255,255,255,.025);
  --om-glow:      0 0 16px var(--om-accent-glow);

  /* — Accent (filled by [data-accent]) ——————— */
  --om-accent:       #ffb43c;
  --om-accent-ink:   #1a0f00;
  --om-accent-soft:  rgba(255,180,60,.18);
  --om-accent-glow:  rgba(255,180,60,.35);
  --om-accent-2:     #ff8a3c; /* secondary marquee color */

  /* — Status — */
  --om-good:  #5ad28a;
  --om-warn:  #ffb43c;
  --om-bad:   #ff5a6b;
}

/* ─── Theme variants ────────────────────────────────── */
[data-theme="dark"] {
  --om-bg:        #0b0d10;
  --om-surface:   #14181d;
  --om-surface-2: #1c2127;
  --om-line:      rgba(255,255,255,.08);
  --om-line-2:    rgba(255,255,255,.14);
  --om-text:      #e7eaee;
  --om-text-dim:  rgba(231,234,238,.62);
  --om-text-fade: rgba(231,234,238,.38);
}
[data-theme="oled"] {
  --om-bg:        #000000;
  --om-surface:   #0a0a0a;
  --om-surface-2: #141414;
  --om-line:      rgba(255,255,255,.07);
  --om-line-2:    rgba(255,255,255,.14);
  --om-text:      #f4f4f4;
  --om-text-dim:  rgba(244,244,244,.55);
  --om-text-fade: rgba(244,244,244,.32);
}
[data-theme="light"] {
  --om-bg:        #f4efe6;
  --om-surface:   #fbf8f1;
  --om-surface-2: #f0eadc;
  --om-line:      rgba(20,16,8,.10);
  --om-line-2:    rgba(20,16,8,.18);
  --om-text:      #1a1610;
  --om-text-dim:  rgba(26,22,16,.65);
  --om-text-fade: rgba(26,22,16,.42);
  --om-shadow:    0 1px 0 rgba(255,255,255,.7) inset, 0 6px 18px rgba(60,40,10,.10);
  --om-led-bg:    #0c0a08;
}

/* ─── Accent variants ────────────────────────────────── */
[data-accent="amber"] {
  --om-accent: #ffb43c; --om-accent-ink: #1a0f00;
  --om-accent-soft: rgba(255,180,60,.18); --om-accent-glow: rgba(255,180,60,.45);
  --om-accent-2: #ff8a3c;
}
[data-accent="green"] {
  --om-accent: #5af095; --om-accent-ink: #002914;
  --om-accent-soft: rgba(90,240,149,.16); --om-accent-glow: rgba(90,240,149,.45);
  --om-accent-2: #b6f05a;
}
[data-accent="pink"] {
  --om-accent: #ff5fa7; --om-accent-ink: #2a0420;
  --om-accent-soft: rgba(255,95,167,.18); --om-accent-glow: rgba(255,95,167,.45);
  --om-accent-2: #ff8a3c;
}
[data-accent="cyan"] {
  --om-accent: #5fd5ff; --om-accent-ink: #002028;
  --om-accent-soft: rgba(95,213,255,.18); --om-accent-glow: rgba(95,213,255,.45);
  --om-accent-2: #5af095;
}

/* ─── Density ────────────────────────────────── */
[data-density="compact"] {
  --om-pad: 10px;
  --om-gap: 8px;
  --om-row: 44px;
}

/* ─── Core layout ────────────────────────────────── */
.om-app {
  display: flex; flex-direction: column;
  width: 100%; height: 100%;
  background: var(--om-bg);
  color: var(--om-text);
  font-family: var(--om-sans);
  font-size: 14px;
  line-height: 1.4;
  letter-spacing: -0.005em;
  overflow: hidden;
  position: relative;
  isolation: isolate;
}
.om-app *::-webkit-scrollbar { width: 6px; height: 6px; }
.om-app *::-webkit-scrollbar-thumb { background: var(--om-line-2); border-radius: 3px; }
.om-app *::-webkit-scrollbar-track { background: transparent; }
.om-app * { scrollbar-width: thin; scrollbar-color: var(--om-line-2) transparent; }

/* ─── Top bar ────────────────────────────────── */
.om-topbar {
  display: flex; align-items: center; gap: 10px;
  padding: 10px var(--om-pad);
  border-bottom: 1px solid var(--om-line);
  background: var(--om-bg);
  position: relative;
  z-index: 5;
}
.om-topbar-title {
  font-family: var(--om-display);
  font-size: 13px; letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--om-text-dim);
  flex: 1;
  display: flex; align-items: center; gap: 8px;
  min-width: 0;
}
.om-topbar-title b {
  color: var(--om-text);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  font-family: var(--om-sans);
  font-size: 15px;
}
.om-topbar-title .om-divider {
  width: 1px; height: 12px; background: var(--om-line-2);
}

/* The wordmark — stylized openMarquee with a tiny dot-matrix mark */
.om-wordmark {
  font-family: var(--om-sans);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--om-text);
}
.om-wordmark .om-mark {
  width: 18px; height: 14px;
  background: var(--om-led-bg);
  border-radius: 3px;
  position: relative;
  overflow: hidden;
  box-shadow: inset 0 0 0 1px var(--om-line-2);
}
.om-wordmark .om-mark::before {
  content: "";
  position: absolute; inset: 2px;
  background-image:
    radial-gradient(circle, var(--om-accent) 38%, transparent 42%),
    radial-gradient(circle, var(--om-accent) 38%, transparent 42%);
  background-size: 4px 4px, 4px 4px;
  background-position: 0 0, 2px 2px;
  background-repeat: repeat;
  opacity: 0.95;
  filter: drop-shadow(0 0 1.5px var(--om-accent-glow));
}
.om-wordmark em { color: var(--om-accent); font-style: normal; font-weight: 700; }

/* ─── Sidebar nav (desktop) ────────────────────────── */
.om-side {
  width: 220px;
  flex: 0 0 220px;
  background: var(--om-surface);
  border-right: 1px solid var(--om-line);
  display: flex; flex-direction: column;
  padding: 16px 10px;
  gap: 2px;
}
.om-side-section {
  font-family: var(--om-display);
  font-size: 10px; letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--om-text-fade);
  padding: 14px 12px 6px;
}
.om-side-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px;
  border-radius: 8px;
  color: var(--om-text-dim);
  cursor: pointer;
  font-size: 13.5px;
  font-weight: 500;
  position: relative;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.om-side-item:hover { background: var(--om-line); color: var(--om-text); }
.om-side-item.active {
  background: var(--om-accent-soft);
  color: var(--om-accent);
}
.om-side-item.active::before {
  content: ""; position: absolute; left: -10px; top: 8px; bottom: 8px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--om-accent);
  box-shadow: 0 0 8px var(--om-accent-glow);
}
.om-side-item .om-side-icon {
  width: 18px; height: 18px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  color: inherit;
}
.om-side-sub {
  padding-left: 26px;
  font-size: 13px;
  font-weight: 400;
}
.om-side-item .om-badge {
  margin-left: auto;
  font-family: var(--om-display);
  font-size: 10px;
  background: var(--om-line);
  color: var(--om-text-dim);
  padding: 2px 6px;
  border-radius: 4px;
  letter-spacing: 0.05em;
}
.om-side-item.active .om-badge { background: var(--om-accent); color: var(--om-accent-ink); }

/* The flock entry gets extra love — live status dots */
.om-side-item.om-flock {
  margin-top: 10px;
  background: linear-gradient(180deg, var(--om-surface-2), var(--om-surface));
  border: 1px solid var(--om-line);
  border-radius: var(--om-radius);
  padding: 10px 12px;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
}
.om-side-item.om-flock:hover { border-color: var(--om-line-2); }
.om-side-item.om-flock.active {
  border-color: var(--om-accent);
  background: var(--om-accent-soft);
}
.om-flock-head { display: flex; align-items: center; gap: 8px; font-weight: 600; color: var(--om-text); }
.om-flock-head .om-flock-name { flex: 1; font-size: 13px; }
.om-flock-dots { display: flex; gap: 4px; }
.om-flock-dots i {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--om-good);
  box-shadow: 0 0 6px color-mix(in srgb, var(--om-good) 80%, transparent);
}
.om-flock-dots i.off { background: var(--om-line-2); box-shadow: none; }
.om-flock-stats {
  display: flex; gap: 10px;
  font-family: var(--om-mono);
  font-size: 10.5px;
  color: var(--om-text-dim);
  letter-spacing: 0.02em;
}

/* ─── Bottom tab bar (mobile alt nav) ───────────────────── */
.om-tabbar {
  display: flex;
  background: var(--om-surface);
  border-top: 1px solid var(--om-line);
  padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
  position: relative;
  z-index: 3;
}
.om-tab {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 8px 0 6px;
  font-family: var(--om-display);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--om-text-fade);
  border: none; background: transparent;
  cursor: pointer;
  border-radius: 8px;
  position: relative;
  -webkit-tap-highlight-color: transparent;
  min-height: 48px;
}
.om-tab svg { width: 20px; height: 20px; }
.om-tab.active { color: var(--om-accent); }
.om-tab.active::after {
  content: ""; position: absolute; top: 0; left: 30%; right: 30%; height: 2px;
  background: var(--om-accent); border-radius: 0 0 2px 2px;
  box-shadow: 0 0 6px var(--om-accent-glow);
}

/* ─── Main scroll region ────────────────────────── */
.om-main { flex: 1; min-height: 0; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
.om-scroll { flex: 1; overflow-y: auto; overflow-x: hidden; padding: var(--om-pad); }
.om-scroll-tight { padding: 0; }

.om-page-head {
  display: flex; align-items: end; justify-content: space-between; gap: 12px;
  padding: 4px 2px 14px;
}
.om-page-head h1 {
  font-family: var(--om-sans);
  font-size: 22px; font-weight: 700; letter-spacing: -0.02em;
  margin: 0;
  color: var(--om-text);
}
.om-page-head .om-eyebrow {
  font-family: var(--om-display);
  font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--om-text-fade); margin-bottom: 4px;
  display: block;
}
.om-page-head p { margin: 4px 0 0; color: var(--om-text-dim); font-size: 13px; max-width: 56ch; }

/* ─── Subnav (Slides → Text/Image/Video) ─────────────────── */
.om-subnav {
  display: flex; gap: 2px;
  padding: 4px;
  background: var(--om-surface-2);
  border: 1px solid var(--om-line);
  border-radius: 10px;
  margin-bottom: var(--om-gap);
}
.om-subnav button {
  flex: 1;
  appearance: none; border: 0;
  background: transparent;
  color: var(--om-text-dim);
  padding: 8px 10px;
  font-family: var(--om-sans);
  font-size: 13px;
  font-weight: 500;
  border-radius: 7px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  display: flex; align-items: center; justify-content: center; gap: 6px;
}
.om-subnav button.active {
  background: var(--om-bg);
  color: var(--om-text);
  box-shadow: 0 1px 2px rgba(0,0,0,.25);
}
/* Count badge — used in subnav buttons and sidebar nav-links. */
.om-count {
  font-family: var(--om-mono);
  font-size: 10.5px;
  color: var(--om-text-fade);
  background: var(--om-line);
  padding: 1px 5px;
  border-radius: 4px;
}
.om-subnav button.active .om-count,
.om-side-item.active .om-count { color: var(--om-accent); background: var(--om-accent-soft); }

/* ─── Card ────────────────────────── */
.om-card {
  background: var(--om-surface);
  border: 1px solid var(--om-line);
  border-radius: var(--om-radius-lg);
  padding: var(--om-pad);
}
.om-card-tight { padding: 0; overflow: hidden; }

/* ─── Buttons ────────────────────────── */
.om-btn {
  appearance: none; border: 0;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 40px;
  padding: 0 14px;
  border-radius: 10px;
  background: var(--om-surface-2);
  color: var(--om-text);
  font-family: var(--om-sans);
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  border: 1px solid var(--om-line);
  transition: transform .08s, background .12s, border-color .12s;
}
.om-btn:hover { border-color: var(--om-line-2); }
.om-btn:active { transform: translateY(1px); }
.om-btn.primary {
  background: var(--om-accent);
  color: var(--om-accent-ink);
  border-color: transparent;
  font-weight: 600;
  box-shadow: 0 0 0 0 var(--om-accent-glow), 0 1px 0 rgba(255,255,255,.25) inset;
}
.om-btn.primary:hover { box-shadow: 0 0 16px var(--om-accent-glow), 0 1px 0 rgba(255,255,255,.25) inset; }
.om-btn.ghost { background: transparent; border-color: transparent; color: var(--om-text-dim); }
.om-btn.ghost:hover { color: var(--om-text); background: var(--om-line); }
.om-btn.sm { height: 30px; padding: 0 10px; font-size: 12.5px; border-radius: 8px; }
.om-btn.icon { width: 36px; padding: 0; }
.om-btn.icon.sm { width: 28px; }
.om-btn:focus-visible { outline: 2px solid var(--om-accent); outline-offset: 2px; }

/* ─── Form fields ────────────────────────── */
.om-field { display: flex; flex-direction: column; gap: 6px; }
.om-field > span {
  font-family: var(--om-display);
  font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--om-text-fade);
}
.om-input, .om-select, .om-textarea {
  appearance: none;
  background: var(--om-surface-2);
  color: var(--om-text);
  border: 1px solid var(--om-line);
  border-radius: 9px;
  padding: 10px 12px;
  font: inherit;
  font-family: var(--om-sans);
  width: 100%;
  outline: none;
  transition: border-color .12s, background .12s;
}
.om-input:focus, .om-select:focus, .om-textarea:focus {
  border-color: var(--om-accent);
  background: var(--om-bg);
}
.om-textarea { min-height: 80px; resize: vertical; font-family: var(--om-mono); font-size: 13px; }
.om-select {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='%23999' d='M0 0h10L5 6z'/></svg>");
  background-repeat: no-repeat; background-position: right 12px center;
  padding-right: 32px;
}

/* Switch */
.om-switch {
  appearance: none; border: 0;
  width: 38px; height: 22px; border-radius: 999px;
  background: var(--om-line-2);
  position: relative; flex-shrink: 0;
  transition: background .15s;
  cursor: pointer;
}
.om-switch::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: 50%;
  background: #fff;
  transition: transform .15s;
  box-shadow: 0 1px 3px rgba(0,0,0,.3);
}
.om-switch[data-on="1"] { background: var(--om-accent); }
.om-switch[data-on="1"]::after { transform: translateX(16px); }

/* Range */
.om-range {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 4px;
  background: var(--om-line-2);
  border-radius: 999px;
  outline: none;
}
.om-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--om-accent);
  box-shadow: 0 0 8px var(--om-accent-glow);
  cursor: pointer;
}
.om-range::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--om-accent);
  box-shadow: 0 0 8px var(--om-accent-glow);
  cursor: pointer; border: 0;
}

/* Pill */
.om-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--om-line);
  color: var(--om-text-dim);
  font-family: var(--om-display);
  font-size: 10.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.om-pill.live { background: var(--om-accent-soft); color: var(--om-accent); }
.om-pill.good { background: rgba(90,210,138,.16); color: var(--om-good); }
.om-pill.bad { background: rgba(255,90,107,.16); color: var(--om-bad); }
.om-pill .om-pulse {
  width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
  animation: om-pulse 1.4s infinite;
}
@keyframes om-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .35; transform: scale(.7); }
}

/* ─── LED preview canvas ─────────────────────────────────
 * The pixel grid that shows what the device will draw. Sits on
 * --om-led-bg (near-black) with a subtle "off-pixel" pattern so the
 * panel structure is visible even on dark slides. --aspect-w/h are
 * set inline by the React component.
 */
.om-led-frame {
  position: relative;
  background: var(--om-led-bg);
  border-radius: 12px;
  border: 1px solid var(--om-line-2);
  padding: 10px;
  box-shadow: var(--om-shadow), inset 0 0 0 1px rgba(0,0,0,.5);
}
.om-led-stage {
  width: 100%;
  aspect-ratio: var(--om-aspect-w, 2) / var(--om-aspect-h, 1);
  background: var(--om-slide-bg, #0a0a0a);
  border-radius: 4px;
  position: relative;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  isolation: isolate;
  container-type: size;
  container-name: ledstage;
  /* Subtle dot-matrix overlay so panels look like panels */
}
.om-led-stage[data-pixel="1"]::after {
  content: "";
  position: absolute; inset: 0;
  background-image:
    radial-gradient(circle, rgba(0,0,0,.55) 30%, transparent 32%);
  background-size: var(--om-pixel, 4px) var(--om-pixel, 4px);
  pointer-events: none;
  mix-blend-mode: multiply;
}
.om-led-stage > * { position: relative; z-index: 1; }
.om-led-meta {
  display: flex; justify-content: space-between; align-items: center;
  margin-top: 8px;
  font-family: var(--om-mono);
  font-size: 10.5px;
  color: var(--om-text-fade);
  letter-spacing: 0.02em;
}
.om-led-meta .om-led-aspect-pick {
  display: flex; gap: 2px;
  background: var(--om-surface-2);
  border-radius: 6px;
  padding: 2px;
  border: 1px solid var(--om-line);
}
.om-led-meta .om-led-aspect-pick button {
  appearance: none; border: 0; background: transparent;
  color: var(--om-text-fade);
  font: inherit;
  padding: 4px 7px;
  border-radius: 5px;
  cursor: pointer;
}
.om-led-meta .om-led-aspect-pick button.active {
  background: var(--om-bg); color: var(--om-accent);
}

/* Slide-content rendering inside the LED stage */
.om-slide-text {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  text-align: center;
  font-weight: 700;
  white-space: pre-line;
  padding: 4%;
  box-sizing: border-box;
}
.om-slide-text.font-block { font-family: "Impact", "Arial Black", var(--om-display); letter-spacing: 0.02em; }
.om-slide-text.font-mono { font-family: var(--om-mono); }
.om-slide-text.font-serif { font-family: Georgia, "Times New Roman", serif; }
.om-slide-text.scroll {
  justify-content: flex-start;
  white-space: nowrap;
  animation: om-scroll 8s linear infinite;
}
@keyframes om-scroll {
  0% { transform: translateX(100%); }
  100% { transform: translateX(-100%); }
}

/* Slide background palette — used both inside the LED stage and as
 * card thumbnails. Kept in sync with the data file's bg keys.  */
.bg-teal   { background: radial-gradient(120% 90% at 30% 20%, #0d4a52, #06222a 70%); color: #f4ecd8; }
.bg-amber  { background: linear-gradient(180deg,#ffb43c,#ff7a3c); color: #1a0f00; }
.bg-cream  { background: linear-gradient(180deg,#fbf3dc,#e9d8a0); color: #1a1610; }
.bg-ink    { background: linear-gradient(180deg,#0d0e10,#000); color: #ffb43c; }
.bg-pink   { background: linear-gradient(180deg,#ff5fa7,#7a1d4a); color: #fff; }

/* — Image-slide placeholder treatments — */
.om-slide-img {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  position: relative; overflow: hidden;
  font-family: var(--om-mono);
  font-size: 10px;
  color: rgba(255,255,255,.5);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.om-slide-img.placeholder-cup { background: linear-gradient(160deg,#c98a5c,#5a3318); }
.om-slide-img.placeholder-cup::before {
  content: "☕ product shot";
  font-family: var(--om-mono);
  color: rgba(255,255,255,.6);
}
.om-slide-img.placeholder-logo { background: #0e0e10; color: var(--om-accent); }
.om-slide-img.placeholder-logo::before { content: "LOGO"; font-size: 16px; letter-spacing: 0.4em; font-weight: 700; }
.om-slide-img.placeholder-video { background: linear-gradient(180deg,#1a1a22,#000); }
.om-slide-img.placeholder-video::before { content: "▶  video loop"; }
.om-slide-img.placeholder-gradient-dusk   { background: linear-gradient(180deg,#ff8a5c,#3c1860 80%,#0a0820); }
.om-slide-img.placeholder-gradient-sunset { background: linear-gradient(180deg,#ffb43c,#ff5a3c 60%,#5e1a1a); }

/* ─── Slide list (cards) ────────────────────────── */
.om-slide-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
}
.om-slide-card {
  background: var(--om-surface);
  border: 1px solid var(--om-line);
  border-radius: var(--om-radius);
  overflow: hidden;
  cursor: pointer;
  position: relative;
  transition: transform .12s, border-color .12s, box-shadow .12s;
}
.om-slide-card:hover {
  border-color: var(--om-line-2);
  transform: translateY(-2px);
  box-shadow: 0 8px 20px rgba(0,0,0,.25);
}
.om-slide-card.selected {
  border-color: var(--om-accent);
  box-shadow: 0 0 0 1px var(--om-accent), 0 0 16px var(--om-accent-glow);
}
.om-slide-card .om-thumb {
  aspect-ratio: 2/1;
  position: relative;
  background: var(--om-led-bg);
}
.om-slide-card .om-thumb > .om-led-stage {
  border-radius: 0;
  height: 100%;
}
.om-slide-card .om-meta {
  padding: 8px 10px;
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
}
.om-slide-card .om-meta b {
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.om-slide-card .om-meta .om-dur {
  font-family: var(--om-mono);
  font-size: 10.5px;
  color: var(--om-text-fade);
  letter-spacing: 0.05em;
}
.om-slide-card .om-kind {
  position: absolute; top: 6px; left: 6px;
  font-family: var(--om-display);
  font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase;
  background: rgba(0,0,0,.5);
  color: #fff;
  padding: 2px 5px; border-radius: 3px;
  backdrop-filter: blur(4px);
}

/* ─── Playlist track editor — vertical (mobile default) ─── */
.om-track {
  display: flex; flex-direction: column;
  gap: 8px;
  padding: 4px 0 8px;
  position: relative;
}
.om-track-block {
  display: flex; align-items: stretch;
  background: var(--om-surface);
  border: 1px solid var(--om-line);
  border-radius: var(--om-radius);
  overflow: hidden;
  position: relative;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
  transition: transform .18s cubic-bezier(.2,.7,.3,1), box-shadow .12s, border-color .12s, opacity .12s;
}
.om-track-block.dragging {
  transition: none;
  z-index: 5;
  border-color: var(--om-accent);
  box-shadow: 0 16px 36px rgba(0,0,0,.4), 0 0 0 1px var(--om-accent), 0 0 24px var(--om-accent-glow);
  transform-origin: center;
}
.om-track-block .om-grip {
  width: 28px;
  display: flex; align-items: center; justify-content: center;
  color: var(--om-text-fade);
  cursor: grab;
  background: var(--om-surface-2);
  border-right: 1px solid var(--om-line);
  flex-shrink: 0;
}
.om-track-block.dragging .om-grip { cursor: grabbing; }
.om-track-block .om-grip:hover { color: var(--om-text); }
.om-track-block .om-block-thumb {
  width: 70px; aspect-ratio: 2/1; flex-shrink: 0;
  background: var(--om-led-bg);
  margin: 8px 0 8px 8px;
  border-radius: 6px;
  overflow: hidden;
  position: relative;
}
.om-track-block .om-block-meta {
  flex: 1; min-width: 0;
  padding: 10px 12px;
  display: flex; flex-direction: column;
  justify-content: center;
  gap: 3px;
}
.om-track-block .om-block-meta b {
  font-size: 13.5px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.om-track-block .om-block-meta .om-block-sub {
  font-family: var(--om-mono);
  font-size: 11px;
  color: var(--om-text-fade);
  letter-spacing: 0.04em;
}
.om-track-block .om-block-dur {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 0 12px;
  border-left: 1px dashed var(--om-line);
  min-width: 64px;
  font-family: var(--om-display);
}
.om-track-block .om-block-dur .om-num {
  font-size: 18px; font-weight: 700;
  color: var(--om-accent);
  font-variant-numeric: tabular-nums;
}
.om-track-block .om-block-dur .om-unit {
  font-size: 9.5px; letter-spacing: 0.14em;
  color: var(--om-text-fade); text-transform: uppercase;
}

/* drop indicator gap */
.om-track-gap {
  height: 2px; border-radius: 2px;
  background: transparent;
  transition: background .12s, height .12s, margin .12s;
  margin: -3px 0;
}
.om-track-gap.active {
  background: var(--om-accent);
  box-shadow: 0 0 12px var(--om-accent-glow);
  height: 4px;
  margin: 4px 0;
}

/* ─── Playlist editor — horizontal timeline variant ─── */
.om-timeline {
  position: relative;
  background: var(--om-surface);
  border: 1px solid var(--om-line);
  border-radius: var(--om-radius);
  padding: 14px 12px 8px;
  overflow-x: auto;
}
.om-timeline-ruler {
  display: flex;
  font-family: var(--om-mono);
  font-size: 9.5px;
  color: var(--om-text-fade);
  letter-spacing: 0.05em;
  margin-bottom: 6px;
  border-bottom: 1px dashed var(--om-line);
  padding-bottom: 4px;
}
.om-timeline-ruler i {
  flex-shrink: 0;
  border-left: 1px solid var(--om-line);
  padding: 0 0 2px 4px;
  font-style: normal;
}
.om-timeline-track {
  display: flex; gap: 4px;
  position: relative;
  user-select: none;
  touch-action: none;
}
.om-timeline-block {
  height: 64px; flex-shrink: 0;
  background: var(--om-surface-2);
  border: 1px solid var(--om-line);
  border-radius: 8px;
  overflow: hidden;
  position: relative;
  display: flex; align-items: stretch;
  transition: transform .18s cubic-bezier(.2,.7,.3,1), border-color .12s;
}
.om-timeline-block.dragging {
  transition: none; z-index: 5;
  border-color: var(--om-accent);
  box-shadow: 0 12px 30px rgba(0,0,0,.4), 0 0 16px var(--om-accent-glow);
}
.om-timeline-block .om-tl-thumb {
  width: 50px; aspect-ratio: 2/1;
  flex-shrink: 0;
  background: var(--om-led-bg);
}
.om-timeline-block .om-tl-info {
  padding: 6px 8px;
  display: flex; flex-direction: column; justify-content: center;
  min-width: 0;
}
.om-timeline-block .om-tl-info b {
  font-size: 11.5px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 100px;
}
.om-timeline-block .om-tl-info .om-tl-dur {
  font-family: var(--om-display);
  font-size: 14px; color: var(--om-accent);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.om-timeline-playhead {
  position: absolute; top: 28px; bottom: 8px;
  width: 2px;
  background: var(--om-accent);
  box-shadow: 0 0 8px var(--om-accent-glow);
  pointer-events: none;
  z-index: 6;
}
.om-timeline-playhead::before {
  content: ""; position: absolute; top: -6px; left: -5px;
  width: 12px; height: 12px;
  background: var(--om-accent);
  border-radius: 2px;
  box-shadow: 0 0 8px var(--om-accent-glow);
}

/* ─── Pallet (saved slides drawer) ─── */
.om-pallet {
  border-top: 1px solid var(--om-line);
  background: var(--om-surface);
  display: flex; flex-direction: column;
  flex-shrink: 0;
}
.om-pallet-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px var(--om-pad);
  border-bottom: 1px solid var(--om-line);
}
.om-pallet-head h3 {
  margin: 0;
  font-family: var(--om-display);
  font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--om-text-dim);
}
/* ─── Flock dashboard ─── */
.om-flock-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
}
.om-peer-card {
  background: var(--om-surface);
  border: 1px solid var(--om-line);
  border-radius: var(--om-radius-lg);
  padding: 14px;
  display: flex; flex-direction: column; gap: 10px;
  position: relative;
  transition: border-color .12s, transform .12s;
}
.om-peer-card:hover { border-color: var(--om-line-2); }
.om-peer-card.this { border-color: var(--om-accent); box-shadow: 0 0 0 1px var(--om-accent-soft); }
.om-peer-card.offline { opacity: 0.62; }

.om-peer-head { display: flex; align-items: center; gap: 8px; }
.om-peer-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--om-good);
  box-shadow: 0 0 6px color-mix(in srgb, var(--om-good) 70%, transparent);
}
.om-peer-dot.off { background: var(--om-line-2); box-shadow: none; }
.om-peer-card .om-peer-name { font-size: 14px; font-weight: 600; flex: 1; }
.om-peer-card .om-peer-thumb {
  background: var(--om-led-bg);
  border-radius: 8px;
  border: 1px solid var(--om-line);
  overflow: hidden;
  position: relative;
}
.om-peer-card .om-peer-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  font-family: var(--om-mono);
  font-size: 11px;
  color: var(--om-text-dim);
}
.om-peer-card .om-peer-stats span b { color: var(--om-text); font-weight: 600; }
.om-peer-card .om-peer-actions {
  display: flex; gap: 6px;
  margin-top: 2px;
}

/* ─── Welcome — first-run hero ─── */
.om-welcome {
  position: relative;
  height: 100%;
  display: flex; flex-direction: column;
  background: var(--om-bg);
  overflow: hidden;
}
.om-welcome-hero {
  position: relative;
  flex: 1;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  overflow: hidden;
}
.om-welcome-hero::before {
  content: "";
  position: absolute; inset: 0;
  background-image:
    radial-gradient(circle at 50% 50%, var(--om-accent-glow) 0%, transparent 50%);
  opacity: 0.5;
  pointer-events: none;
}
.om-marquee-strip {
  position: absolute; left: 0; right: 0;
  display: flex; overflow: hidden;
  font-family: var(--om-display);
  font-size: 11px; letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--om-accent);
  padding: 7px 0;
  border-block: 1px solid var(--om-line);
  background: var(--om-bg);
  z-index: 1;
}
.om-marquee-strip.top { top: 0; }
.om-marquee-strip.bot { bottom: 0; }
.om-marquee-strip > div {
  flex-shrink: 0;
  display: flex; gap: 24px;
  padding-right: 24px;
  animation: om-scroll-loop 20s linear infinite;
}
.om-marquee-strip span { display: inline-flex; align-items: center; gap: 16px; }
.om-marquee-strip i { font-style: normal; color: var(--om-text-fade); }
@keyframes om-scroll-loop {
  0% { transform: translateX(0); }
  100% { transform: translateX(-50%); }
}

/* ─── Welcome — preview "card" ─── */
.om-welcome-card {
  background: var(--om-led-bg);
  border-radius: 18px;
  padding: 14px;
  border: 1px solid var(--om-line-2);
  box-shadow: 0 30px 80px rgba(0,0,0,.5), 0 0 60px var(--om-accent-glow);
  width: min(100%, 360px);
  position: relative;
  z-index: 2;
}

/* ─── Misc helpers ─── */
.om-row { display: flex; align-items: center; gap: 10px; }
.om-row.between { justify-content: space-between; }
.om-stack { display: flex; flex-direction: column; gap: var(--om-gap); }
.om-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 40px 20px;
  text-align: center;
  color: var(--om-text-dim);
  border: 1px dashed var(--om-line);
  border-radius: var(--om-radius);
  gap: 10px;
}

/* ─── Sheet (mobile modal) ─── */
.om-sheet-back {
  position: absolute; inset: 0;
  background: rgba(0,0,0,.55);
  backdrop-filter: blur(2px);
  z-index: 30;
  animation: om-fade-in .15s ease both;
}
.om-sheet {
  position: absolute; left: 0; right: 0; bottom: 0;
  background: var(--om-surface);
  border-radius: var(--om-radius-lg) var(--om-radius-lg) 0 0;
  border-top: 1px solid var(--om-line-2);
  z-index: 31;
  display: flex; flex-direction: column;
  max-height: 88%;
  animation: om-sheet-up .25s cubic-bezier(.2,.8,.2,1) both;
}
.om-sheet-grip {
  width: 40px; height: 4px; border-radius: 2px;
  background: var(--om-line-2);
  margin: 8px auto 4px;
}
.om-sheet-head {
  display: flex; align-items: center; justify-content: flex-end;
  padding: 8px var(--om-pad) 10px;
  border-bottom: 1px solid var(--om-line);
}
.om-sheet-body { padding: var(--om-pad); overflow-y: auto; flex: 1; }
@keyframes om-sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes om-fade-in  { from { opacity: 0; } to { opacity: 1; } }

/* ─── Reduced motion ─── */
@media (prefers-reduced-motion: reduce) {
  .om-marquee-strip > div { animation: none; }
  .om-slide-text.scroll { animation: none; }
}


/* Desktop layout — sidebar + main side-by-side. Mobile uses
   flex-direction: column with the topbar on top and the tabbar/
   sheet below; desktop puts the sidebar on the left. The bundle's
   MarqueeApp toggles this inline; we use an explicit attribute so
   media queries can flip back to column on narrow viewports. */
.om-app[data-layout="desktop"] {
    flex-direction: row;
}
@media (max-width: 760px) {
    .om-app[data-layout="desktop"] { flex-direction: column; }
    .om-app[data-layout="desktop"] .om-side { display: none; }
}

/* Page bg + zero margin so the .om-app shell can fill the viewport. */
html, body { margin: 0; padding: 0; height: 100%; }


/* ─── Mobile sheet nav ─────────────────────────────────
   On narrow viewports, hide the persistent sidebar and show a
   hamburger button in the topbar. Clicking opens a sheet anchored
   to the bottom that mirrors the sidebar's nav. */
.om-app [hidden] { display: none !important; }
.om-sheet-open { display: none; }
@media (max-width: 760px) {
    .om-sheet-open { display: inline-flex; }
}
.om-app .om-sheet .om-side {
    display: flex;
    width: 100%;
    border-right: 0;
    background: transparent;
    padding: 8px 8px 24px;
}

/* ─── Mobile/tablet adjustments ──────────────────────────
   Below 760px (matches the sidebar-hide breakpoint), the topbar
   drops the breadcrumb + status pills (sign name + hamburger
   remain — full title is in the page-head h1 under the topbar).
   Otherwise the broadcasting + peer pills push the topbar wider
   than the narrowed viewport, dragging .om-main with them and
   cropping the playlist track + form rows on the right edge.

   Below 560px, multi-column form rows also stack to one column. */
@media (max-width: 760px) {
    .om-topbar .om-divider,
    .om-topbar [data-section-title],
    .om-topbar .om-pill { display: none; }
    .om-topbar [data-peer-pill] { display: inline-flex; flex-shrink: 0; }
}
@media (max-width: 560px) {
    .row { flex-wrap: wrap; }
    .row > .field { flex: 1 1 100%; min-width: 100%; }
}

/* Visual font picker — replaces a plain <select> for the text-slide
   editor's Font field. Underlying <select> stays as state of record
   so existing tests + form serialization keep working; visually
   clipped (not display:none) so Playwright still treats it as a real
   element for `selectOption`. */
.font-picker { position: relative; }
.font-picker .field-font-family {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    pointer-events: none;
}
.font-picker-trigger {
    display: flex; align-items: center; justify-content: space-between;
    gap: 8px;
    width: 100%;
    height: 40px;
    padding: 0 12px;
    background: var(--om-surface-2);
    color: var(--om-text);
    border: 1px solid var(--om-line);
    border-radius: 9px;
    font-size: 14px;
    cursor: pointer;
    text-align: left;
}
.font-picker-trigger:hover { border-color: var(--om-line-strong, var(--om-text-fade)); }
.font-picker-trigger:focus-visible {
    outline: 2px solid var(--om-accent);
    outline-offset: 1px;
}
.font-picker-trigger-label {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.font-picker-trigger-caret { color: var(--om-text-fade); flex-shrink: 0; }

.font-picker-popover {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 30;
    max-height: 360px;
    overflow-y: auto;
    padding: 8px;
    background: var(--om-surface);
    border: 1px solid var(--om-line);
    border-radius: 9px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
}
.font-picker-section + .font-picker-section { margin-top: 12px; }
.font-picker-section-head {
    padding: 4px 6px;
    font-family: var(--om-mono, ui-monospace, monospace);
    font-size: 10.5px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--om-text-fade);
}
.font-picker-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 6px;
    margin-top: 6px;
}
.font-picker-tile {
    display: block;
    width: 100%;
    padding: 10px 12px;
    background: var(--om-surface-2);
    color: var(--om-text);
    border: 1px solid transparent;
    border-radius: 7px;
    font-size: 18px;
    line-height: 1.2;
    cursor: pointer;
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    transition: background 80ms ease, border-color 80ms ease;
}
.font-picker-tile:hover { background: var(--om-surface-3, var(--om-line)); }
.font-picker-tile.selected {
    border-color: var(--om-accent);
    background: color-mix(in oklab, var(--om-accent) 12%, var(--om-surface-2));
}
@media (max-width: 560px) {
    .font-picker-grid { grid-template-columns: 1fr; }
}
