/* Self-hosted rather than fetched from Google Fonts: it drops two DNS lookups
   and two TLS handshakes to a third party from the critical path, which on a
   cold mobile connection is the difference between text appearing immediately
   and a visible flash of fallback. Only the latin subsets are vendored — the
   accented characters Italian needs live in U+00C0-00FF, inside latin.

   One file per subset covers weights 300-500: Google Sans Flex is a variable
   font, so the three weights the design uses are axis positions in the same
   binary, not three downloads. */
@font-face {
    font-family: "Google Sans Flex";
    src: url("/assets/fonts/google-sans-flex-latin.woff2") format("woff2");
    font-weight: 300 500;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
        U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
        U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Google Sans Flex";
    src: url("/assets/fonts/google-sans-flex-latin-ext.woff2") format("woff2");
    font-weight: 300 500;
    font-style: normal;
    font-display: swap;
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
        U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
        U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
        U+A720-A7FF;
}

:root {
    color-scheme: light;
    --bg: #f2f5f4;
    --surface: #ffffff;
    --surface-soft: #f7f9f8;
    --surface-strong: #e9efed;
    --text: #17201e;
    --muted: #68736f;
    --faint: #9aa4a0;
    --line: #dce3e0;
    --line-strong: #c8d2ce;
    --accent: #237a70;
    --accent-soft: #dff1ed;
    --cool: #3879c9;
    --cool-soft: #e3eefb;
    --heat: #d4654f;
    --heat-soft: #fae8e3;
    --dry: #8a6cb0;
    --dry-soft: #eee8f5;
    --fan: #547c73;
    --fan-soft: #e3efeb;
    --danger: #b34c43;
    --warning: #9a6a18;
    --ok: #23815f;
    --shadow: 0 16px 45px rgba(32, 50, 45, 0.07);
}

* {
    box-sizing: border-box;
}

html {
    scroll-behavior: smooth;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family:
        Inter,
        ui-sans-serif,
        system-ui,
        -apple-system,
        BlinkMacSystemFont,
        "Segoe UI",
        sans-serif;
    line-height: 1.45;
    /* Chrome's pull-to-refresh reads as a bug in a full-screen app: you nudge a
       room card and the whole page reloads. */
    overscroll-behavior-y: none;
    /* The app already answers a press with a scale transform on :active; the
       grey flash on top of it looks like a double reaction. */
    -webkit-tap-highlight-color: transparent;
}

button,
input,
select {
    font: inherit;
}

.iconly-icon {
    display: inline-block;
    width: 20px;
    height: 20px;
    flex: 0 0 auto;
    object-fit: contain;
}

.inline-iconly {
    width: 14px;
    height: 14px;
    margin: 0 2px;
    vertical-align: -2px;
}
button {
    cursor: pointer;
}
button:disabled {
    cursor: not-allowed;
    opacity: 0.45;
}

.app-header {
    position: sticky;
    z-index: 20;
    top: 0;
    border-bottom: 1px solid rgba(220, 227, 224, 0.9);
    background: rgba(242, 245, 244, 0.92);
    backdrop-filter: blur(18px);
}

.header-inner {
    display: flex;
    width: min(1280px, calc(100% - 40px));
    min-height: 68px;
    margin: 0 auto;
    align-items: center;
    justify-content: space-between;
    /* With viewport-fit=cover the header sits under the status bar / notch on a
       phone. These insets are 0 on every desktop browser, so the layout there
       is unchanged. */
    padding-top: env(safe-area-inset-top);
    padding-right: env(safe-area-inset-right);
    padding-left: env(safe-area-inset-left);
}

.brand {
    display: inline-flex;
    gap: 10px;
    align-items: center;
    color: var(--text);
    font-size: 15px;
    font-weight: 750;
    text-decoration: none;
}

.brand-mark {
    display: grid;
    width: 34px;
    height: 34px;
    place-items: center;
    border-radius: 8px;
    color: #fff;
    background: var(--text);
    font-size: 11px;
}

.header-actions {
    display: flex;
    gap: 8px;
    align-items: center;
}

.connection-pill {
    display: inline-flex;
    gap: 7px;
    align-items: center;
    min-height: 36px;
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0 12px;
    color: var(--muted);
    background: var(--surface);
    font-size: 12px;
    font-weight: 700;
}

.connection-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--faint);
}
.is-connected .connection-dot {
    background: var(--ok);
    box-shadow: 0 0 0 4px rgba(35, 129, 95, 0.11);
}

.icon-button {
    display: grid;
    width: 38px;
    height: 38px;
    place-items: center;
    border: 1px solid var(--line);
    border-radius: 50%;
    padding: 0;
    color: var(--text);
    background: var(--surface);
    font-size: 18px;
}
.icon-button .iconly-icon {
    width: 19px;
    height: 19px;
}

@media (hover: hover) {
    .icon-button:hover:not(:disabled) {
        border-color: var(--line-strong);
        background: var(--surface-strong);
    }
}

.shell {
    width: min(1280px, calc(100% - 40px));
    margin: 0 auto;
    /* The extra bottom padding clears the gesture-navigation bar; the existing
       60px is the floor, so nothing changes on desktop. */
    padding: 28px 0 calc(60px + env(safe-area-inset-bottom));
}

.login-stage {
    display: grid;
    grid-template-columns: minmax(0, 0.8fr) minmax(420px, 1.2fr);
    gap: 40px;
    align-items: end;
    min-height: 310px;
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 42px;
    background: var(--surface);
    box-shadow: var(--shadow);
}

.is-connected .login-stage {
    display: none;
}

/* Which of the two forms applies is only known once /api/session answers with
   200 or 401, so both stay hidden for that one round-trip rather than flashing
   the wrong one. */
.is-booting .login-stage {
    display: none;
}

.gate-stage {
    display: none;
}

body:not(.is-unlocked):not(.is-booting) .gate-stage {
    display: grid;
}

body:not(.is-unlocked) #loginStage {
    display: none;
}

.login-intro {
    display: flex;
    gap: 18px;
    align-items: flex-start;
}
.login-symbol {
    display: grid;
    width: 54px;
    height: 54px;
    place-items: center;
    border-radius: 50%;
    color: var(--accent);
    background: var(--accent-soft);
    font-size: 26px;
}
.login-symbol .iconly-icon {
    width: 27px;
    height: 27px;
}
.eyebrow {
    margin: 0 0 7px;
    color: var(--muted);
    font-size: 11px;
    font-weight: 800;
    text-transform: uppercase;
}
h1,
h2,
p {
    margin-top: 0;
}
h1 {
    margin-bottom: 0;
    font-size: 36px;
    line-height: 1.08;
    letter-spacing: 0;
}
h2 {
    margin: 0;
    font-size: 18px;
    letter-spacing: 0;
}

.login-form {
    display: grid;
    grid-template-columns: 1fr 1fr auto;
    gap: 10px;
    align-items: end;
}

label {
    display: grid;
    gap: 6px;
    color: var(--muted);
    font-size: 11px;
    font-weight: 750;
}
input,
select {
    width: 100%;
    min-height: 44px;
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0 12px;
    color: var(--text);
    background: var(--surface-soft);
}
input:focus,
select:focus {
    border-color: var(--accent);
    outline: 3px solid rgba(35, 122, 112, 0.12);
}

.connect-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 44px;
    border: 0;
    border-radius: 8px;
    padding: 0 18px;
    color: #fff;
    background: var(--text);
    font-weight: 750;
}
.connect-button .iconly-icon {
    width: 17px;
    height: 17px;
}

@media (hover: hover) {
    .connect-button:hover {
        background: var(--accent);
    }
}

.status {
    display: none;
    margin: 14px 0 0;
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 10px 13px;
    color: var(--muted);
    background: var(--surface);
    font-size: 12px;
}
.status.error,
.status.idle:not(:first-of-type) {
    display: block;
}
.status.error {
    border-color: #e8c5c0;
    color: var(--danger);
    background: #fff5f3;
}
.status.ok {
    display: block;
    border-color: #bedfd3;
    color: var(--ok);
    background: #f0faf6;
}
.is-connected .status.ok {
    display: none;
}

.rooms-section {
    display: none;
}
.is-connected .rooms-section {
    display: block;
}
.units {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
    gap: 14px;
}
.units.empty {
    display: block;
}
.empty-state {
    display: grid;
    min-height: 260px;
    place-items: center;
    align-content: center;
    color: var(--muted);
}
.empty-state span {
    font-size: 34px;
}

.workspace {
    display: none;
    gap: 10px;
    margin-top: 26px;
}
.is-connected .workspace {
    display: grid;
}
.workspace-section {
    overflow: hidden;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--surface);
}
.workspace-section > summary {
    display: grid;
    grid-template-columns: 40px 1fr auto;
    gap: 12px;
    align-items: center;
    min-height: 72px;
    padding: 0 18px;
    cursor: pointer;
    list-style: none;
}
@media (hover: hover) {
    .workspace-section > summary:hover {
        background: var(--surface-soft);
    }
}
.summary-icon {
    display: grid;
    width: 36px;
    height: 36px;
    place-items: center;
    border: 1px solid var(--line);
    border-radius: 50%;
    color: var(--accent);
    background: var(--surface-soft);
    font-size: 14px;
}
.summary-icon .iconly-icon {
    width: 18px;
    height: 18px;
}
.workspace-section summary strong {
    display: block;
    font-size: 13px;
}
.workspace-section summary small {
    display: block;
    margin-top: 2px;
    color: var(--muted);
    font-size: 10px;
    font-weight: 500;
}
.workspace-content {
    border-top: 1px solid var(--line);
    padding: 20px;
}
.system-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 18px;
}
.system-grid {
    grid-template-columns: 1fr 0.55fr;
}
.feature-panel {
    grid-column: 1 / -1;
}
.tool-pane {
    min-width: 0;
}
.tool-pane + .tool-pane {
    border-left: 1px solid var(--line);
    padding-left: 18px;
}
.tool-heading {
    display: flex;
    margin-bottom: 14px;
    justify-content: space-between;
}
.tool-heading p,
.tool-pane > p {
    margin: 3px 0 0;
    color: var(--muted);
    font-size: 10px;
}

.schedules {
    display: grid;
    gap: 0;
    margin-top: 14px;
}
.schedules.empty {
    color: var(--muted);
    font-size: 11px;
}
.diagnosis {
    border-left: 3px solid var(--line);
    padding-left: 12px;
}
.diagnosis h3 {
    margin: 0 0 4px;
    font-size: 13px;
}
.diagnosis p {
    margin: 0;
    color: var(--muted);
    font-size: 10px;
}
.diagnosis.ok {
    border-color: var(--ok);
}
.diagnosis.warning {
    border-color: var(--warning);
}
.diagnosis.critical {
    border-color: var(--danger);
}
.metrics dl {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin: 12px 0 0;
}
.metrics dl div {
    border-left: 1px solid var(--line);
    padding-left: 12px;
}
dt {
    color: var(--muted);
    font-size: 9px;
    font-weight: 750;
}
dd {
    margin: 2px 0 0;
    font-size: 22px;
    font-weight: 650;
}

.feature-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 20px;
    margin-top: 10px;
}
.feature-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 8px;
    align-items: center;
    border-top: 1px solid var(--line);
    padding: 9px 0;
    font-size: 10px;
}
.feature-row small {
    display: none;
}
.feature-status {
    font-size: 9px;
    font-weight: 750;
}
.feature-status.supported {
    color: var(--ok);
}
.feature-status.unavailable {
    color: var(--faint);
}

.developer-actions {
    display: flex;
    gap: 8px;
    margin-bottom: 12px;
}
.secondary {
    min-height: 38px;
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 0 12px;
    color: var(--text);
    background: var(--surface-soft);
}
pre {
    max-height: 460px;
    margin: 0;
    overflow: auto;
    border-radius: 8px;
    padding: 15px;
    color: #dce8e4;
    background: #17221f;
    font-size: 10px;
    white-space: pre-wrap;
}

@media (max-width: 900px) {
    .login-stage {
        grid-template-columns: 1fr;
    }
    .login-form {
        grid-template-columns: 1fr 1fr;
    }
    .connect-button {
        grid-column: 1 / -1;
    }
    .system-grid {
        grid-template-columns: 1fr;
    }
    .feature-panel {
        grid-column: auto;
    }
    .tool-pane + .tool-pane {
        border-top: 1px solid var(--line);
        border-left: 0;
        padding-top: 18px;
        padding-left: 0;
    }
}

@media (max-width: 620px) {
    .header-inner,
    .shell {
        width: min(100% - 20px, 1280px);
    }
    .header-inner {
        min-height: 60px;
    }
    .connection-pill {
        padding: 0 9px;
    }
    .brand > span:last-child {
        display: none;
    }
    .shell {
        padding-top: 14px;
    }
    .login-stage {
        min-height: auto;
        padding: 22px 18px;
    }
    .login-intro {
        display: grid;
    }
    .login-form {
        grid-template-columns: 1fr;
    }
    .connect-button {
        grid-column: auto;
    }
    h1 {
        font-size: 30px;
    }
    .units {
        grid-template-columns: 1fr;
    }
    .workspace-section > summary {
        min-height: 66px;
        padding: 0 13px;
    }
    .workspace-content {
        padding: 15px;
    }
    .feature-grid {
        grid-template-columns: 1fr;
    }
}

/* Glass room cards: the landing view keeps the live controls visible while
   the detailed MELCloud settings stay behind the summary row. */
:root {
    --bg: #398fe3;
    --surface: rgba(132, 198, 247, 0.2);
    --surface-soft: rgba(145, 205, 249, 0.18);
    --surface-strong: rgba(222, 242, 255, 0.3);
    --text: #062d62;
    --muted: #0c4b85;
    --faint: #75aade;
    --line: rgba(220, 240, 255, 0.53);
    --line-strong: rgba(236, 248, 255, 0.78);
    --accent: #f0f8ff;
    --accent-soft: rgba(231, 245, 255, 0.48);
    --cool: #063d7c;
    --cool-soft: rgba(221, 241, 255, 0.6);
    --heat: #08386f;
    --heat-soft: rgba(255, 237, 219, 0.42);
    --dry: #174f87;
    --dry-soft: rgba(224, 239, 255, 0.42);
    --fan: #0a4b83;
    --fan-soft: rgba(218, 243, 255, 0.42);
    --danger: #7b2c3a;
    --warning: #704b13;
    --ok: #075b55;
    --shadow: 0 24px 70px rgba(12, 65, 127, 0.2);
}

body {
    /* dvh, not vh: with vh the layout is sized for the tallest viewport and
       jumps by the height of the URL bar the moment it hides. */
    min-height: 100dvh;
    background-color: var(--bg);
    background-image: linear-gradient(
        135deg,
        #2e84dc 0%,
        #4d9be7 55%,
        #378bdd 100%
    );
}

.app-header {
    border-bottom-color: rgba(218, 240, 255, 0.3);
    background: rgba(48, 137, 222, 0.32);
}

.brand {
    color: var(--text);
}
.brand-mark {
    color: var(--text);
    background: rgba(226, 244, 255, 0.66);
}
.connection-pill,
.icon-button {
    border-color: var(--line);
    color: var(--text);
    background: rgba(226, 244, 255, 0.2);
}
@media (hover: hover) {
    .icon-button:hover:not(:disabled) {
        border-color: var(--line-strong);
        background: var(--surface-strong);
    }
}
.connection-dot {
    background: var(--faint);
}
.is-connected .connection-dot {
    background: #d9fff1;
    box-shadow: 0 0 0 4px rgba(216, 255, 242, 0.16);
}

.shell {
    width: min(1200px, calc(100% - 56px));
    padding-top: 34px;
}
.login-stage,
.workspace-section {
    border-color: var(--line);
    background: rgba(125, 192, 244, 0.2);
    box-shadow: var(--shadow);
    backdrop-filter: blur(22px);
}
.login-stage {
    border-radius: 34px;
}
.login-symbol {
    color: var(--text);
    background: rgba(226, 244, 255, 0.34);
}
.eyebrow,
.target-label,
.room-temperature > span {
    color: var(--muted);
}
input,
select {
    border-color: var(--line);
    color: var(--text);
    background: rgba(234, 247, 255, 0.22);
}
input:focus,
select:focus {
    border-color: var(--line-strong);
    outline-color: rgba(232, 247, 255, 0.24);
}
.connect-button {
    color: var(--text);
    background: rgba(232, 247, 255, 0.68);
}
@media (hover: hover) {
    .connect-button:hover {
        background: rgba(246, 251, 255, 0.88);
    }
}
.status {
    border-color: var(--line);
    color: var(--text);
    background: rgba(226, 244, 255, 0.2);
}
.status.error {
    border-color: rgba(255, 214, 219, 0.72);
    color: var(--danger);
    background: rgba(255, 235, 237, 0.34);
}
.status.ok {
    border-color: rgba(211, 255, 240, 0.7);
    color: var(--ok);
    background: rgba(220, 255, 246, 0.26);
}

.workspace {
    margin-top: 32px;
}
.workspace-section {
    border-radius: 24px;
}
@media (hover: hover) {
    .workspace-section > summary:hover {
        background: rgba(227, 245, 255, 0.12);
    }
}
.summary-icon {
    border-color: var(--line);
    color: var(--text);
    background: rgba(226, 244, 255, 0.2);
}
.workspace-content {
    border-top-color: var(--line);
}
.secondary {
    border-color: var(--line);
    color: var(--text);
    background: rgba(226, 244, 255, 0.18);
}
pre {
    color: #e4f4ff;
    background: rgba(4, 40, 83, 0.5);
}

@media (max-width: 760px) {
    .shell {
        width: min(100% - 24px, 1200px);
        padding-top: 20px;
    }
}

/* ============================================================
   Room Card — rebuilt 1:1 from the Paper designs "AC App":
   Room Card - Power OFF / Power ON / Expanded Setting UI.
   Ink #0A315C on translucent white glass over the blue canvas.
   ============================================================ */
:root {
    --card-ink: #0a315c;
    --card-ink-72: rgba(10, 49, 92, 0.72);
    --card-glass-8: rgba(255, 255, 255, 0.08);
    --card-glass-16: rgba(255, 255, 255, 0.16);
    --card-glass-64: rgba(255, 255, 255, 0.64);
    --card-glass-border-gradient: linear-gradient(
        to bottom,
        rgba(255, 255, 255, 1) 0%,
        rgba(255, 255, 255, 0.04) 50%,
        rgba(255, 255, 255, 0.12) 100%
    );
    --card-inset: rgba(29, 100, 180, 0.08);
    /* The inverse of the glass hairline. A recessed bed is not lit from above,
       it is shadowed into the surface, so its ring is the canvas blue at low
       alpha -- heaviest along the top lip, almost gone across the middle, a
       faint return at the bottom. Stops are the Paper file's own: 1D64B4 at
       16% / 4% / 8%. */
    --card-inset-border-gradient: linear-gradient(
        to bottom,
        rgba(29, 100, 180, 0.16) 0%,
        rgba(29, 100, 180, 0.04) 50%,
        rgba(29, 100, 180, 0.08) 100%
    );
    --card-font: "Google Sans Flex", system-ui, sans-serif;
}

body {
    background: #4c9ae6;
}

.units {
    display: grid;
    grid-template-columns: 1fr;
    gap: 24px;
}
.units.empty {
    display: block;
}

.unit {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    width: min(380px, 100%);
    justify-self: center;
    border-radius: 28px;
    padding: 8px;
    color: var(--card-ink);
    background: var(--card-glass-8);
    font-family: var(--card-font);
}

/* No backdrop-filter on the card itself, deliberately.
 *
 * An element with backdrop-filter becomes a Backdrop Root, and a nested
 * backdrop-filter can then only sample that root's own backdrop -- which is
 * empty -- so every blur *inside* the card silently rendered as nothing. That
 * cost us the slider thumb's lens, .inner-glass and the standby strip, all
 * three of which have something real to blur (the ticks, the card's own
 * fills). The card had nothing: behind it is only the page's smooth gradient,
 * and blurring a smooth gradient is a no-op you cannot see. Trading an
 * invisible blur for three visible ones. */

/* Header row: room identity on the left, target panel on the right */
.room-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.room-info {
    display: flex;
    align-items: center;
    gap: 24px;
    min-width: 0;
    padding-left: 16px;
}
.room-icon {
    display: block;
    width: 32px;
    height: 32px;
    flex: 0 0 auto;
}
.room-info-text {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}
.room-info-text > span {
    font-size: 12px;
    font-weight: 500;
    line-height: 12px;
    opacity: 0.72;
}
.room-info-text > strong {
    font-size: 32px;
    font-weight: 400;
    line-height: 32px;
    white-space: nowrap;
}
.degree {
    display: inline-block;
    margin-left: 2px;
    color: currentColor;
    font-size: 0.62em;
    font-weight: 400;
    line-height: 1;
    opacity: 0.4;
    vertical-align: top;
}

.target-panel {
    position: relative;
    display: flex;
    flex-direction: column;
    border-radius: 20px;
    padding: 16px;
}
.unit.is-on .target-panel {
    border-radius: 20px 20px 0 0;
}
/* Mirror the left side: label row ↔ room name, temperature ↔ temperature,
   power button vertically centered like the room icon. */
.target-row {
    display: flex;
    align-items: center;
    gap: 24px;
}
.target-info {
    display: flex;
    flex-direction: column;
    gap: 4px;
    width: 71px;
    flex: 0 0 71px;
}
.target-info > span {
    font-size: 12px;
    font-weight: 400;
    line-height: 12px;
}
.unit.is-off .target-info > span {
    opacity: 0.72;
}
.target-value {
    display: flex;
    align-items: flex-start;
    gap: 0;
    color: var(--card-ink);
    font-size: 32px;
    font-weight: 400;
    line-height: 32px;
}
/* Inline-grid mirror keeps the input exactly as wide as its value,
   so the degree sign hugs the digits like the design's plain text. */
.target-sizer {
    display: inline-grid;
    min-width: 18px;
}
.target-sizer > span {
    grid-area: 1 / 1;
    visibility: hidden;
    white-space: pre;
}
.target-value input {
    grid-area: 1 / 1;
    /* width:0 keeps the input out of the grid track's intrinsic sizing —
       the hidden mirror alone decides the width; min-width refills the track. */
    width: 0;
    min-width: 100%;
    height: 32px;
    min-height: 0;
    border: 0;
    padding: 0;
    color: var(--card-ink);
    background: transparent;
    font: inherit;
    appearance: textfield;
}
.target-value input:focus {
    outline: none;
}
.target-value input::-webkit-inner-spin-button,
.target-value input::-webkit-outer-spin-button {
    margin: 0;
    appearance: none;
}

.target-divider {
    position: absolute;
    right: 16px;
    bottom: 0;
    left: 16px;
    height: 1px;
    background: var(--card-glass-16);
}

.power-button {
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    flex: 0 0 auto;
    border: 0;
    border-radius: 999px;
    padding: 0;
    background: var(--card-glass-8);
}
.power-button.active {
    background: var(--card-glass-64);
}
.power-button img {
    display: block;
    width: 32px;
    height: 32px;
}

/* The inner surfaces remain visually distinct from the room shell, but their
   shared outline is painted once by .inner-border below. */
.unit-advanced {
    align-self: stretch;
    position: relative;
}

/* The target panel and advanced controls form one irregular inner shape.
   JS traces a single path from their measured rects (five convex corners
   plus a concave fillet at the panel/summary junction); the same path
   clips the shared glass fill and strokes the gradient outline. */
/* No backdrop-filter here, for the same reason the card itself has none (see
   the note above .room-card-header) and for one more: this layer's clip-path is
   rewritten on every frame of the expand. A backdrop-filter whose clip changes
   forces the compositor to re-read and re-blur the region each frame, which is
   what made the outline stutter and fringe as the panel slid open. And the blur
   was buying nothing — .inner-glass paints beneath all card content, so its
   backdrop is only the card's flat 8% fill over the page's smooth gradient, and
   blurring a smooth gradient is a no-op you cannot see. It was already off on
   coarse pointers for the same budget reason. */
.inner-glass {
    position: absolute;
    inset: 0;
    background: var(--card-glass-8);
    pointer-events: none;
}
.inner-border {
    position: absolute;
    z-index: 1;
    inset: 0;
    pointer-events: none;
}
.inner-border svg {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
}
.inner-border-path {
    fill: none;
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}

/* Paint the gradient only into the single outer ring. The mask removes the
   padding box, keeping the shared glass fill untouched. */
.unit::before {
    position: absolute;
    z-index: 2;
    inset: 0;
    border: 1px solid transparent;
    border-radius: inherit;
    background: var(--card-glass-border-gradient) border-box;
    content: "";
    pointer-events: none;
    -webkit-mask:
        linear-gradient(#fff 0 0) border-box,
        linear-gradient(#fff 0 0) padding-box;
    -webkit-mask-composite: xor;
    mask:
        linear-gradient(#fff 0 0) border-box,
        linear-gradient(#fff 0 0) padding-box;
    mask-composite: exclude;
}
.unit-advanced > summary {
    display: block;
    padding: 12px 16px;
    cursor: pointer;
    list-style: none;
}
.unit-advanced > summary::-webkit-details-marker {
    display: none;
}
.card-summary-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.summary-item {
    display: flex;
    align-items: center;
}
.summary-item strong {
    font-size: 16px;
    font-weight: 500;
    line-height: 24px;
    white-space: nowrap;
}
.summary-fan strong,
.summary-vertical strong,
.summary-horizontal strong,
.dropdown-value strong,
.dropdown-option strong {
    box-sizing: border-box;
    display: block;
    flex: 0 0 16px;
    width: 16px;
    text-align: left;
}
.summary-mode {
    gap: 6px;
}
.summary-mode img {
    display: block;
    width: 16px;
    height: 16px;
}
.summary-fan,
.summary-vertical,
.summary-horizontal {
    gap: 6px;
}
.summary-fan img {
    display: block;
    width: 16px;
    height: 16px;
}
.timer-summary {
    gap: 8px;
}
.timer-summary img {
    display: block;
    width: 16px;
    height: 16px;
}
.summary-chevron {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    flex: 0 0 auto;
    border-radius: 999px;
    background: var(--card-glass-8);
}
.summary-chevron img {
    display: block;
    width: 12px;
    height: 12px;
}
.summary-chevron img,
.workspace-section .chevron i {
    transition: transform 0.2s ease;
}
.workspace-section .chevron > .iconly-icon {
    transition: transform 0.2s ease;
}

/* Only the glyph turns. Rotating the button would carry its top-lit hairline
   round with it and light the ring from below. */
.unit-advanced[open] .summary-chevron img {
    transform: rotate(180deg);
}

/* Vane glyphs: vertical vane = stacked slats, horizontal vane = upright lines.
   The selected position is full ink; the remaining positions sit at 40%. */
.direction-icon {
    display: flex;
    justify-content: space-between;
    flex: 0 0 auto;
}
.direction-icon i {
    display: block;
    background: var(--card-ink);
}
.direction-icon i {
    opacity: 0.4;
}
.direction-icon i.is-active {
    opacity: 1;
}
.direction-icon--vertical {
    flex-direction: column;
}
.direction-icon--horizontal {
    flex-direction: row;
}
.direction-icon--vertical {
    width: 16px;
    height: 16px;
}
.direction-icon--vertical i {
    width: 16px;
    height: 2px;
}
.direction-icon--horizontal {
    width: 16px;
    height: 16px;
}
.direction-icon--horizontal i {
    width: 2px;
    height: 16px;
}

/* Expanded settings panel */
.advanced-content {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    height: 318px;
    padding: 0 16px 12px;
}

/* Glass expansion: the panel slides open and closed like a sheet of glass,
   then the controls surface one after another. */
@supports selector(::details-content) {
    .unit-advanced::details-content {
        display: block;
        height: 0;
        overflow: clip;
        opacity: 0;
        transition:
            height 0.45s cubic-bezier(0.2, 0.7, 0.2, 1),
            opacity 0.35s ease,
            content-visibility 0.45s allow-discrete;
    }
    .unit-advanced[open]::details-content {
        height: 318px;
        opacity: 1;
        animation: panel-unclip 0s 0.5s forwards;
    }
}
@keyframes panel-unclip {
    to {
        overflow: visible;
    }
}
/* A panel re-rendered while already open must not replay the reveal. */
.unit-advanced.no-reveal .advanced-content > * {
    animation: none;
}
.unit-advanced[open] .advanced-content > * {
    animation: card-reveal 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) backwards;
}
.unit-advanced[open] .advanced-content > :nth-child(2) {
    animation-delay: 0.07s;
}
.unit-advanced[open] .advanced-content > :nth-child(3) {
    animation-delay: 0.12s;
}
.unit-advanced[open] .advanced-content > :nth-child(4) {
    animation-delay: 0.17s;
}
.unit-advanced[open] .advanced-content > :nth-child(5) {
    animation-delay: 0.22s;
}
.unit-advanced[open] .advanced-content > :nth-child(6) {
    animation-delay: 0.27s;
}
@keyframes card-reveal {
    from {
        opacity: 0;
        transform: translateY(12px);
    }
}
::view-transition-group(*) {
    animation-duration: 0.35s;
    animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
}
@media (prefers-reduced-motion: reduce) {
    .unit-advanced::details-content {
        transition: none;
    }
    .unit-advanced[open] .advanced-content > * {
        animation: none;
    }
}
.advanced-divider {
    flex: 0 0 1px;
    height: 1px;
    margin: 0 -1px;
    background: var(--card-glass-16);
}

/* Temperature ruler: one tick per 0.5° step (tall = whole degree,
   short = half degree). The track is inset 16px each side so the thumb
   never leaves the container; ticks and thumb share the same formula. */
.panel-progress {
    --slider-gutter: 16px;
    position: relative;
    height: 80px;
    flex: 0 0 80px;
}
.slider-ticks {
    position: absolute;
    top: 32px;
    right: 0;
    left: 0;
    height: 32px;
}
.slider-ticks i {
    position: absolute;
    bottom: 0;
    width: 2px;
    background: var(--card-ink);
    transform: translateX(-50%);
}
.slider-ticks i.major {
    height: 32px;
}
.slider-ticks i.minor {
    height: 24px;
}
.slider-value {
    position: absolute;
    top: 0;
    left: clamp(
        var(--slider-gutter),
        calc(
            var(--slider-gutter) + (100% - (var(--slider-gutter) * 2)) *
                var(--progress)
        ),
        calc(100% - var(--slider-gutter))
    );
    transform: translateX(-50%);
    font-size: 24px;
    font-weight: 400;
    line-height: 24px;
    white-space: nowrap;
    pointer-events: none;
}
.progress-thumb {
    position: absolute;
    top: 48px;
    left: clamp(
        var(--slider-gutter),
        calc(
            var(--slider-gutter) + (100% - (var(--slider-gutter) * 2)) *
                var(--progress)
        ),
        calc(100% - var(--slider-gutter))
    );
    transform: translateX(-50%);
    width: 32px;
    height: 32px;
    border-radius: 999px;
    background: var(--card-glass-64);
    /* The thumb is a lens, not a dot: the ticks it passes over are blurred
       through it, which is what makes the strip read as glass rather than as
       an opaque marker sliding along a rule. */
    backdrop-filter: blur(4px);
    pointer-events: none;
}

.progress-thumb::after {
    position: absolute;
    inset: 0;
    border: 1px solid transparent;
    border-radius: inherit;
    background: var(--card-glass-border-gradient) border-box;
    content: "";
    -webkit-mask:
        linear-gradient(#fff 0 0) border-box,
        linear-gradient(#fff 0 0) padding-box;
    -webkit-mask-composite: xor;
    mask:
        linear-gradient(#fff 0 0) border-box,
        linear-gradient(#fff 0 0) padding-box;
    mask-composite: exclude;
}
.temperature-slider {
    position: absolute;
    top: 24px;
    left: 0;
    width: 100%;
    height: 56px;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}

/* Mode strip */
.mode-strip {
    position: relative;
    display: flex;
    isolation: isolate;
    height: 56px;
    flex: 0 0 56px;
    overflow: hidden;
    border-radius: 20px;
    background: var(--card-inset);
}
/* The selected mode wears the power button's treatment -- white 64% under the
   glass hairline -- and slides between positions, so it has to be a real
   element: a pseudo-element cannot carry a ring of its own. */
.mode-pill {
    position: absolute;
    z-index: 0;
    inset: 0 auto 0 0;
    width: 20%;
    border-radius: 20px;
    background: var(--card-glass-64);
    pointer-events: none;
    transform: translateX(calc(var(--mode-index, 0) * 100%));
    transition: transform 0.36s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.choice-button {
    position: relative;
    z-index: 1;
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    min-width: 0;
    border: 0;
    border-radius: 20px;
    color: var(--card-ink-72);
    background: transparent;
    font-family: var(--card-font);
}
.choice-button img {
    display: block;
    width: 20px;
    height: 20px;
    opacity: 0.72;
}
.choice-button small {
    font-size: 12px;
    font-weight: 500;
    line-height: 12px;
}
.choice-button.active {
    color: var(--card-ink);
    background: transparent;
}
.choice-button.active img {
    opacity: 1;
}

/* Fan / vane dropdown pills */
.figma-dropdown-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.figma-dropdown {
    position: relative;
}
.figma-dropdown > summary {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 15px;
    border-radius: 20px;
    padding: 4px 4px 4px 12px;
    background: var(--card-inset);
    cursor: pointer;
    list-style: none;
}
.figma-dropdown > summary::-webkit-details-marker {
    display: none;
}
.dropdown-value {
    display: flex;
    align-items: center;
    gap: 8px;
}
.dropdown-value {
    min-width: 0;
    flex: 1 1 auto;
    justify-content: flex-start;
}
.dropdown-value img {
    display: block;
    width: 16px;
    height: 16px;
}
.dropdown-value strong {
    font-size: 20px;
    font-weight: 400;
    line-height: 20px;
}
.dropdown-chevron {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex: 0 0 auto;
    border-radius: 999px;
    background: var(--card-glass-8);
}
.dropdown-chevron img {
    display: block;
    width: 12px;
    height: 12px;
    transition: transform 0.2s ease;
}
.figma-dropdown[open] .dropdown-chevron img {
    transform: rotate(180deg);
}
.dropdown-options {
    position: absolute;
    z-index: 10;
    top: calc(100% + 8px);
    bottom: auto;
    left: 0;
    display: grid;
    gap: 2px;
    width: max-content;
    min-width: 100%;
    border-radius: 16px;
    padding: 6px;
    background: #9dd3fa;
    box-shadow: 0 12px 28px rgba(10, 49, 92, 0.16);
}
.dropdown-option {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    width: 100%;
    gap: 8px;
    min-height: 36px;
    border: 0;
    border-radius: 12px;
    padding: 0 12px 0 6px;
    color: var(--card-ink);
    background: transparent;
    font-family: var(--card-font);
}
.dropdown-option img {
    display: block;
    width: 16px;
    height: 16px;
}
.dropdown-option .direction-icon {
    display: flex;
    width: 16px;
    height: 16px;
}
.dropdown-option strong {
    font-size: 20px;
    font-weight: 400;
    line-height: 20px;
}
/* .active is a selection state, not a pointer state — it must keep applying on
   touch, so it stays outside the hover guard. */
.dropdown-option.active {
    background: rgba(255, 255, 255, 0.56);
}

@media (hover: hover) {
    .dropdown-option:hover {
        background: rgba(255, 255, 255, 0.56);
    }
}

/* Timer row */
.timer-control {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-radius: 20px;
    padding: 8px 8px 8px 12px;
    background: var(--card-inset);
}
.timer-title {
    display: flex;
    align-items: center;
    gap: 8px;
}
.timer-title img {
    display: block;
    width: 16px;
    height: 16px;
}
.timer-title strong {
    font-size: 12px;
    font-weight: 500;
    line-height: 12px;
}
.timer-options {
    display: flex;
    gap: 8px;
}
.timer-option {
    border: 0;
    border-radius: 12px;
    padding: 8px;
    color: var(--card-ink);
    background: var(--card-glass-8);
    font-family: var(--card-font);
    font-size: 16px;
    font-weight: 400;
    line-height: 16px;
    white-space: nowrap;
}
@media (hover: hover) {
    .timer-option:hover {
        background: var(--card-glass-16);
    }
}
.timer-option.active {
    color: var(--card-ink);
    background: rgba(255, 255, 255, 0.64);
}

.unit:has(.figma-dropdown[open]) {
    z-index: 5;
}

.unit-feedback {
    margin: 8px 16px 12px;
    font-size: 12px;
    line-height: 1.4;
    color: inherit;
}
.unit-feedback:empty { display: none; }
.state-stale .connection-dot { background: #c88b28; }
.state-stale .connection-pill { color: #684400; }

/* ============================================================
   Global glass system — the room card is the reference surface.
   What makes the card read as modern, distilled into five rules that
   every other element in the interface now follows:

   1. FILL — translucent white over the blue canvas, never a milky
      tinted gradient. Four steps only: a recessed bed
      (--card-inset, a blue-shifted 8%), a quiet surface (white 8%),
      hover (white 16%), and the single selected/primary thing in a
      group (white 64%). The canvas stays readable through all of them.
   2. EDGE — one 1px hairline lit from the top: opaque white at 0%,
      almost gone at 50%, a faint lift at 100%, exactly the card's
      --card-glass-border-gradient. Painted by putting the fill in
      padding-box and the gradient in border-box, which also works on
      <input>, where the card's ::before mask ring cannot reach.
   3. DEPTH — the hairline plus a 2px backdrop blur. No drop shadows:
      the card has none, and shadows are what made the rest of the UI
      look like a different, heavier app. Only genuinely floating
      layers keep one (the dropdown menu, the sticky header).
   4. RADIUS — 28 shell / 20 panel / 16 pill / 12 chip / 999 circle,
      matching the card's 28 outer, 20 inner panel, 12 chip ladder.
   5. TYPE — Google Sans Flex, weights 400 and 500 only (no 650/750/800,
      no uppercase, no letter-spacing), sizes 32 / 24 / 20 / 16 / 12,
      line-height equal to the font size on numerals so figures sit
      tight. Hierarchy comes from ink opacity — 100% / 72% / 40% of
      #0A315C — never from extra hues.
   ============================================================ */
:root {
    --radius-shell: 28px;
    --radius-panel: 20px;
    --radius-pill: 16px;
    --radius-chip: 12px;
    --glass-quiet: rgba(255, 255, 255, 0.08);
    --glass-hover: rgba(255, 255, 255, 0.16);
    --glass-selected: rgba(255, 255, 255, 0.64);
}

body {
    color: var(--card-ink);
    font-family: var(--card-font);
}

/* Rule 1: --glass is the only thing an element sets to pick its fill step. */
.login-stage,
.workspace-section,
.status,
.empty-state,
.login-symbol,
.brand-mark,
.connection-pill,
.icon-button,
.summary-icon,
.workspace-section .chevron,
.connect-button,
.secondary,
pre {
    --glass: var(--glass-quiet);
    position: relative;
    border: 0;
    background: var(--glass);
    box-shadow: none;
    color: var(--card-ink);
    font-family: var(--card-font);
}

/* Rule 2: the card's hairline, painted the card's way. The gradient is
   masked down to the 1px ring (border-box minus padding-box) — laying it
   in border-box behind an 8% fill would let it wash across the whole
   surface instead of lighting only the edge. */
.login-stage::before,
.workspace-section::before,
.status::before,
.empty-state::before,
.login-symbol::before,
.brand-mark::before,
.connection-pill::before,
.icon-button::before,
.summary-icon::before,
.workspace-section .chevron::before,
.connect-button::before,
.secondary::before,
pre::before {
    position: absolute;
    z-index: 1;
    inset: 0;
    border: 1px solid transparent;
    border-radius: inherit;
    background: var(--card-glass-border-gradient) border-box;
    content: "";
    pointer-events: none;
    -webkit-mask:
        linear-gradient(#fff 0 0) border-box,
        linear-gradient(#fff 0 0) padding-box;
    -webkit-mask-composite: xor;
    mask:
        linear-gradient(#fff 0 0) border-box,
        linear-gradient(#fff 0 0) padding-box;
    mask-composite: exclude;
}

/* Form controls cannot carry that pseudo-element, so they approximate the
   same top-lit hairline with a real border: bright at the top edge,
   nearly gone at the sides, a faint lift along the bottom. Scoped to the
   form surfaces — the card's own temperature field stays borderless. */
.login-form :is(input, select),
.workspace-content :is(input, select) {
    --glass: var(--glass-quiet);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-top-color: rgba(255, 255, 255, 0.86);
    background: var(--glass);
    box-shadow: inset 0 -1px rgba(255, 255, 255, 0.16);
    color: var(--card-ink);
    font-family: var(--card-font);
}

/* The header floats over scrolling content, so it is the one surface
   allowed a heavier blur — the tint stays at the card's 8%. */
.app-header {
    border-bottom: 1px solid var(--card-glass-16);
    background: var(--glass-quiet);
    box-shadow: none;
    backdrop-filter: blur(16px) saturate(118%);
}

.header-inner {
    min-height: 72px;
}

.brand {
    gap: 12px;
    color: var(--card-ink);
    font-size: 16px;
    font-weight: 500;
    line-height: 24px;
}

.brand-mark {
    --glass: var(--glass-selected);
    width: 40px;
    height: 40px;
    border-radius: var(--radius-chip);
    font-size: 16px;
    font-weight: 500;
}

.header-actions {
    gap: 8px;
}

.connection-pill,
.icon-button {
    min-height: 40px;
}

.connection-pill {
    gap: 8px;
    border-radius: 999px;
    padding: 0 14px;
    font-size: 12px;
    font-weight: 500;
    line-height: 12px;
}

.icon-button {
    width: 40px;
    height: 40px;
    border-radius: 999px;
    font-size: 18px;
}

@media (hover: hover) {
    .connection-pill:hover,
    .icon-button:hover:not(:disabled),
    .secondary:hover:not(:disabled) {
        --glass: var(--glass-hover);
    }
}

.connection-dot {
    width: 8px;
    height: 8px;
    background: rgba(10, 49, 92, 0.4);
}

.is-connected .connection-dot {
    background: var(--card-ink);
    box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.24);
}

.shell {
    padding-top: 28px;
    padding-bottom: 64px;
}

/* Surfaces: the card's 8% glass and 2px blur, no drop shadow. */
.login-stage,
.workspace-section,
.status,
.empty-state {
    backdrop-filter: blur(2px);
}

.login-stage {
    gap: 28px;
    min-height: 0;
    border-radius: var(--radius-shell);
    padding: 24px 16px;
}

.login-intro {
    gap: 16px;
    align-items: center;
}

.login-symbol {
    width: 48px;
    height: 48px;
    flex: 0 0 48px;
    border-radius: 999px;
    font-size: 22px;
}

h1 {
    color: var(--card-ink);
    font-size: 32px;
    font-weight: 400;
    line-height: 36px;
    letter-spacing: 0;
}

h2,
.tool-pane h2 {
    color: var(--card-ink);
    font-family: var(--card-font);
    font-size: 20px;
    font-weight: 400;
    line-height: 20px;
}

/* Rule 5: labels are the card's 12/12 medium at 72% ink — no uppercase. */
.eyebrow,
label,
dt {
    margin-bottom: 8px;
    color: var(--card-ink-72);
    font-family: var(--card-font);
    font-size: 12px;
    font-weight: 500;
    line-height: 12px;
    letter-spacing: 0;
    text-transform: none;
}

label {
    margin-bottom: 0;
    gap: 8px;
}

.login-form {
    gap: 12px;
}

.login-form :is(input, select),
.workspace-content :is(input, select) {
    min-height: 48px;
    border-radius: var(--radius-pill);
    padding: 0 14px;
    font-size: 16px;
    font-weight: 400;
}

input::placeholder {
    color: rgba(10, 49, 92, 0.4);
}

input:focus,
select:focus,
button:focus-visible,
summary:focus-visible {
    outline: 2px solid rgba(255, 255, 255, 0.72);
    outline-offset: 2px;
}

/* Primary actions borrow the card's "selected" step (white 64%),
   the same fill the active power button and mode pill use. */
.connect-button {
    --glass: var(--glass-selected);
    min-height: 48px;
    border-radius: var(--radius-pill);
    padding: 0 18px;
    font-size: 16px;
    font-weight: 500;
    line-height: 16px;
}

@media (hover: hover) {
    .connect-button:hover {
        --glass: rgba(255, 255, 255, 0.82);
        color: var(--card-ink);
    }
}

.status {
    margin-top: 16px;
    border-radius: var(--radius-panel);
    padding: 14px 16px;
    font-size: 13px;
    font-weight: 400;
}

.status.error {
    --glass: rgba(255, 226, 226, 0.22);
    color: var(--card-ink);
}

.status.ok {
    --glass: rgba(220, 255, 246, 0.22);
    color: var(--card-ink);
}

.empty-state {
    min-height: 160px;
    border-radius: var(--radius-shell);
    color: var(--card-ink-72);
    font-family: var(--card-font);
    font-size: 13px;
}

.workspace {
    gap: 12px;
    margin-top: 36px;
}

.workspace-section {
    overflow: clip;
    border-radius: var(--radius-shell);
    transition: background-color 0.24s ease;
}

.workspace-section > summary {
    grid-template-columns: 40px minmax(0, 1fr) 32px;
    gap: 14px;
    min-height: 76px;
    padding: 0 16px;
    color: var(--card-ink);
    font-family: var(--card-font);
}

@media (hover: hover) {
    .workspace-section > summary:hover {
        background: var(--glass-quiet);
    }
}

.summary-icon {
    width: 40px;
    height: 40px;
    border-radius: 999px;
    font-size: 16px;
}

/* Matches .summary-item strong on the card: 16/24 medium. */
.workspace-section summary strong {
    color: var(--card-ink);
    font-size: 16px;
    font-weight: 500;
    line-height: 24px;
}

.workspace-section summary small {
    margin-top: 2px;
    color: var(--card-ink-72);
    font-size: 12px;
    font-weight: 500;
    line-height: 16px;
}

/* Same circle-with-chevron as the card's .summary-chevron. */
.workspace-section .chevron {
    display: grid;
    width: 32px;
    height: 32px;
    place-items: center;
    border-radius: 999px;
    font-size: 18px;
    line-height: 1;
    transition: transform 0.2s ease;
}

.workspace-section .chevron i {
    display: block;
    font-style: normal;
    line-height: 1;
}

.workspace-section[open] .chevron {
    --glass: var(--glass-selected);
}

.workspace-section[open] .chevron i {
    transform: rotate(180deg);
}
.workspace-section[open] .chevron > .iconly-icon {
    transform: rotate(180deg);
}

.workspace-content {
    border-top-color: var(--card-glass-16);
    padding: 20px 16px 22px;
}

.tool-heading {
    margin-bottom: 18px;
}

.tool-heading p,
.tool-pane > p {
    margin-top: 8px;
    color: var(--card-ink-72);
    font-family: var(--card-font);
    font-size: 13px;
    line-height: 18px;
}

.tool-pane + .tool-pane {
    border-left-color: var(--card-glass-16);
    padding-left: 20px;
}

.schedules {
    gap: 0;
    margin-top: 20px;
}

.schedules.empty {
    padding: 12px 0 0;
    color: var(--card-ink-72);
    font-family: var(--card-font);
    font-size: 13px;
}

.secondary {
    min-height: 36px;
    border-radius: var(--radius-chip);
    padding: 0 12px;
    font-size: 12px;
    font-weight: 500;
    line-height: 12px;
}

/* The card never uses accent bars — recessed beds carry state instead,
   the same --card-inset the mode strip and dropdown pills sit on. */
.diagnosis {
    border-left: 0;
    border-radius: var(--radius-panel);
    padding: 14px 16px;
    background: var(--card-inset);
}

.diagnosis h3 {
    margin-bottom: 6px;
    color: var(--card-ink);
    font-family: var(--card-font);
    font-size: 16px;
    font-weight: 500;
    line-height: 16px;
}

.diagnosis p {
    color: var(--card-ink-72);
    font-family: var(--card-font);
    font-size: 13px;
    line-height: 18px;
}

/* State reads as a dot on the heading, the same trick the header pill
   uses, instead of a coloured rail the card language has no room for. */
.diagnosis h3::before {
    display: inline-block;
    width: 8px;
    height: 8px;
    margin-right: 8px;
    border-radius: 999px;
    background: rgba(10, 49, 92, 0.4);
    content: "";
    vertical-align: middle;
}

.diagnosis.ok h3::before {
    background: var(--glass-selected);
}

.diagnosis.warning h3::before {
    background: rgba(255, 226, 178, 0.86);
}

.diagnosis.critical h3::before {
    background: rgba(255, 206, 206, 0.9);
}

.metrics dl {
    gap: 12px;
    margin-top: 14px;
}

.metrics dl div {
    border-left-color: var(--card-glass-16);
    padding-left: 12px;
}

dt {
    margin-bottom: 4px;
}

dd {
    color: var(--card-ink);
    font-family: var(--card-font);
    font-size: 32px;
    font-weight: 400;
    line-height: 32px;
}

.feature-grid {
    gap: 0 24px;
    margin-top: 12px;
}

.feature-row {
    border-top-color: var(--card-glass-16);
    padding: 12px 0;
    color: var(--card-ink-72);
    font-family: var(--card-font);
    font-size: 12px;
    font-weight: 400;
    line-height: 16px;
}

.feature-status {
    font-size: 12px;
    font-weight: 500;
    line-height: 12px;
}

.feature-status.supported {
    color: var(--card-ink);
}

.feature-status.unavailable {
    color: rgba(10, 49, 92, 0.4);
}

.developer-actions {
    gap: 8px;
    margin-bottom: 14px;
}

/* Raw JSON stays dark for legibility, but wears the same hairline. */
pre {
    --glass: rgba(6, 44, 90, 0.34);
    max-height: 460px;
    border-radius: var(--radius-panel);
    padding: 16px;
    color: #e9f5ff;
    font-family:
        ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: 11px;
    line-height: 1.55;
}

@media (max-width: 900px) {
    .tool-pane + .tool-pane {
        border-top-color: var(--card-glass-16);
        border-left: 0;
        padding-top: 20px;
        padding-left: 0;
    }
}

@media (max-width: 620px) {
    .header-inner {
        min-height: 64px;
    }

    .connection-pill {
        width: 40px;
        padding: 0;
        justify-content: center;
    }

    .connection-pill > span:last-child {
        display: none;
    }

    .shell {
        padding-top: 20px;
        padding-bottom: 44px;
    }

    .workspace {
        margin-top: 28px;
    }
}

@media (max-width: 400px) {
    .room-info {
        gap: 16px;
        padding-left: 8px;
    }
    .target-row {
        gap: 12px;
    }
    .timer-options {
        gap: 4px;
    }
}

/* Laria is intentionally a focused, phone-width control surface even on
   larger screens. The canvas remains visible around it instead of stretching
   utility controls beyond a room card. */
.header-inner,
.shell {
    width: min(380px, calc(100% - 24px));
}

.units {
    grid-template-columns: minmax(0, 1fr);
}

.login-stage,
.login-form,
.system-grid {
    grid-template-columns: 1fr;
}

.connect-button {
    grid-column: auto;
}

.tool-pane + .tool-pane {
    border-top: 1px solid rgba(255, 255, 255, 0.28);
    border-left: 0;
    padding-top: 22px;
    padding-left: 0;
}

/* ============================================================
   Timer composer — the timer is a sentence, the sentence is the
   control. Every underlined word opens one tray of chips beneath
   it; nothing is a dropdown, and nothing has to be reopened to be
   checked, because the sentence always reads back the whole timer.
   Same five glass rules as the room card: white 8/16/64 fills,
   the top-lit hairline, 20/16/12 radii, ink at 100/72/40%.
   ============================================================ */
.timer-composer {
    display: block;
    margin-top: 4px;
}

.timer-sentence {
    margin: 0;
    color: rgba(10, 49, 92, 0.4);
    font-family: var(--card-font);
    font-size: 24px;
    font-weight: 400;
    line-height: 38px;
}

.timer-sentence--extra {
    margin-top: 10px;
    font-size: 20px;
    line-height: 34px;
}

/* The one thing on this surface that is filled at the card's
   "selected" step: a slot is a value the user has already chosen. */
.timer-slot {
    border: 0;
    border-radius: var(--radius-chip);
    padding: 3px 10px;
    color: var(--card-ink);
    background: var(--glass-selected);
    font-family: var(--card-font);
    font-size: inherit;
    font-weight: 400;
    line-height: 30px;
    transition:
        transform 0.12s ease,
        background 0.2s ease;
}

.timer-sentence--extra .timer-slot {
    padding: 2px 9px;
    line-height: 26px;
}

@media (hover: hover) {
    .timer-slot:hover {
        background: rgba(255, 255, 255, 0.82);
    }
}

.timer-slot:active {
    transform: scale(0.96);
}

/* Open reads as recessed, the same inversion the mode strip uses:
   the value is no longer a statement, it is being edited. */
.timer-slot.is-open {
    background: var(--card-inset);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.5);
}

/* An unset optional setting is an invitation, not a value. */
.timer-slot.is-muted {
    color: rgba(10, 49, 92, 0.4);
    background: var(--glass-quiet);
}

@media (hover: hover) {
    .timer-slot.is-muted:hover {
        color: var(--card-ink-72);
        background: var(--glass-hover);
    }
}

.timer-extra-invite {
    display: block;
    margin-top: 12px;
    border: 0;
    padding: 0;
    color: rgba(10, 49, 92, 0.4);
    background: none;
    font-family: var(--card-font);
    font-size: 16px;
    line-height: 24px;
    text-align: left;
}

@media (hover: hover) {
    .timer-extra-invite:hover {
        color: var(--card-ink-72);
    }
}

.timer-tray {
    margin-top: 14px;
    border-radius: var(--radius-panel);
    padding: 14px;
    background: var(--card-inset);
    animation: timer-tray-drop 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
}

@keyframes timer-tray-drop {
    from {
        opacity: 0;
        transform: translateY(-6px);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

.timer-tray-label {
    margin: 0 0 10px;
    color: var(--card-ink-72);
    font-family: var(--card-font);
    font-size: 12px;
    font-weight: 500;
    line-height: 12px;
}

.timer-tray-label:not(:first-child) {
    margin-top: 14px;
}

.timer-chips {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.timer-chips--trailing {
    margin-top: 12px;
}

.timer-chip {
    border: 0;
    border-radius: var(--radius-chip);
    padding: 10px 12px;
    color: var(--card-ink);
    background: var(--glass-quiet);
    font-family: var(--card-font);
    font-size: 16px;
    font-weight: 400;
    line-height: 16px;
    transition: transform 0.12s ease;
}

@media (hover: hover) {
    .timer-chip:hover {
        background: var(--glass-hover);
    }
}

.timer-chip:active {
    transform: scale(0.94);
}

.timer-chip.is-selected {
    background: var(--glass-selected);
    font-weight: 500;
}

.timer-days {
    display: flex;
    gap: 6px;
    margin-top: 10px;
    justify-content: space-between;
}

.timer-day {
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: 999px;
    color: var(--card-ink-72);
    background: var(--glass-quiet);
    font-family: var(--card-font);
    font-size: 12px;
    font-weight: 500;
    transition: transform 0.12s ease;
}

@media (hover: hover) {
    .timer-day:hover {
        background: var(--glass-hover);
    }
}

.timer-day:active {
    transform: scale(0.92);
}

.timer-day.is-selected {
    color: var(--card-ink);
    background: var(--glass-selected);
}

/* The time field is the biggest thing in the panel because it is the
   one value people came here to set. */
.workspace-content .timer-time {
    display: block;
    width: 100%;
    min-height: 0;
    border: 0;
    border-radius: var(--radius-pill);
    padding: 6px 0;
    color: var(--card-ink);
    background: var(--glass-quiet);
    box-shadow: none;
    font-family: var(--card-font);
    font-size: 40px;
    font-weight: 400;
    line-height: 52px;
    text-align: center;
}

.timer-time::-webkit-calendar-picker-indicator {
    display: none;
}

.timer-stepper {
    display: flex;
    gap: 12px;
    align-items: center;
    justify-content: space-between;
}

.timer-stepper span {
    font-size: 32px;
    line-height: 32px;
}

.timer-stepper button {
    width: 48px;
    height: 48px;
    border: 0;
    border-radius: 999px;
    color: var(--card-ink);
    background: var(--glass-quiet);
    font-family: var(--card-font);
    font-size: 22px;
}
.timer-stepper button .iconly-icon {
    width: 20px;
    height: 20px;
}

@media (hover: hover) {
    .timer-stepper button:hover {
        background: var(--glass-hover);
    }
}

.timer-preview {
    margin: 16px 0 0;
    color: var(--card-ink-72);
    font-family: var(--card-font);
    font-size: 12px;
    line-height: 16px;
}

.timer-preview.is-invalid {
    color: rgba(10, 49, 92, 0.4);
}

.timer-note {
    color: var(--card-ink);
}

.timer-save {
    --glass: var(--glass-selected);
    position: relative;
    width: 100%;
    min-height: 56px;
    margin-top: 12px;
    border: 0;
    border-radius: var(--radius-pill);
    color: var(--card-ink);
    background: var(--glass);
    font-family: var(--card-font);
    font-size: 16px;
    font-weight: 500;
    line-height: 16px;
    transition: transform 0.12s ease;
}

@media (hover: hover) {
    .timer-save:hover:not(:disabled) {
        background: rgba(255, 255, 255, 0.82);
    }
}

.timer-save:active:not(:disabled) {
    transform: scale(0.985);
}

/* Coming up — every room's timers in the order they will happen. */
.schedules-title {
    margin: 0 0 4px;
    color: var(--card-ink-72);
    font-family: var(--card-font);
    font-size: 12px;
    font-weight: 500;
    line-height: 12px;
}

.timer-row {
    display: flex;
    gap: 12px;
    align-items: center;
    border-top: 1px solid var(--card-glass-16);
    padding: 12px 0;
    border-radius: var(--radius-chip);
    transition: background 0.6s ease;
}

.timer-row.is-new {
    background: var(--glass-hover);
}

.timer-row.is-paused {
    opacity: 0.55;
}

.timer-row-when {
    min-width: 62px;
}

.timer-row-when strong {
    display: block;
    color: var(--card-ink);
    font-size: 20px;
    font-weight: 400;
    line-height: 22px;
}

.timer-row-when small,
.timer-row-what small {
    display: block;
    color: var(--card-ink-72);
    font-size: 12px;
    font-weight: 400;
    line-height: 16px;
}

.timer-row-what {
    min-width: 0;
    flex: 1;
}

.timer-row-what strong {
    display: block;
    color: var(--card-ink);
    font-size: 16px;
    font-weight: 400;
    line-height: 20px;
}

.timer-row-delete {
    width: 32px;
    height: 32px;
    flex: 0 0 32px;
    border: 0;
    border-radius: 999px;
    color: var(--card-ink-72);
    background: var(--glass-quiet);
    font-family: var(--card-font);
    font-size: 18px;
    line-height: 1;
}
.timer-row-delete .iconly-icon {
    width: 15px;
    height: 15px;
}

@media (hover: hover) {
    .timer-row-delete:hover {
        color: var(--card-ink);
        background: var(--glass-hover);
    }
}

.timer-paused {
    display: flex;
    gap: 12px;
    align-items: center;
    justify-content: space-between;
    margin-top: 10px;
    border-radius: var(--radius-panel);
    padding: 12px 14px;
    background: var(--card-inset);
}

.timer-paused span {
    color: var(--card-ink-72);
    font-family: var(--card-font);
    font-size: 12px;
    line-height: 16px;
}

.timer-paused button {
    flex: 0 0 auto;
    min-height: 36px;
    border: 0;
    border-radius: var(--radius-chip);
    padding: 0 12px;
    color: var(--card-ink);
    background: var(--glass-selected);
    font-family: var(--card-font);
    font-size: 12px;
    font-weight: 500;
}

@media (max-width: 400px) {
    .timer-sentence {
        font-size: 22px;
        line-height: 36px;
    }
    .timer-days {
        gap: 4px;
    }
    .timer-day {
        width: 36px;
        height: 36px;
    }
}

/* ============================================================
   Card rings — every raised control wears the glass hairline, every
   recessed bed wears its inverse. Both are the same masked ring: the
   gradient goes in border-box, a padding-box hole is punched out of it,
   so only the 1px edge is painted and the fill underneath is untouched.
   ============================================================ */
.power-button,
.summary-chevron,
.dropdown-chevron,
.timer-option,
.unit.is-off .target-panel,
.unit-standby,
.mode-strip,
.figma-dropdown > summary,
.timer-control {
    position: relative;
}

.power-button::after,
.summary-chevron::after,
.dropdown-chevron::after,
.timer-option::after,
.unit.is-off .target-panel::after,
.unit-standby::after,
.mode-strip::after,
.figma-dropdown > summary::after,
.timer-control::after {
    position: absolute;
    z-index: 3;
    inset: 0;
    border: 1px solid transparent;
    border-radius: inherit;
    content: "";
    pointer-events: none;
    -webkit-mask:
        linear-gradient(#fff 0 0) border-box,
        linear-gradient(#fff 0 0) padding-box;
    -webkit-mask-composite: xor;
    mask:
        linear-gradient(#fff 0 0) border-box,
        linear-gradient(#fff 0 0) padding-box;
    mask-composite: exclude;
}

/* Raised: lit from above. */
.power-button::after,
.summary-chevron::after,
.dropdown-chevron::after,
.timer-option::after,
.unit.is-off .target-panel::after,
.unit-standby::after {
    background: var(--card-glass-border-gradient) border-box;
}

/* Recessed: shadowed into the surface. */
.mode-strip::after,
.figma-dropdown > summary::after,
.timer-control::after {
    background: var(--card-inset-border-gradient) border-box;
}

/* An off card has no inner-glass layer to outline it, so the target panel
   carries the header's own hairline and stays unfilled. */
.unit.is-off .target-panel {
    border-radius: 20px;
}

/* Standby strip: the one line an off room still has to say. */
.unit-standby {
    align-self: stretch;
    margin-top: 8px;
    border-radius: 20px;
    background: var(--card-glass-8);
    backdrop-filter: blur(2px);
}

.unit-standby > summary {
    display: block;
    padding: 12px;
    cursor: pointer;
    list-style: none;
}

.unit-standby > summary::-webkit-details-marker {
    display: none;
}

/* With nothing to expand the strip is a plain div, not a <details>. */
.unit-standby.is-static {
    padding: 12px;
}

.standby-summary {
    display: flex;
    min-width: 0;
    gap: 8px;
    align-items: center;
    margin-right: 8px;
}

.standby-summary img {
    display: block;
    width: 16px;
    height: 16px;
    flex: 0 0 16px;
}

/* One line, always. A full note ("Accensione impostata dalle 14.00 alle
   15.00") needs 320px at the summary row's 16px and the strip only offers
   280, so this line sits one step down the ladder; the ellipsis is the
   backstop for the rare note that carries a weekday as well. */
.standby-summary strong {
    overflow: hidden;
    font-size: 14px;
    font-weight: 400;
    line-height: 24px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.standby-content {
    display: grid;
    gap: 10px;
    padding: 0 16px 14px;
}

.standby-event {
    display: flex;
    gap: 12px;
    align-items: baseline;
}

.standby-event strong {
    min-width: 52px;
    font-size: 16px;
    font-weight: 400;
    line-height: 20px;
}

.standby-event small {
    color: var(--card-ink-72);
    font-size: 12px;
    font-weight: 400;
    line-height: 16px;
}

/* The active mode carries the same fill as the power button; the pill behind
   it supplies both that fill and the hairline, so the button stays clear. */
.mode-strip .mode-pill::after {
    position: absolute;
    z-index: 1;
    inset: 0;
    border: 1px solid transparent;
    border-radius: inherit;
    background: var(--card-glass-border-gradient) border-box;
    content: "";
    pointer-events: none;
    -webkit-mask:
        linear-gradient(#fff 0 0) border-box,
        linear-gradient(#fff 0 0) padding-box;
    -webkit-mask-composite: xor;
    mask:
        linear-gradient(#fff 0 0) border-box,
        linear-gradient(#fff 0 0) padding-box;
    mask-composite: exclude;
}

/* ---------------------------------------------------------------------------
   Touch-device compositing budget

   Every backdrop-filter promotes its element to its own render surface and
   makes the compositor re-sample everything behind it. On a phone GPU that is
   the difference between a 60fps drag and a visibly stepped one, and this
   layout stacks them: one per off room (.unit-standby), plus the large panels.
   (.inner-glass used to be on this list; it now has no blur on any pointer —
   see the note on the rule itself.)

   Cut on coarse pointers only, so the desktop look is untouched. Two survive
   deliberately:
     - .app-header, which genuinely floats over scrolling content, so removing
       its blur would let text collide with text.
     - .progress-thumb, whose blur IS the lens effect that makes the slider read
       as glass; halved instead, since it moves every frame during a drag.

   The alpha bumps compensate for the lost blur: without it the 8% fill reads as
   a faint grey wash rather than a surface.
   --------------------------------------------------------------------------- */
@media (pointer: coarse) {
    .login-stage,
    .workspace-section,
    .status,
    .empty-state,
    .unit-standby {
        backdrop-filter: none;
    }

    .login-stage,
    .workspace-section,
    .status,
    .empty-state,
    .unit-standby {
        background: rgba(255, 255, 255, 0.14);
    }

    .progress-thumb {
        backdrop-filter: blur(2px);
    }

    /* The two elements that actually animate under a finger. Promoting them up
       front avoids a layer being created mid-gesture, which is what produces
       the dropped first frame. */
    .mode-pill,
    .timer-tray {
        will-change: transform;
    }

    /* The header buttons draw at 40px to keep the header slim. Rather than
       inflate the circle and change the header's proportions, push the hit area
       past the visible edge: the 44px minimum is about where a fingertip lands,
       not about what gets painted. ::before is already the gradient border. */
    .icon-button {
        position: relative;
    }

    .icon-button::after {
        position: absolute;
        inset: -2px;
        border-radius: inherit;
        content: "";
    }
}

/* Freshness must override the decorative connected state. */
body.is-connected.state-stale .connection-dot { background: #aa6510; box-shadow: 0 0 0 4px rgba(255, 209, 116, .35); }
