/* =====================================================================
   "Console" - the design language for the on-device UI.

   Borrowed from the mixing console this box actually controls:
   - Depth is surface elevation plus a 1px hairline. Never stacked shadows.
   - Amber (--signal) is an indicator lamp. It marks the now-line, the
     selected control and the focus ring, and never sits under text as a
     large fill.
   - Every number the amp reports (time, dB, IP, uptime) is monospace with
     tabular figures, so columns of them line up. That is the one
     typographic move the app is built around.

   No webfont: the device serves from LittleFS on a LAN with no internet,
   so both faces are system stacks.
   ===================================================================== */

:root {
    color-scheme: dark;

    /* --- Surfaces: four steps, each measurably lighter than the last, so
       hierarchy survives without a single drop shadow. --- */
    --surface-0: #0d0f12;
    /* page ground - cool graphite, not pure black */
    --surface-1: #14171c;
    /* panel / section */
    --surface-2: #1c2027;
    /* card / list row */
    --surface-3: #262b34;
    /* control at rest: buttons, inputs, chips */

    /* --- Separation --- */
    --line: #2f3540;
    /* hairline: every border and the shadow-as-border ring */
    --line-strong: #3d4552;
    /* control edge, hover fill */

    /* --- Text --- */
    --text-1: #eef1f5;
    /* headings, values, anything you read to act */
    --text-2: #aeb7c4;
    /* labels, secondary rows */
    --text-3: #8b95a4;
    /* micro-labels, metadata. Both are floors, not preferences: text-3 is the
       quietest tone that still clears 4.5:1 against surface-2, the lightest
       background it ever sits on. Going dimmer fails AA at 11-12px, which is
       exactly the size this tone is used at. */

    /* --- Signal & state --- */
    --signal: #ffb020;
    /* the lamp: now-line, selected, focus ring */
    --signal-ink: #0d0f12;
    /* text that sits ON signal amber (dark, always) */
    --on: #3ddc84;
    /* lit: zone playing, entry switches audio on */
    --off: #6b7684;
    /* unlit: muted. Deliberately NOT red - muting a zone
       at 22:30 is normal operation, not a fault. */
    --fault: #ff5c4d;
    /* actual faults only: amp disconnected, entry conflict, delete */

    /* High-contrast CTA (Save, primary action) */
    --invert-bg: #eef1f5;
    --invert-text: #0d0f12;

    /* --- Geometry --- */
    --r-sm: 6px;
    /* chips, small controls */
    --r-md: 10px;
    /* buttons, inputs, cards */
    --r-lg: 14px;
    /* panels */
    --r-xl: 18px;
    /* the outermost section */

    /* Resting elevation: a hairline drawn in the shadow layer (no box-model
       cost, follows the radius cleanly). Float adds real depth on top - used
       only by things that genuinely lift off the page. */
    --ring: 0 0 0 1px var(--line);
    --float: 0 0 0 1px var(--line), 0 8px 24px -8px rgba(0, 0, 0, 0.6);
}

/* Light: the same structure in daylight. Panels and cards go white and
   separate by hairline alone; the page ground steps back. Signal and state
   colors darken so they still pass contrast as text. */
html.light {
    color-scheme: light;
    /* Same direction of travel as dark: each step lifts. The panel sits back
       off the page and cards go white on top of it, so a list of rows reads
       as rows rather than one flat sheet. */
    --surface-0: #eef1f5;
    --surface-1: #f8fafc;
    --surface-2: #ffffff;
    --surface-3: #eef2f6;
    --line: #dde2e9;
    --line-strong: #c3ccd7;
    --text-1: #10141a;
    --text-2: #4a5462;
    --text-3: #5c6675;
    --signal: #a86800;
    --signal-ink: #ffffff;
    --on: #0a7a44;
    --off: #8a94a1;
    --fault: #c8342a;
    --invert-bg: #10141a;
    --invert-text: #ffffff;
    --float: 0 0 0 1px var(--line), 0 8px 24px -10px rgba(16, 20, 26, 0.25);
}

[x-cloak] {
    display: none !important;
}

html {
    scroll-behavior: smooth;
}

body {
    font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    background: var(--surface-0);
    color: var(--text-1);
    -webkit-font-smoothing: antialiased;
}

/* One focus ring for the whole app, in the lamp color. Mouse clicks don't
   raise it (:focus-visible), keyboard navigation always does. */
:focus-visible {
    outline: 2px solid var(--signal);
    outline-offset: 2px;
    border-radius: var(--r-sm);
}

@media (prefers-reduced-motion: reduce) {

    html {
        scroll-behavior: auto;
    }

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
    }
}

/* --- Surfaces -------------------------------------------------------- */

/* .panel wraps a whole section; .card is a row or tile inside one. The two
   differ by one surface step and share the same hairline, which is enough
   separation on its own - that is the point of the four-step scale. */
.panel {
    background: var(--surface-1);
    border-radius: var(--r-xl);
    box-shadow: var(--ring);
}

.card {
    background: var(--surface-2);
    border-radius: var(--r-md);
    box-shadow: var(--ring);
}

/* A bordered block inside a panel that keeps the panel's fill, so the
   cards inside it (zone tiles) still read one step up. */
.subpanel {
    border-radius: var(--r-xl);
    box-shadow: var(--ring);
}

/* Settings categories (Overview / Audio / System) and their TOC cards get a
   faint hue of their own; .tinted panels mix it into the panel surface. */
.tint-overview { --tint: #5b8cff; }
.tint-audio { --tint: #b07bff; }
.tint-system { --tint: #2fbfa8; }
.tinted .panel,
.panel.tinted {
    background: color-mix(in srgb, var(--tint) 7%, var(--surface-1));
}

/* Phones: a section panel drops its own frame - the title stands free on
   the page and the cards inside become the first level. Saves a full layer
   of padding on narrow screens. .panel-keep opts out (cards that ARE the
   content, like the settings TOC). */
@media (max-width: 639px) {
    .panel:not(.panel-keep) {
        background: transparent !important;
        box-shadow: none !important;
        padding: 0 !important;
        border-radius: 0;
    }
}

/* Only for surfaces that genuinely lift off the page (the open editor). */
.float {
    box-shadow: var(--float);
}

/* --- Type ------------------------------------------------------------ */

/* Every amp-reported number. Tabular figures so a column of times or dB
   values aligns digit-for-digit instead of jittering. */
.mono {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}

/* The single micro-label style. One per group, never stacked - it is a
   section marker, not decoration. */
.label {
    font-size: 11px;
    line-height: 1.2;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--text-3);
}

/* --- Lamp ------------------------------------------------------------ */

/* The status primitive: a small round light. Lit lamps glow faintly in
   their own color so on/off/fault read at a glance; an unlit lamp is a
   flat dark disc, which is exactly what "muted" should look like. */
.lamp {
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 999px;
    background: var(--off);
    flex-shrink: 0;
}

.lamp-on {
    background: var(--on);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--on) 18%, transparent);
}

.lamp-off {
    background: var(--off);
    box-shadow: none;
}

.lamp-fault {
    background: var(--fault);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--fault) 20%, transparent);
}

.lamp-signal {
    background: var(--signal);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--signal) 20%, transparent);
}

/* --- Controls -------------------------------------------------------- */

/* Every button/input/select shares one resting look: surface-3 with a
   hairline. Selected and primary states are the two exceptions below. */
.ctl {
    background: var(--surface-3);
    color: var(--text-2);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}

.ctl:hover:not(:disabled) {
    background: var(--line-strong);
    color: var(--text-1);
}

.ctl:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* Selected: amber edge and amber text on the control's own dark surface.
   Filling the control with amber and putting white on top is what the old
   palette did, and it failed contrast at ~2.3:1. */
.ctl-selected,
.ctl-selected:hover {
    background: color-mix(in srgb, var(--signal) 14%, var(--surface-3));
    border-color: var(--signal);
    color: var(--signal);
}

/* The one high-contrast action per view (Save, New schedule). */
.ctl-primary {
    background: var(--invert-bg);
    color: var(--invert-text);
    border: 1px solid transparent;
    border-radius: var(--r-md);
    font-weight: 600;
    transition: filter 0.12s ease;
}

.ctl-primary:hover {
    filter: brightness(0.92);
}

/* Destructive: red text on a red-tinted surface, never a solid red slab. */
.ctl-danger {
    background: color-mix(in srgb, var(--fault) 12%, transparent);
    color: var(--fault);
    border: 1px solid color-mix(in srgb, var(--fault) 35%, transparent);
    border-radius: var(--r-md);
}

.ctl-danger:hover {
    background: color-mix(in srgb, var(--fault) 20%, transparent);
}

/* Segmented control: language toggle, list/week toggle, DHCP/static, etc.
   A single hairline frame with flush children, so it reads as one control
   with two positions rather than two competing buttons. */
.seg {
    display: inline-flex;
    background: var(--surface-3);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    padding: 2px;
    gap: 2px;
}

.seg>button {
    border-radius: calc(var(--r-md) - 3px);
    color: var(--text-2);
    transition: background-color 0.12s ease, color 0.12s ease;
}

.seg>button:hover {
    color: var(--text-1);
}

.seg>button[aria-pressed="true"] {
    background: color-mix(in srgb, var(--signal) 16%, var(--surface-1));
    color: var(--signal);
}

/* Text inputs and selects. */
.field {
    background: var(--surface-0);
    color: var(--text-1);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
}

.field::placeholder {
    color: var(--text-3);
}

.field:hover {
    border-color: var(--line-strong);
}

/* --- Pills ----------------------------------------------------------- */

/* Read-only state markers in lists and chips. Tinted, never solid. */
.pill {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.125rem 0.5rem;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
    background: var(--surface-3);
    color: var(--text-2);
}

.pill-on {
    background: color-mix(in srgb, var(--on) 14%, transparent);
    color: var(--on);
}

.pill-off {
    background: var(--surface-3);
    color: var(--text-2);
}

.pill-fault {
    background: color-mix(in srgb, var(--fault) 14%, transparent);
    color: var(--fault);
}

.pill-signal {
    background: color-mix(in srgb, var(--signal) 14%, transparent);
    color: var(--signal);
}

/* --- Zone status strip ----------------------------------------------- */

/* Read-only readout of what the amp is doing right now. Renders straight
   from the already-polled zone list - it never writes to the amp. The
   scheduler stays the only thing you can edit here. */
.zone-meter {
    position: relative;
    height: 0.375rem;
    border-radius: 999px;
    background: var(--surface-0);
    box-shadow: inset 0 0 0 1px var(--line);
    overflow: hidden;
}

.zone-meter>span {
    position: absolute;
    inset: 0 auto 0 0;
    border-radius: 999px;
}

/* A muted zone still shows where its gain sits, but drained of color -
   the level is stored, it just isn't going anywhere. */
.zone-strip-muted .zone-meter>span {
    background: var(--off) !important;
    opacity: 0.55;
}

/* --- Week calendar --------------------------------------------------- */

/* Today's column: a restrained tint, plus the amber now-line below. */
.cal-today {
    background: color-mix(in srgb, var(--signal) 7%, transparent);
}

/* The now-line: a hairline of amber across the grid at the current time,
   with a dot on the left gutter. The single most useful mark on the page -
   it tells you which side of "now" every entry sits on. */
.cal-now {
    position: relative;
    height: 0;
    z-index: 2;
    pointer-events: none;
}

.cal-now::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: -1px;
    height: 1px;
    background: var(--signal);
    opacity: 0.75;
}

.cal-now::after {
    content: "";
    position: absolute;
    left: 0;
    top: -3px;
    width: 5px;
    height: 5px;
    border-radius: 999px;
    background: var(--signal);
}

/* Entry chips. Flat and tinted, with a lamp dot carrying the on/off state -
   the old solid-green slab spanning seven columns was shouting, and used
   the fault red for a normal night-time mute. */
.cal-chip {
    display: block;
    text-align: left;
    border-radius: var(--r-sm);
    border: 1px solid;
    overflow: hidden;
    transition: background-color 0.12s ease;
}

.cal-chip-on {
    background: color-mix(in srgb, var(--on) 10%, var(--surface-2));
    border-color: color-mix(in srgb, var(--on) 34%, transparent);
    color: var(--text-1);
}

.cal-chip-on:hover {
    background: color-mix(in srgb, var(--on) 22%, var(--surface-2));
}

.cal-chip-off {
    background: var(--surface-3);
    border-color: var(--line);
    color: var(--text-2);
}

.cal-chip-off:hover {
    background: var(--line-strong);
}

/* Entries firing at the same minute for a shared zone and day overwrite
   each other. Outline (not box-shadow) so it stacks on the chip's own
   border instead of replacing it. */
.conflict-ring {
    outline: 2px dashed var(--fault);
    outline-offset: 1px;
}

/* --- Editor: zone cards ---------------------------------------------- */

/* Mixer-strip card in the schedule editor. Single source of truth for the
   strip width: padding and the toggle footer's full-bleed both derive from
   --card-width via calc(), so resizing the card is one number. */
.zone-card {
    --card-width: 10rem;
    --card-pad: calc(var(--card-width) * 0.25);
    width: var(--card-width);
    padding: var(--card-pad) var(--card-pad) 0;
}

/* Phones: the strip fills its grid cell (two per row) with a tighter pad. */
@media (max-width: 639px) {
    .zone-card {
        width: auto;
        min-width: 0;
        --card-pad: 0.75rem;
    }
}

/* Zone name label: bleeds past the card's own padding so long names get
   more room, stopping a flat 1rem short of the outer edge. */
.zone-name {
    width: calc(100% + 2 * (var(--card-pad) - 1rem));
    margin-inline: calc(-1 * (var(--card-pad) - 1rem));
}

/* On/off footer: flush against the card's bottom corners and edge-to-edge,
   since it spans the card like a status strip rather than a standalone
   control. Bleeds out past --card-pad on both sides at any card width. */
.zone-toggle-footer {
    border-radius: 0 0 var(--r-md) var(--r-md);
    width: calc(100% + 2 * var(--card-pad));
    margin-inline: calc(-1 * var(--card-pad));
    border-top: 1px solid var(--line);
    font-weight: 600;
}

.zone-toggle-on {
    background: color-mix(in srgb, var(--on) 14%, transparent);
    color: var(--on);
}

.zone-toggle-off {
    background: color-mix(in srgb, var(--fault) 14%, transparent);
    color: var(--fault);
}

/* The +/- buttons and the fader track share the control family. */
.zone-control {
    width: 4rem;
    background: var(--surface-3);
    color: var(--text-2);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
}

.zone-control:hover:not(:disabled) {
    background: var(--line-strong);
    color: var(--text-1);
}

.zone-control:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

/* The fader track is a plain <div> driven by pointer events, not a native
   control, so the browser's tap highlight isn't suppressed for it - that
   shows up as a light rim flashing around the pill on touch. */
.zone-fader {
    background: var(--surface-0);
    -webkit-tap-highlight-color: transparent;
    outline: none;
}

.touch-none {
    -webkit-tap-highlight-color: transparent;
    outline: none;
}

/* Same frame as .zone-control without the fixed width - for buttons that
   size to their own text (the rhythm presets) but belong to the family. */
.control-frame {
    background: var(--surface-3);
    color: var(--text-2);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
}

.control-frame:hover {
    background: var(--line-strong);
    color: var(--text-1);
}

/* Time picker hour/minute chips: same control, just narrower since they
   only ever hold two digits. Below sm the chip grid switches to an even
   fluid track, so the chip fills its cell instead of forcing a width -
   six fixed 2.5rem chips overflow a phone screen. */
.zone-control-time {
    width: 100%;
}

@media (min-width: 640px) {
    .zone-control-time {
        width: 2.5rem;
    }
}

/* Zone color swatch: the dot stays 24px, but the button around it is a
   36px target - the swatch alone was too small to hit on a phone. */
.swatch-hit {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--r-md);
}

.swatch-hit:hover {
    background: var(--surface-3);
}

.swatch {
    width: 1.25rem;
    height: 1.25rem;
    border-radius: 999px;
    border: 1px solid var(--line-strong);
}

/* --- Settings quick-nav ---------------------------------------------- */

section[id] {
    scroll-margin-top: 5rem;
}

.toc-link {
    display: flex;
    align-items: center;
    min-height: 2rem;
    color: var(--text-2);
    border-left: 2px solid transparent;
    padding-left: 0.625rem;
    transition: color 0.12s ease, border-color 0.12s ease;
}

.toc-link:hover {
    color: var(--signal);
    border-left-color: var(--signal);
}

/* --- Schedule list grid ---------------------------------------------- */

/* The desktop shape is a fixed column table that only fits from sm up.
   Below sm it collapses into a stacked card - time on top, then state,
   name, days and zones each on their own line, with edit spanning the full
   height on the right - so a row fits a phone without horizontal scroll.
   The header row is pure column labels, meaningless once stacked, so it is
   hidden below sm rather than collapsed too. */
.schedule-grid {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
        "status time  edit"
        "state  input edit"
        "name   name  edit"
        "days   days  edit"
        "zones  zones edit";
    row-gap: 0.375rem;
    column-gap: 0.75rem;
    align-items: center;
}

.schedule-grid>.col-status {
    grid-area: status;
}

.schedule-grid>.col-time {
    grid-area: time;
}

.schedule-grid>.col-state {
    grid-area: state;
}

.schedule-grid>.col-input {
    grid-area: input;
}

.schedule-grid>.col-days {
    grid-area: days;
}

.schedule-grid>.col-name {
    grid-area: name;
}

.schedule-grid>.col-zones {
    grid-area: zones;
}

.schedule-grid>.col-edit {
    grid-area: edit;
    align-self: center;
}

.schedule-grid-header {
    display: none;
}

/* Every column label is clipped to its own track, so a long translation can
   never run into the next header the way "Lautstärke"+"Eingang" did. */
.schedule-grid-header>* {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (min-width: 640px) {

    /* Column budget: every fixed width below must sum (plus gaps) to less
       than the panel's content width - otherwise the grid scrolls and the
       sticky edit column floats OVER the zones column, hiding its last
       chips. Flexible columns (input/name/zones) absorb the leftover. */
    .schedule-grid,
    .schedule-grid-header {
        grid-template-columns: 112px 64px 88px 72px minmax(90px, 0.9fr) minmax(100px, 1.1fr) minmax(120px, 1.2fr) 44px;
        grid-template-areas: "status time days state input name zones edit";
        column-gap: 0.75rem;
    }

    .schedule-grid-header {
        display: grid;
    }
}
