/**
 * Design tokens - auto-generated, do not edit tokens.css directly
 */
@import url('tokens.css');

/**
 * Desktop-only light palette (mobile keeps the dark default).
 *
 * Homey's web shell inverts every app iframe via `filter: invert(1)
 * hue-rotate(180deg)` when the parent `<html>` has class `darkTheme` —
 * on desktop only. Mobile Homey never applies the filter. Inside the
 * cross-origin iframe (sandbox lacks `allow-same-origin`, parent doc /
 * frameElement / canvas readback / postMessage / Homey SDK all blank
 * for theme), PELS has no signal for Homey's theme. The previous
 * counter-filter approach proxied Homey-theme via `prefers-color-scheme`
 * (OS coupling) and broke whenever OS and Homey themes disagreed.
 *
 * Approach: ship PELS as a **light-canvas app on desktop** and let
 * Homey's invert produce the dark skin in Homey dark mode — the design
 * pattern every other Homey settings app uses. Mobile keeps the
 * designer-tuned dark palette unchanged (no filter to fight).
 *
 * Gate is `(hover: hover) and (pointer: fine)` — the same hardware probe
 * used in `tooltips.ts`. Deterministic on mobile (no filter) vs desktop
 * (always-invert-in-dark-mode); not coupled to OS theme.
 *
 * Override scope: neutrals + surfaces + borders + state-text "soft"
 * variants + state-info text. Saturated *default* role hues (accent
 * green, warn amber, alert red, info blue, price lavender, good teal)
 * keep their dark-palette values — Homey's invert produces hue-shifted
 * but recognisable forms for dark mode, and they read as designed on
 * the light canvas in light mode. The state-text soft variants flip
 * dark→light here so warning/positive/negative text remains readable
 * on the light canvas (Homey's invert then forwards them to readable
 * warm tones for desktop dark mode). On-accent text intentionally
 * does NOT flip — see the in-block comment for the AA contrast
 * justification.
 *
 * `<meta name="color-scheme" content="light dark">` in index.html
 * declares both palettes so the UA picks form-control / scrollbar
 * defaults from the user's preference; CSS still drives the actual
 * surface colors per the @media gate below.
 */
@media (hover: hover) and (pointer: fine) {
  :root {
    /* Light-canvas palette mapped 1:1 to Homey's canonical iframe-
       injected variable set (`_homey-variables.css` served at
       `/manager/webserver/assets/css/`, 2026-05-17). Homey defines:
         body bg:     --homey-color-ice-white = #f6f7fb
         component:   --homey-color-white     = #ffffff
         line:        --homey-color-mono-10   = #dddee2
         text:        --homey-color-mono-90   = #181818
         text-light:  --homey-color-mono-40   = #939496
       PELS sits inside Homey's iframe — adopt these values verbatim
       so the surface reads as a continuation of Homey's own chrome,
       not a third-party app with a similar-but-distinct neutral
       system. Status semantics are conveyed by *border* and *text*
       colour; Homey itself never tints status surfaces. */
    --color-base-black: #f6f7fb;       /* Homey ice-white / body */
    --color-base-black-rgb: 246, 247, 251;
    --color-base-gray-50: #181818;     /* Homey mono-90 / primary text */
    --color-base-gray-100: #7a7b7d;    /* Homey mono-50 / secondary */
    --color-base-gray-200: #939496;    /* Homey mono-40 / text-light — muted tier; on the light desktop canvas Homey mono-40 already reads fine, so the mobile-dark muted raise doesn't apply here */
    --color-base-gray-300: #939496;    /* Homey mono-40 / text-light — disabled tier (desktop leans on opacity for disabled states) */
    --color-base-gray-900: #ffffff;
    /* Keep the designer-tuned dark on-accent text from the mobile palette —
       `#ffffff` text on the `#16a34a` calm green-600 accent is only ~3.30 : 1
       (fails WCAG AA for normal text), while `#0c1610` on the same green is
       ~5.60 : 1 (AA pass for both normal and large text). Saturated accent
       default stays identical across mobile and desktop, so the dark
       on-accent reads correctly on both canvases. */
    --color-surface-1: #ffffff;        /* Homey mono-0 / component */
    --color-surface-2: #fafbfd;        /* Homey mono-01 */
    --color-surface-3: #f1f2f6;        /* Homey mono-02 */
    --color-surface-4: #dddee2;        /* Homey mono-10 / line */
    --color-surface-5: #c4c5c8;        /* Homey mono-20 */
    --color-surface-elevated: #ffffff;
    --color-border-default: #dddee2;   /* Homey line (mono-10) */
    --color-border-medium: #c4c5c8;    /* Homey line-hover/active (mono-20) */
    --color-border-strong: #939496;    /* Homey mono-40 */
    --color-overlay-backdrop: rgba(24, 24, 24, 0.5);  /* mono-90 at 0.5 */

    /* State-text "soft" tones — readable dark tones on light surface
       and they forward-invert to readable light tones under Homey's
       dark mode skin (#92400e → #f5a371, #065f46 → #71cab1,
       #991b1b → #ffaeae). Values computed by applying the standard
       SVG `invert(1) hue-rotate(180deg)` matrix to each input. */
    --color-base-warning-soft: #92400e;
    --color-base-good-soft: #065f46;
    --color-base-danger-soft: #991b1b;

    /* info-text falls back through `--color-role-info` → `--color-base-info-default`
       (`#60a5fa`, sky-400), which only hits ~3.5 : 1 on white. Borrow Homey's
       primary blue for state-info text so info-toned text + borders read at
       AA on the light canvas. */
    --color-state-info-text: #0075e0;  /* Homey blue-active */

    /* State containers — Homey-style: no saturated background tint.
       Containers stay white; the semantic role hue lives on the
       border and the inner title/text. Mobile keeps the original 14%
       color-mix tint (different aesthetic for a dark canvas). */
    --color-state-positive-bg: var(--color-surface-1);
    --color-state-warning-bg: var(--color-surface-1);
    --color-state-negative-bg: var(--color-surface-1);
    --color-state-info-bg: var(--color-surface-1);

    /* Shadow stack lifted directly from Homey's `--homey-box-shadow*`
       variables — same three-layer composition Homey uses on its own
       cards, panels and selected states. PELS card elevations now
       match Homey's elevation language exactly. */
    --shadow-sm: 0 5px 22px 0 rgba(0, 0, 0, 0.04), 0 2px 5px 0 rgba(0, 0, 0, 0.05), 0 2px 2px 0 rgba(0, 0, 0, 0.02);  /* Homey box-shadow-light */
    --shadow-md: 0 10px 44px 0 rgba(0, 0, 0, 0.07), 0 2px 12px 0 rgba(0, 0, 0, 0.1), 0 2px 4px 0 rgba(0, 0, 0, 0.04);  /* Homey box-shadow */
    --shadow-lg: 0 10px 44px 0 rgba(0, 0, 0, 0.07), 0 2px 12px 0 rgba(0, 0, 0, 0.1), 0 2px 4px 0 rgba(0, 0, 0, 0.04);
    --shadow-card: 0 5px 22px 0 rgba(0, 0, 0, 0.04), 0 2px 5px 0 rgba(0, 0, 0, 0.05), 0 2px 2px 0 rgba(0, 0, 0, 0.02);
    --shadow-panel: 0 5px 22px 0 rgba(0, 0, 0, 0.04), 0 2px 6px 0 rgba(0, 0, 0, 0.05), 0 2px 2px 0 rgba(0, 0, 0, 0.02);  /* Homey box-shadow-selected */
    --shadow-inset-soft: inset 0 1px 2px rgba(0, 0, 0, 0.04);
  }

}

/**
 * Homey injects _base.css which sets light theme defaults.
 * These overrides ensure our dark theme works properly.
 */

/* Override Homey's light background */
html {
  background: var(--bg) !important;
  color: var(--text) !important;
}

/* Override Homey's font */
body,
md-filled-text-field,
md-filled-select,
md-checkbox,
md-switch,
md-filled-button,
md-outlined-button,
md-text-button,
md-icon-button,
md-tabs,
md-primary-tab {
  font-family: var(--font-sans) !important;
}

/* Override Homey's label uppercase styling */
label {
  text-transform: none !important;
  color: inherit !important;
  font-size: inherit !important;
  font-weight: inherit !important;
}

/* Override Homey's fieldset styling */
fieldset {
  border-color: var(--panel-border) !important;
  background: transparent !important;
}

fieldset legend {
  color: var(--muted) !important;
}

/* Override Homey's hr styling */
hr {
  background: var(--panel-border) !important;
}

:root {
  /* Semantic aliases - map to design tokens for backward compatibility */
  --bg: var(--color-semantic-bg);
  --panel: var(--pels-surface-container-low);
  --panel-border: var(--pels-surface-outline);
  --accent: var(--color-semantic-accent);
  --accent-strong: var(--color-semantic-accent-strong);
  --text: var(--pels-text-primary);
  --muted: var(--pels-text-muted);
  --text-secondary: var(--pels-text-secondary);
  --danger: var(--color-semantic-danger);
  --warning: var(--color-semantic-warning);
  --info: var(--color-semantic-info);
  --radius: var(--radius-lg);
  --font-sans: var(--font-family-sans);
  --temp-input-width: 80px;
  --pels-md-primary: var(--accent);
  --pels-md-on-primary: var(--color-semantic-text-inverse);
  --pels-md-primary-container: var(--color-surface-4);
  --pels-md-on-primary-container: var(--text);
  --pels-md-secondary: var(--accent);
  --pels-md-on-secondary: var(--color-semantic-text-inverse);
  --pels-md-secondary-container: var(--color-surface-3);
  --pels-md-on-secondary-container: var(--text);
  --pels-md-surface: var(--color-surface-2);
  --pels-md-surface-variant: var(--color-surface-3);
  --pels-md-surface-container-highest: var(--color-surface-4);
  --pels-md-on-surface: var(--text);
  --pels-md-on-surface-variant: var(--text-secondary);
  --pels-md-outline: var(--panel-border);
  --pels-md-error: var(--danger);
  --pels-md-on-error: var(--color-semantic-text-inverse);
  
  /* Panel variants for different contexts */
  --panel-bg: var(--color-surface-1);
  --panel-hover: var(--color-surface-3);

  /* Material Web component theme bridge. */
  --md-ref-typeface-plain: var(--font-sans);
  --md-ref-typeface-brand: var(--font-sans);
  --md-sys-color-primary: var(--pels-md-primary);
  --md-sys-color-on-primary: var(--pels-md-on-primary);
  --md-sys-color-primary-container: var(--pels-md-primary-container);
  --md-sys-color-on-primary-container: var(--pels-md-on-primary-container);
  --md-sys-color-secondary: var(--pels-md-secondary);
  --md-sys-color-on-secondary: var(--pels-md-on-secondary);
  --md-sys-color-secondary-container: var(--pels-md-secondary-container);
  --md-sys-color-on-secondary-container: var(--pels-md-on-secondary-container);
  --md-sys-color-surface: var(--pels-md-surface);
  --md-sys-color-surface-variant: var(--pels-md-surface-variant);
  --md-sys-color-surface-container: var(--pels-md-surface);
  --md-sys-color-surface-container-low: var(--pels-surface-container-low);
  --md-sys-color-surface-container-high: var(--pels-surface-container-high);
  --md-sys-color-surface-container-highest: var(--pels-md-surface-container-highest);
  --md-sys-color-on-surface: var(--pels-md-on-surface);
  --md-sys-color-on-surface-variant: var(--pels-md-on-surface-variant);
  --md-sys-color-outline: var(--pels-md-outline);
  --md-sys-color-outline-variant: var(--pels-md-outline);
  --md-sys-color-error: var(--pels-md-error);
  --md-sys-color-on-error: var(--pels-md-on-error);
  --md-sys-shape-corner-extra-small: var(--radius-md);
  --md-sys-shape-corner-small: var(--radius-md);
  --md-sys-typescale-body-large-font: var(--font-sans);
  --md-sys-typescale-body-large-size: var(--font-size-md);
  --md-sys-typescale-body-large-line-height: var(--font-line-height-normal);
  --md-sys-typescale-label-large-font: var(--font-sans);
}

* {
  box-sizing: border-box;
}

[hidden] {
  display: none !important;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  line-height: var(--font-line-height-comfortable);
  /* Override Homey's body padding (we handle our own layout spacing). */
  padding: 0 !important;
  overflow-x: clip;
}

/* ─── Typography role utilities ─────────────────────────────────────────────
 * Role-based classes that map directly to the published role tokens. Use these
 * (or rebind primitive selectors to the same tokens) instead of reaching for
 * `--font-size-*` directly so the type scale stays a single source of truth.
 *
 *   .text-body              15 / 400 / normal  default supporting copy
 *   .text-caption           11 / 500 / tight   helper / meta text, eyebrows
 * --------------------------------------------------------------------------- */

.text-body {
  font-size: var(--pels-text-body-font-size);
  font-weight: var(--pels-text-body-font-weight);
  line-height: var(--pels-text-body-font-line-height);
}

.text-caption {
  font-size: var(--pels-text-caption-font-size);
  font-weight: var(--pels-text-caption-font-weight);
  line-height: var(--pels-text-caption-font-line-height);
}

md-filled-text-field,
md-filled-select {
  width: 100%;
  min-width: 0;
  min-height: var(--pels-touch-target-min);
  --md-filled-text-field-container-color: var(--color-surface-4);
  --md-filled-text-field-container-shape: var(--radius-md);
  --md-filled-text-field-input-text-color: var(--text);
  --md-filled-text-field-input-text-placeholder-color: var(--muted);
  --md-filled-text-field-focus-active-indicator-color: var(--accent);
  --md-filled-text-field-hover-active-indicator-color: var(--text-secondary);
  --md-filled-text-field-active-indicator-color: var(--panel-border);
  /* No hover wash: the default on-surface state layer painted the whole
     container grey — visually identical to the disabled skin — and hover is
     noise in a touch-first WebView anyway. */
  --md-filled-text-field-hover-state-layer-opacity: 0;
  --md-filled-select-text-field-container-color: var(--color-surface-4);
  --md-filled-select-text-field-container-shape: var(--radius-md);
  --md-filled-select-text-field-input-text-color: var(--text);
  --md-filled-select-text-field-focus-active-indicator-color: var(--accent);
  --md-filled-select-text-field-hover-active-indicator-color: var(--text-secondary);
  --md-filled-select-text-field-active-indicator-color: var(--panel-border);
  --md-filled-select-text-field-hover-state-layer-opacity: 0;
  --md-menu-container-color: var(--pels-surface-container-highest);
  --md-menu-container-shape: var(--radius-md);
  --md-menu-container-elevation: 3;
  --md-menu-item-container-color: var(--pels-surface-container-highest);
  --md-menu-item-label-text-color: var(--text);
  --md-menu-item-hover-state-layer-color: var(--accent);
  --md-menu-item-pressed-state-layer-color: var(--accent);
  --md-menu-item-selected-container-color: var(--color-surface-3);
  --md-menu-item-selected-label-text-color: var(--text);
  --md-menu-item-leading-space: var(--spacing-4);
  --md-menu-item-trailing-space: var(--spacing-4);
}

md-select-option {
  --md-menu-item-container-color: var(--pels-surface-container-highest);
  --md-menu-item-label-text-color: var(--text);
  --md-menu-item-hover-state-layer-color: var(--accent);
  --md-menu-item-pressed-state-layer-color: var(--accent);
  --md-menu-item-selected-container-color: var(--color-surface-3);
  --md-menu-item-selected-label-text-color: var(--text);
}

md-checkbox {
  flex: 0 0 auto;
  --md-checkbox-selected-container-color: var(--accent);
  --md-checkbox-selected-checkmark-color: var(--color-semantic-text-inverse);
  --md-checkbox-selected-focus-icon-color: var(--accent);
  --md-checkbox-selected-hover-icon-color: var(--accent);
  --md-checkbox-unselected-icon-color: var(--text-secondary);
}

md-filled-button,
md-outlined-button,
md-text-button {
  min-height: var(--pels-touch-target-min);
  font-weight: var(--font-weight-bold);
}

:where(md-dialog > [slot="actions"]) md-text-button {
  --md-sys-color-primary: var(--pels-text-primary);
}

/* Shared settings surfaces: restore the M3 lead/trail padding on the host
   boxes — Homey's host stylesheet opens with a universal `* { padding: 0 }`
   reset that beats the components' own :host padding, collapsing labels to
   the container edges (same idiom as .dry-run-banner__action /
   .budget-page-header__action). Placed up here with the base button rules so
   stylelint's no-descending-specificity ordering holds against the
   id-scoped button rules further down. */
.homes-settings md-filled-button,
.setup-journey md-filled-button {
  padding-inline: var(--spacing-6);
}

.homes-settings md-text-button,
.setup-journey md-text-button,
.setup-path md-text-button {
  padding-inline: var(--spacing-3);
}

.setup-journey__device md-text-button {
  padding-inline: var(--spacing-3);
  justify-self: start;
}

/* M3 destructive text button: foreground (label, ripple, focus, icon) uses
   the error role. Used for irreversible actions such as removing a
   stepped-load row. Scoping --md-sys-color-primary to this selector
   cascades through every md-text-button-* default that resolves to primary. */
md-text-button.md-text-button--destructive {
  --md-sys-color-primary: var(--md-sys-color-error);
}

.screen {
  width: min(100%, 480px);
  max-width: 480px;
  margin: 0 auto;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--spacing-4);
  padding: clamp(6px, 2vw, 10px);
}

.screen > * {
  min-width: 0;
}

.hero h1 {
  margin: 0 0 var(--spacing-1) 0;
  color: var(--pels-text-headline-color);
  font-size: var(--pels-text-headline-font-size);
  font-weight: var(--pels-text-headline-font-weight);
  line-height: var(--pels-text-headline-font-line-height);
  letter-spacing: -0.02em;
}

.hero .lede {
  margin: 0;
  color: var(--muted);
}

.tabs {
  display: flex;
  align-items: center;
  width: 100%;
  min-width: 0;
  margin-top: var(--spacing-3);
  overflow: hidden;
  background: var(--pels-shell-nav-container-background);
  border: 1px solid var(--pels-shell-nav-container-border);
  border-radius: var(--pels-shell-nav-container-radius);
  padding: var(--pels-shell-nav-container-padding);
  --md-primary-tab-container-color: transparent;
  --md-primary-tab-container-height: var(--pels-shell-nav-tab-height);
  --md-primary-tab-container-shape: var(--pels-shell-nav-tab-radius);
  --md-primary-tab-active-indicator-color: var(--pels-shell-nav-tab-selected-indicator-color);
  --md-primary-tab-active-indicator-height: var(--pels-shell-nav-tab-selected-indicator-height);
  --md-primary-tab-label-text-font: var(--font-sans);
  --md-primary-tab-label-text-size: var(--pels-shell-nav-tab-label-size);
  --md-primary-tab-label-text-line-height: var(--font-line-height-flat);
  --md-primary-tab-label-text-weight: var(--pels-shell-nav-tab-label-weight);
  --md-primary-tab-label-text-color: var(--pels-shell-nav-tab-label-color);
  --md-primary-tab-hover-label-text-color: var(--pels-shell-nav-tab-label-hover-color);
  --md-primary-tab-focus-label-text-color: var(--pels-shell-nav-tab-label-hover-color);
  --md-primary-tab-pressed-label-text-color: var(--pels-shell-nav-tab-label-hover-color);
  --md-primary-tab-active-label-text-color: var(--pels-shell-nav-tab-selected-text);
  --md-primary-tab-active-hover-label-text-color: var(--pels-shell-nav-tab-selected-text);
  --md-primary-tab-active-focus-label-text-color: var(--pels-shell-nav-tab-selected-text);
  --md-primary-tab-active-pressed-label-text-color: var(--pels-shell-nav-tab-selected-text);
  --md-primary-tab-hover-state-layer-color: var(--pels-shell-nav-tab-label-hover-color);
  --md-primary-tab-active-hover-state-layer-color: var(--pels-shell-nav-tab-selected-text);
}

#shell-nav {
  margin-top: 0;
  box-shadow: inset 0 0 0 1px var(--pels-shell-nav-container-border);
}

#shell-nav > md-primary-tab {
  flex: 1 1 0;
  min-width: 0;
}

#shell-nav > md-primary-tab:not(:last-child) {
  margin-inline-end: 2px;
}

.device-loading-notice {
  padding: var(--spacing-4);
  margin-bottom: var(--spacing-4);
  background: var(--pels-surface-container);
  border-radius: var(--radius-md);
  color: var(--muted);
  font-size: var(--font-size-sm);
  text-align: center;
  animation: pulse var(--pels-motion-pulse-duration) ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

.tab {
  min-width: 0;
  padding: 0 var(--pels-shell-nav-tab-padding-inline);
  padding-inline: var(--pels-shell-nav-tab-padding-inline);
  overflow: hidden;
  font-weight: var(--pels-shell-nav-tab-label-weight);
  font-size: var(--pels-shell-nav-tab-label-size);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.tab:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* Hover treatments are gated to genuinely hover-capable pointers. In the
   touch WebView a tap leaves the element in a sticky `:hover` state, which
   painted a phantom filled pill behind the last-tapped tab — making the strip
   read as pill + underline, two competing active-tab treatments. The M3
   underline is the single selection indicator. */
@media (hover: hover) {
  .tab:hover:not(.active) {
    color: var(--text);
    background: color-mix(in srgb, var(--accent) 8%, transparent);
  }
}

@media (hover: none) {
  /* Target the shell-nav container by id (it carries both `.tabs` and
     `#shell-nav`): the state-layer-opacity custom properties are consumed by
     each `md-primary-tab` child and inherit down from here, and the id keeps
     the override scoped to the shell nav specifically. */
  #shell-nav {
    --md-primary-tab-hover-state-layer-opacity: 0;
    --md-primary-tab-active-hover-state-layer-opacity: 0;
  }
}

#shell-nav .tab {
  --md-primary-tab-container-height: var(--pels-shell-nav-tab-height);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--pels-shell-nav-tab-height);
  min-height: var(--pels-shell-nav-tab-height);
  line-height: var(--font-line-height-flat);
  min-width: 0;
}

/* Selected nav tab: M3 underline indicator (accent bar via the .tabs
   --md-primary-tab-active-indicator-* tokens) plus a primary-text label.
   The container fill stays transparent (inherited from the base .tabs rule),
   so the strip reads as a calm modern M3 tab strip rather than a loud solid
   block — only the label colour lifts to the primary on-surface role. */
md-primary-tab.tab.active,
md-primary-tab.tab[active] {
  color: var(--pels-shell-nav-tab-selected-text);
  font-weight: var(--font-weight-semibold);
}

/* Simulation banner: one slim warning-tinted row — leading warning icon + a
   concise line + an inline "Turn off simulation" text action. The scoped
   multi-meter copy stacks at the narrowest viewport in the media rule below.
   The amber tint
   comes from `.banner--warning` on every screen (no per-viewport neutral
   override), so it reads as STATE, not just another grey card. `.banner`
   supplies the flex row + radius; this override tightens it to a single
   ~48 px row and pins the trailing text action. */
#dry-run-banner {
  flex-wrap: nowrap;
  gap: var(--spacing-2);
  /* The 48 px M3 text-button (a11y touch target) defines the row height; zero
     vertical padding keeps the whole banner to that single ~48 px row instead
     of stacking the old three-row card. `min-height` matches the button so the
     value and the comment agree. */
  min-height: 48px;
  padding-block: 0;
  padding-inline: var(--spacing-3);
}

/* NB: the `#dry-run-banner .banner__text` shrink override lives down in the
   `.banner` section (after the base + `.banner--stacked` `.banner__text` rules)
   so stylelint's no-descending-specificity ordering holds. */

/* Shared text action inside a warning banner (simulation + stale-data). */
.banner__action {
  /* Restore the M3 text-button lead/trail padding on the host box. Homey injects
     a universal `* { padding: 0 }` reset that beats Material Web's :host padding
     on the real device (invisible in CI), collapsing the label to the state-layer
     edges. Host `padding-inline` is a layout property the shadow DOM does not
     swallow, so it reaches the outer box (same idiom as the modes-panel buttons). */
  padding-inline: var(--spacing-3);
  /* M3 text-button action inside an amber banner: override the primary role on
     the host so the label + ripple/state-layer read amber. This cascades
     through every `--md-text-button-*` default that resolves to primary — the
     same one-property idiom as `.md-text-button--destructive` above. */
  --md-sys-color-primary: var(--color-state-warning-text);
}

/* The slim single-row simulation banner pins its action to the row end; the
   stale-data banner keeps the base `.banner` wrap so its longer message can
   break and the action drops to its own line at narrow widths. */
.dry-run-banner__action {
  flex: 0 0 auto;
}

.device-control-legend {
  border: 1px solid var(--pels-surface-outline);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--pels-surface-container) 70%, transparent);
  padding: 0 var(--spacing-3);
  margin-bottom: var(--spacing-1);
}

.device-control-legend__summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-2) 0;
  color: var(--pels-text-secondary);
  font-size: var(--font-size-sm);
}

.device-control-legend__summary::-webkit-details-marker {
  display: none;
}

.device-control-legend__pill {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
  color: var(--text);
  font-weight: var(--font-weight-semibold);
}

.device-control-legend__action {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
  color: var(--pels-text-secondary);
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: var(--font-weight-semibold);
}

.device-control-legend__chevron {
  width: 0.875rem;
  height: 0.875rem;
  transition: transform var(--transition-fast);
  flex-shrink: 0;
}

.device-control-legend[open] .device-control-legend__chevron {
  transform: rotate(180deg);
}

.device-control-legend__list {
  list-style: none;
  margin: 0;
  padding: 0 0 var(--spacing-3);
  display: grid;
  gap: var(--spacing-2);
  border-top: 1px solid var(--pels-surface-outline);
  margin-top: var(--spacing-1);
  padding-top: var(--spacing-3);
}

.device-control-legend__item {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  gap: var(--spacing-3);
  align-items: start;
  color: var(--pels-text-secondary);
}

.device-control-legend__item strong {
  color: var(--text);
  font-size: var(--font-size-sm);
}

.device-control-legend__item p {
  margin: var(--spacing-1) 0 0;
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-cozy);
}

.device-control-legend__icon {
  display: inline-flex;
  width: 22px;
  height: 22px;
  align-items: center;
  justify-content: center;
  color: var(--accent);
}

.device-control-legend__icon svg {
  width: 20px;
  height: 20px;
}

/* At 320px the legend's three pills plus "Explain" overflow by ~2px because of
   the action label's uppercase letter-spacing. Drop the text and keep the chevron
   alone — the open/close affordance is still clear. */
@media (max-width: 360px) {
  .device-control-legend__summary {
    gap: var(--spacing-2);
  }

  .device-control-legend__action-label {
    display: none;
  }
}

.pels-device-card {
  --pels-device-switch-col-width: 44px;
  --pels-device-detail-col-width: 40px;
}

.pels-device-card[data-managed-state="all"]::before {
  background: var(--pels-status-good);
}

.pels-device-card[data-managed-state="partial"]::before {
  background: var(--pels-status-warning);
}

.pels-device-card[data-managed-state="none"]::before {
  background: repeating-linear-gradient(
    180deg,
    color-mix(in srgb, var(--text-secondary) 55%, transparent) 0 6px,
    transparent 6px 10px
  );
}

.pels-device-card__header {
  align-items: baseline;
}

.pels-device-card__count-chip {
  font-variant-numeric: tabular-nums;
}

.pels-device-card__grid {
  display: grid;
  gap: var(--spacing-1);
}

.pels-device-card__row {
  display: grid;
  grid-template-columns:
    minmax(0, 1fr)
    repeat(3, var(--pels-device-switch-col-width))
    var(--pels-device-detail-col-width);
  gap: var(--spacing-2);
  align-items: center;
  padding: var(--spacing-2) var(--spacing-1);
  border-radius: var(--radius-md);
  transition: background var(--transition-fast);
}

.pels-device-card__row:hover {
  background: color-mix(in srgb, var(--pels-surface-container-high) 60%, transparent);
}

.pels-device-card__row:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: -2px;
}

.pels-device-card__row--unmanageable {
  opacity: 0.65;
}

.pels-device-card__name {
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-1);
}

.pels-device-card__reasons {
  flex: 1 0 100%;
  display: grid;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--pels-text-muted);
  font-size: var(--font-size-xs);
  line-height: var(--font-line-height-normal);
}

.pels-device-card__cell {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 28px;
  position: relative;
}

.pels-device-card__cell-placeholder {
  color: var(--pels-text-secondary);
  font-size: var(--font-size-sm);
  user-select: none;
}

.pels-icon-toggle {
  width: var(--pels-touch-target-min);
  height: var(--pels-touch-target-min);
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--text-secondary) 40%, transparent);
  background: transparent;
  color: color-mix(in srgb, var(--text-secondary) 75%, transparent);
  border-radius: var(--radius-full);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}

.pels-icon-toggle > svg {
  width: 22px;
  height: 22px;
  stroke: currentColor;
  fill: none;
}

:where(.pels-device-card__cell--disabled) .pels-icon-toggle {
  display: none;
}

.pels-icon-toggle:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.pels-icon-toggle.is-disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

.pels-icon-toggle.is-on {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  border-color: var(--accent);
}

.pels-icon-toggle:not(.is-disabled):hover {
  border-color: var(--text);
  color: var(--text);
}

.pels-icon-toggle.is-on:not(.is-disabled):hover {
  background: color-mix(in srgb, var(--accent) 32%, transparent);
}

.pels-device-card__detail-button {
  width: var(--pels-touch-target-min);
  height: var(--pels-touch-target-min);
  color: var(--pels-text-muted);
  border-radius: var(--radius-full);
}

.pels-device-card__detail-button:hover {
  color: var(--text);
  background: color-mix(in srgb, var(--pels-surface-container-high) 70%, transparent);
}

.pels-device-card__detail-button:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.pels-device-card__detail-icon {
  display: block;
  width: 1.5rem;
  height: 1.5rem;
}

.budget-redesign-surface {
  display: grid;
  gap: var(--spacing-3);
}

.card {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: clamp(14px, 3vw, 16px);
  backdrop-filter: blur(8px);
  box-shadow: var(--shadow-card);
}

.settings-home-panel {
  display: grid;
  gap: var(--spacing-3);
}

.settings-current-mode {
  display: grid;
  gap: var(--spacing-2);
  background: var(--pels-surface-container-low);
  box-shadow: var(--shadow-sm);
}

.settings-current-mode__rows {
  display: grid;
  gap: var(--spacing-4);
}

.settings-current-mode__row {
  display: grid;
  gap: var(--spacing-2);
}

.settings-current-mode__row md-filled-select {
  width: 100%;
}

.settings-current-mode__home-name {
  min-width: 0;
  overflow-wrap: anywhere;
}

/*
 * `.settings-current-mode__heading` promotes the field label to an <h3>
 * so the Settings page keeps a screen-reader nav rung between the
 * visually-hidden `h2 "Settings"` landmark heading and the nav cards below.
 * Neutralise the browser defaults so it reads visually identical to the
 * surrounding `.field__label` spans.
 */
.settings-current-mode__heading {
  margin: 0;
  font-size: inherit;
  line-height: inherit;
  letter-spacing: 0;
}

.settings-current-mode__hint {
  margin: 0;
}

/* Homey-native grouped Settings nav: one outer surface, rows separated by
   internal 1px dividers (no per-row border or shadow), each row paired with a
   flat outline icon. */
.settings-nav-list {
  display: grid;
  gap: 0;
  padding: 0;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container-low);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

.settings-nav-card,
.setup-path__step {
  position: relative;
  display: flex;
  width: 100%;
  min-height: var(--pels-touch-target-min);
  padding: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
  --md-list-item-label-text-color: var(--text);
  --md-list-item-label-text-font: var(--font-sans);
  --md-list-item-label-text-weight: var(--font-weight-bold);
  --md-list-item-label-text-size: var(--font-size-md);
  --md-list-item-supporting-text-color: var(--muted);
  --md-list-item-supporting-text-font: var(--font-sans);
  --md-list-item-supporting-text-size: var(--font-size-sm);
  --md-list-item-two-line-container-height: var(--spacing-8);
  --md-list-item-leading-space: var(--spacing-3);
  --md-list-item-trailing-space: var(--spacing-3);
  --md-list-item-top-space: var(--spacing-3);
  --md-list-item-bottom-space: var(--spacing-3);
}

/* Internal dividers between rows; the first row has none. */
.settings-nav-card + .settings-nav-card,
.setup-path__step + .setup-path__step {
  border-top: 1px solid var(--panel-border);
}

.settings-nav-card:hover,
.settings-nav-card:focus-visible,
.setup-path__step:hover,
.setup-path__step:focus-visible {
  background: var(--pels-surface-container) !important;
}

.settings-nav-card:focus-visible,
.setup-path__step:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: calc(-1 * var(--spacing-1));
}

.settings-nav-card--quiet,
.setup-path__step[data-setup-status="later"] {
  opacity: 0.86;
}

.settings-nav-card__icon {
  flex: none;
  width: 1.375rem;
  height: 1.375rem;
  color: var(--muted);
}

/* `.settings-nav-card__title` now composes `.pels-text-card-title` in markup;
   no per-block type needed. */

.settings-nav-card__description {
  color: var(--muted);
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-cozy);
}

.settings-nav-card__chevron {
  justify-self: end;
  color: var(--muted);
  width: 1.25rem;
  height: 1.25rem;
}

/* Exception-only state chip on a hub nav card (Simulation `On`, Daily budget
   `Off`, Electricity prices `Awaiting prices`). Sits in the `end` slot beside
   the chevron; normal state keeps it `hidden`. */
.settings-nav-card__chip {
  align-self: center;
  margin-inline-end: var(--spacing-2);
  white-space: nowrap;
}

/* Device-detail hero: layout, surface, and type roles come from the shared
   `.pels-hero` primitive (state word = `.plan-hero__headline` display role,
   fact/reason = `.plan-hero__subline`). This block only binds the card
   grammar's state tones to the state word (`data-state-kind`, matching the
   Overview card's role-toned text) and the power fact's supporting role —
   the neutral-surface rule (`notes/overview-hero-spec.md`) holds because the
   toned WORD carries the meaning, never the surface. */
.device-detail-live-status[data-state-kind="held"] .device-detail-live-status__state {
  color: var(--color-state-warning-text);
}

.device-detail-live-status[data-state-kind="resuming"] .device-detail-live-status__state {
  color: var(--color-state-positive-text);
}

.device-detail-live-status[data-state-kind="unavailable"] .device-detail-live-status__state {
  color: var(--color-state-negative-text);
}

.device-detail-live-status__power {
  font-size: var(--pels-text-supporting-font-size);
  line-height: var(--pels-text-supporting-font-line-height);
  color: var(--pels-text-supporting-color);
  white-space: nowrap;
}

.device-detail-live-status__reason {
  overflow-wrap: anywhere;
}

.settings-detail-panel {
  display: grid;
  gap: var(--spacing-3);
}

/* ─── Navigation app bar (`.pels-appbar`) ────────────────────────────────────
   The single back+title chrome row every sub-page uses: a 48 px icon-only
   back button and the page title on one un-carded line. Replaces the three
   historical back treatments (accent "← Settings" text button, carded
   "← Smart tasks" button, dark "← Devices" pill) AND the boxed hero-title
   card that used to follow them — the bar itself is transparent page chrome,
   so content starts one row below the tab bar / banner. Neutral (non-accent)
   colours only: navigation chrome must not compete with content accents. */
.pels-appbar {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  min-width: 0;
  min-height: var(--pels-touch-target-min);
}

.pels-appbar__back {
  flex: 0 0 auto;
  width: var(--pels-touch-target-min);
  height: var(--pels-touch-target-min);
  /* Route the glyph colour through the MD icon-button token API — the svg
     lives in the component's slot and does not inherit host `color`. */
  --md-icon-button-icon-color: var(--pels-text-primary);
  --md-icon-button-hover-icon-color: var(--pels-text-primary);
  --md-icon-button-focus-icon-color: var(--pels-text-primary);
  --md-icon-button-pressed-icon-color: var(--pels-text-primary);
  --md-icon-button-state-layer-color: var(--pels-text-primary);
  --md-icon-button-hover-state-layer-color: var(--pels-text-primary);
  --md-icon-button-pressed-state-layer-color: var(--pels-text-primary);
}

/* Two-step discard confirm reflected on the Budget-adjust app-bar back arrow:
   the armed state tints the glyph to the warning tone (matching the Budget
   header toggle's `.confirming` treatment) so a second tap reads as the
   destructive "discard my draft" choice rather than a neutral navigation. */
.pels-appbar__back.confirming {
  --md-icon-button-icon-color: var(--color-state-warning-text);
  --md-icon-button-hover-icon-color: var(--color-state-warning-text);
  --md-icon-button-focus-icon-color: var(--color-state-warning-text);
  --md-icon-button-pressed-icon-color: var(--color-state-warning-text);
  --md-icon-button-state-layer-color: var(--color-state-warning-text);
}

/* Doubled selector (parent class + element class) so the title's card-title
   type beats the generic `.card h2` / `.pels-hero h2` section-headline
   cascade on panels that still carry the `.card` class. */
.pels-appbar .pels-appbar__title {
  margin: 0;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--pels-text-card-title-color);
  font-size: var(--pels-text-card-title-font-size);
  font-weight: var(--pels-text-card-title-font-weight);
  line-height: var(--pels-text-card-title-font-line-height);
}

/* Short lede paragraph directly under an app bar (Electricity prices). Plain
   page-level text — not a card — so the chrome row stays the only container. */
.pels-appbar-lede {
  margin: 0;
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-cozy);
}

.settings-form-card {
  padding: var(--spacing-3);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container);
}

.card h2,
.pels-hero h2 {
  margin: var(--spacing-1) 0 0;
  color: var(--pels-text-section-headline-color);
  font-size: var(--pels-text-section-headline-font-size);
  font-weight: var(--pels-text-section-headline-font-weight);
  line-height: var(--pels-text-section-headline-font-line-height);
}


.eyebrow {
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  line-height: var(--font-line-height-tight);
  color: var(--muted);
}

.form-grid {
  display: grid;
  gap: var(--spacing-4);
  margin-bottom: var(--spacing-6);
}

.field {
  display: grid;
  gap: 6px;
}

/* `.field__label` composes `.pels-text-settings-label` in markup. */

.field__hint {
  margin: 0;
  color: var(--muted);
}

.field__hint[hidden] {
  display: none;
}

.field__hint a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.field__hint--alert {
  color: var(--color-state-warning-text);
  font-weight: var(--font-weight-medium);
}

.charging-current-setup {
  display: grid;
  padding-inline: var(--spacing-1);
  gap: var(--spacing-3);
  min-width: 0;
}

.continuous-battery-plan {
  display: grid;
  gap: var(--spacing-3);
  min-width: 0;
}

.continuous-battery-plan > *,
.battery-plan-fields > * {
  min-width: 0;
}

.battery-plan-fields {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
}

.battery-plan-fields md-filled-text-field {
  width: 100%;
}

.battery-plan-advanced summary {
  padding-block: var(--spacing-3);
}

.battery-plan-chart {
  margin: var(--spacing-3) 0;
}

.battery-plan-chart svg {
  display: block;
  width: 100%;
  height: 120px;
  overflow: visible;
}

.battery-plan-chart__plot {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--spacing-2);
}

.battery-plan-chart__axis {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  text-align: end;
}

.battery-plan-chart__times {
  grid-column: 2;
  display: flex;
  justify-content: space-between;
  gap: var(--spacing-2);
  flex-wrap: wrap;
}

.battery-plan-chart :is(path, polyline) {
  vector-effect: non-scaling-stroke;
}

.battery-plan-chart__grid {
  stroke: var(--panel-border);
  fill: none;
}

.battery-plan-chart__line {
  stroke: var(--accent);
  stroke-width: 2;
  fill: none;
  stroke-dasharray: 4 3;
}

.battery-plan-chart__reserve {
  stroke: var(--text-secondary);
  stroke-dasharray: 2 3;
  fill: none;
}

.battery-plan-windows {
  list-style: none;
  padding: 0;
  margin: 0;
}

.battery-plan-windows li {
  display: grid;
  gap: var(--spacing-1);
  border-top: 1px solid var(--panel-border);
  padding-block: var(--spacing-3);
}

.charging-current-setup md-filled-text-field {
  width: 100%;
  min-width: 0;
}

.charging-current-setup .field__hint {
  color: var(--text-secondary);
}

/* Homey's universal padding reset also applies to Material button hosts. */
.charging-current-setup :is(md-filled-button, md-outlined-button) {
  padding-inline: var(--spacing-6);
}

#device-detail-charging-current,
.charging-current-setup > * {
  min-width: 0;
}

/* Homey's reset reaches the dialog host and slotted content. Restore Material's
   centering and spacing here; shadow-root defaults lose to host-page rules. */
md-dialog {
  margin: auto;
}

md-dialog > [slot="headline"] {
  padding: var(--spacing-6) var(--spacing-6) 0;
}

md-dialog > [slot="content"] {
  padding: var(--spacing-6);
}

md-dialog:has(> [slot="actions"]) > [slot="content"] {
  padding-block-end: var(--spacing-2);
}

md-dialog > [slot="actions"] {
  display: flex;
  gap: var(--spacing-2);
  justify-content: flex-end;
  padding: var(--spacing-4) var(--spacing-6) var(--spacing-6);
}

/* Promoted computed-result row: the payoff number of a settings card, in
   neutral bold so it reads as the answer the inputs produce — not a muted
   caption. Neutral (not accent) matches the smart-task hero's stat-pair values
   (`.deadline-hero-stats__value`): one "loud passive readout" language
   app-wide, with accent reserved for actions / selection / status. */
.settings-result {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--spacing-1) var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-3);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container-high);
}

.settings-result__label {
  color: var(--pels-text-secondary);
  font-size: var(--pels-text-supporting-font-size);
  line-height: var(--pels-text-supporting-font-line-height);
}

.settings-result__value {
  color: var(--text);
  font-size: var(--font-size-lg);
  font-weight: var(--font-weight-semibold);
  line-height: var(--font-line-height-tight);
}

/* Derivation note after the value ("(hard cap minus safety margin)") — the
   quiet "why" behind the computed ceiling, so it stays legible but recessive. */
.settings-result__note {
  color: var(--pels-text-supporting-color);
  font-size: var(--pels-text-supporting-font-size);
  line-height: var(--pels-text-supporting-font-line-height);
}

.form__actions {
  display: flex;
  gap: var(--spacing-3);
  flex-wrap: wrap;
}

/* Busy state for MD Web buttons (used by advanced.ts's setApiDeviceButtonsBusy
 * / setClearButtonBusy helpers on `md-outlined-button` hosts). The host's
 * `cursor` + `opacity` properties DO reach the user even though MD Web
 * renders in shadow DOM: `cursor` is a host-level pointer affordance and
 * `opacity` composites the entire host tree. The legacy `.btn` / `.btn.ghost`
 * / `.btn.secondary` / `.btn.primary` ghost-decoration companions that
 * wrapped this rule were dropped (batch 11) -- their `color` / `background`
 * / `border` / `transform` declarations painted nothing the shadow DOM let
 * through. Selector is scoped to MD Web button tags so a future `is-busy`
 * consumer elsewhere doesn't inherit the busy affordance by accident. */
md-outlined-button.is-busy,
md-text-button.is-busy,
md-filled-button.is-busy {
  cursor: progress;
  opacity: 0.8;
}

.pill {
  padding: var(--spacing-2) var(--spacing-3);
  border-radius: var(--radius-full);
  background: var(--pels-surface-container);
  color: var(--muted);
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-sm);
  border: 1px solid var(--panel-border);
  white-space: nowrap;
}

.pill.ok {
  color: var(--color-semantic-bg);
  background: var(--color-role-good);
  border-color: transparent;
}

.pill.warn {
  background: var(--color-state-negative-bg);
  color: var(--danger);
  border-color: var(--color-state-negative-border);
}

.muted {
  color: var(--muted);
  margin: var(--spacing-2) 0 0;
}

/* Screen-reader-only utility — visually hidden but announced by assistive tech. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.list__header,
.device-row {
  display: block;
  gap: var(--spacing-2);
  align-items: center;
  padding: var(--spacing-2) 0;
  border-bottom: 1px solid var(--panel-border);
}

.tight-list .device-row {
  padding: var(--spacing-1) 0;
}

.control-row {
  display: grid;
  grid-template-columns: 1fr 80px 40px 40px;
  align-items: center;
  gap: var(--spacing-2);
}

.mode-row {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr) 80px 44px;
  align-items: center;
  gap: var(--spacing-1);
}

.mode-target-placeholder {
  color: var(--muted);
  font-size: var(--font-size-sm);
  text-align: center;
}

.mode-row__inputs {
  display: inline-flex;
  gap: var(--spacing-1);
  align-items: center;
  flex-wrap: nowrap;
  white-space: nowrap;
  flex: 0 0 auto;
  justify-content: flex-end;
}

.drag-handle {
  cursor: grab;
  padding: var(--spacing-1) var(--spacing-2);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-sm);
  background: var(--pels-surface-container-high);
  user-select: none;
  touch-action: none;
  -webkit-user-select: none;
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-flat);
}

.drag-handle:hover {
  background: var(--pels-surface-container-highest);
  color: var(--text);
}

.drag-handle:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.draggable {
  /* Allow vertical scrolling on the row, only block on drag-handle */
  touch-action: pan-y;
}

/* SortableJS styles */
.sortable-ghost {
  opacity: 0.4;
  background: var(--color-state-positive-bg);
}

.sortable-chosen {
  background: var(--color-state-positive-bg);
}

.sortable-drag {
  opacity: 0.9;
  background: var(--panel);
  box-shadow: var(--shadow-md);
}

.sortable-fallback {
  opacity: 1 !important;
  background: var(--panel) !important;
  box-shadow: var(--shadow-lg) !important;
  border-radius: var(--radius-md);
}

.device-list {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.usage-list {
  gap: var(--spacing-1);
  margin: var(--spacing-2) 0;
  min-width: 0;
}

/* Power-week heatmap container. Sizing previously mutated inline from
 * `powerWeekChartEcharts.ts` (height/min-height/-webkit-tap-highlight-color)
 * and cleared in `dispose`. Hoisting to CSS keeps the chart's physical
 * footprint in the stylesheet and lets the component own DOM lifecycle
 * only. The height resolves through `--pels-chart-week-height` so the
 * container box and the ECharts viewport (`resolveChartSize` in
 * `powerWeekChartEcharts.ts`) share a single chart-scoped token; the
 * resolved value is still 240 px (physical, not theme-relative — same
 * convention as `.deadline-horizon-chart`). */
.power-week-chart {
  height: var(--pels-chart-week-height);
  min-height: var(--pels-chart-week-height);
  -webkit-tap-highlight-color: transparent;
}

/* `.plan-row__chip` retired 2026-05-24 (chip primitive consolidation) — the
   plan-row variant was replaced by the device-card layout in the redesign
   and had no consumers left. */

.plan-hero,
.pels-hero,
.plan-hour-strip,
.plan-cards {
  min-width: 0;
}

/* `.pels-hero` is the canonical hero / section-header primitive. `.plan-hero`
   is the historical alias — they resolve to the same rules so the existing
   Overview / Usage / Deadline-plan surfaces keep working unchanged while
   Budget / Smart tasks / Settings / Limits / Simulation / Advanced headers
   adopt the same look via the shared selector. Tone bindings use the flat
   `--color-state-{positive,warning,negative,info}-{bg,border}` tokens.
   Surface tier is `--pels-surface-container-high` (one step above the
   device-card surface `--color-surface-1`) so the hero reads as the
   primary container on the page — see `notes/overview-hero-spec.md`. */
.plan-hero,
.pels-hero {
  display: grid;
  gap: var(--pels-hero-gap);
  padding: var(--pels-hero-padding);
  border: 1px solid var(--pels-surface-outline);
  border-radius: var(--radius-lg);
  background: var(--pels-surface-container-high);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--color-base-white) 10%, transparent);
}

/* M3 status pattern (TODO M3 alignment items #3/#7 "reduce orange weight",
   refined 2026-05-30): the tonal hero keeps a PLAIN neutral surface with a
   neutral hairline on all four sides — no saturated tint and no role-coloured
   left state-rail. (A colour bar down a card's leading edge reads as the
   Material-2 / iOS accent-list idiom, which is off-pattern for this Android/M3
   UI.) The semantic is carried entirely by the hero's status chip
   (`.plan-chip[data-tone]` inside `.plan-hero__chips`); a calm on-track hero
   has no chip and therefore no colour at all. The `good` / `warn` / `alert` /
   `info` tones flow through the shared `.plan-hero` / `.pels-hero` primitive
   with no per-tone surface override. */

/* History-detail-only tone for the Abandoned shape — a log entry, not a
 * receipt or a diagnosis. Neutral surface so the chip + postmortem carry
 * the muted framing without competing with succeeded/missed tones. The
 * other tones (`good`, `warn`, `alert`, `info`) flow through from the
 * shared `.plan-hero` / `.pels-hero` primitive because the
 * `.plan-history-detail__hero` root carries `.plan-hero pels-hero` in
 * markup (DeadlinePlanHistoryDetail.tsx); see PR #1242 codex/gemini
 * audit. */
.plan-history-detail__hero[data-tone="muted"] {
  border-color: var(--color-border-default);
  background: var(--pels-surface-container);
}

/* Canonical chip primitive. `.plan-chip` is the single source of truth for
   chip shape, padding, radius, and typography across every settings-UI
   surface — Overview status / freshness chips, Budget price-level chip,
   Usage hero delta / day-status pills, Smart-task list status/kind/
   confidence chips, deadline-plan hero chips, history-detail outcome /
   cost / shortfall chips, device-list state chips, and the device-card
   "managed-count" chip all walk this same primitive.

   Tonal state lives in two equivalent APIs (both supported so existing
   consumers don't need a mass migration to flip to the canonical attribute):
   - BEM modifier:   `.plan-chip.plan-chip--good`
   - data attribute: `.plan-chip[data-tone="good"]`

   The data-attribute form matches `.plan-hero` / `.pels-hero` so a future
   chip rebound from a hero context can carry the same `data-tone` value
   without a class swap. New consumers should prefer the data-attribute
   form; existing modifier sites continue to work.

   `.plan-chip--link` is an *additive role modifier*, not a tone — it layers
   a tappable affordance (touch target, cursor, hover/focus/active states)
   over an underlying tonal chip (today every link consumer pairs it with
   `--info` for the link-blue background). There is intentionally no
   `[data-tone="link"]` companion rule: `data-tone` is reserved for tonal
   state, and a tone-only link would ship without its touch target. Use the
   `.plan-chip--link` class form when you need link affordances. */
.plan-chip {
  display: inline-flex;
  align-items: center;
  /* Defensive native-control reset. Many chips render as `<button>` (the
     past-tasks device-filter row, future tappable chips). The real Homey
     webview leaked native light-mode button chrome (light fill + default
     text colour) onto those button-backed chips — chromium's dark emulation
     doesn't reproduce it, but the device does — so explicitly strip the UA
     appearance and pin the text colour to the theme's `--text`. Tonal
     modifiers below override `color` where they need a state tone. */
  appearance: none;
  -webkit-appearance: none;
  color: var(--text);
  padding: var(--chip-padding-block) var(--chip-padding-inline);
  border-radius: var(--chip-radius);
  border: var(--chip-border-width) solid var(--pels-surface-outline);
  background: var(--pels-surface-container-high);
  font-size: var(--chip-font-size);
  font-weight: var(--chip-font-weight);
  line-height: var(--chip-line-height);
  white-space: nowrap;
}

/* `data-tooltip` chips opt into the help cursor so the tooltip affordance
   is discoverable on hover. Ported from the retired `.chip[data-tooltip]`
   selector so the device-list state chips (Unavailable / Budget exempt /
   Flow-backed) keep their help cursor after consolidating onto `.plan-chip`. */
.plan-chip[data-tooltip] {
  cursor: help;
}

.plan-chip--ok,
.plan-chip--good,
.plan-chip[data-tone="ok"],
.plan-chip[data-tone="good"] {
  background: var(--pels-status-good-container);
  border-color: var(--pels-status-good-border);
  color: var(--pels-status-on-good);
}

.plan-chip--warn,
.plan-chip[data-tone="warn"] {
  background: var(--pels-status-warning-container);
  border-color: var(--pels-status-warning-border);
  color: var(--pels-status-on-warning);
}

.plan-chip--limited,
.plan-chip[data-tone="limited"] {
  background: var(--pels-status-limited-container);
  border-color: var(--pels-status-limited-border);
  color: var(--pels-status-on-limited);
}

.plan-chip--alert,
.plan-chip[data-tone="alert"] {
  background: var(--pels-status-critical-container);
  border-color: var(--pels-status-critical-border);
  color: var(--pels-status-on-critical);
}

.plan-chip--info,
.plan-chip[data-tone="info"] {
  background: var(--color-state-info-bg);
  border-color: var(--color-state-info-border);
  color: var(--color-state-info-text);
}

.plan-chip--muted,
.plan-chip[data-tone="muted"] {
  background: var(--pels-surface-container-high);
  border-color: var(--pels-surface-outline);
  /* Use primary text rather than --pels-text-secondary. Secondary on a
     container-high surface only reaches ~3:1 on the light canvas and ~3.9:1
     under Homey's dark-mode invert (chip text is font-size-xs = 11px, so
     small-text AA needs 4.5:1). Primary text gives ~9:1 in both modes
     while the "muted" feel is still carried by the chip's neutral container
     background and smaller font size. */
  color: var(--text);
}

/* Quiet outline chip: transparent (no fill) with a subtle border, for a
   TYPE/category label that must read as structural metadata, not a state
   signal. Pairs with the tonal (filled) status chips so a list card can show
   "Temperature" (outline) next to "Building plan…" (tonal) and the two never blur
   into one grey pair. Text stays `--text` for the same small-text AA reason
   the `--muted` variant documents; the missing fill is what makes it quiet. */
.plan-chip--outline,
.plan-chip[data-tone="outline"] {
  background: transparent;
  border-color: var(--pels-surface-outline);
  color: var(--text);
}

/* Meter-area home badge on the Devices and Modes rows — a bounded-width layer
   over the outline chip, not a second chip primitive. An area name is
   unbounded user input (a real one: "Leilighet i underetasjen"), and
   `.plan-chip` is `white-space: nowrap`, so an unclamped badge pushes the row
   past a 320 px viewport. `display: inline-block` replaces the primitive's
   `inline-flex` because `text-overflow: ellipsis` needs a block container —
   inside a flex container the label sits in an anonymous flex item and the
   ellipsis never applies. The full name stays reachable in the chip's
   tooltip (touch-triggered in the Homey WebView), so nothing is lost. */
.pels-home-badge {
  display: inline-block;
  flex: 0 1 auto;
  min-inline-size: 0;
  max-inline-size: 12ch;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The priority row's name cell is an inline-flex that now holds the device
   name AND (once meter areas are in use) its home badge. Give the name span a
   shrink floor so it wraps rather than pushing the badge out of the row. */
.mode-row__name-text {
  min-inline-size: 0;
}

.plan-chip--link {
  /* `--link` is the only tappable chip modifier — every other tonal
     variant is an informational pill. Enforce the M3 48 px touch-target
     minimum so any current and future link-shaped chip ships with a
     finger-safe tap surface, regardless of the chip's underlying
     content size. The min-block-size pushes the chip taller than the
     default ~22 px pill height; consumers shouldn't need to wrap their
     own touch target. */
  min-block-size: var(--pels-touch-target-min);
  /* The inline minimum must exceed the 48px block minimum so a SHORT label —
     most acutely the "All" filter chip — can't clamp to a 48×48 square, which
     at the chip's full `--chip-radius` (9999px) renders as a CIRCLE and reads
     as a brand-green selection dot once the tonal selected-state lands (PR2
     §9, m3-critic P0). 80px (48px touch target + `--spacing-8`) makes "All" a
     clear horizontal pill (80×48); every device-name chip already exceeds it,
     so the floor only ever fires for the shortest labels. */
  min-inline-size: calc(var(--pels-touch-target-min) + var(--spacing-8));
  /* Inline padding wider than the base chip (`--chip-padding-inline` = 10px)
     so medium labels keep breathing room inside the pill rather than crowding
     the rounded ends. */
  padding-inline: var(--spacing-4);
  cursor: pointer;
  text-decoration: none;
  transition: background var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast);
}

.plan-chip--link:hover {
  background: color-mix(in srgb, var(--color-state-info-bg) 70%, var(--pels-surface-container-highest));
  border-color: var(--accent);
}

.plan-chip--link:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.plan-chip--link:active {
  transform: translateY(1px);
}

/* Leading-icon assist-chip variant. A tappable link chip may carry a leading
   glyph (e.g. the Overview "Let it run now" rescue action) so it reads as an
   action and stays visually distinct from look-alike status badges sitting in
   the same chip row. The chip is already `inline-flex` / `align-items: center`,
   so this only adds the icon↔label gap. The icon inherits the chip's
   `currentColor` and sizes off the chip font (`1em`, per the inline-SVG icon
   convention in `views/icons.tsx`), so no separate colour or pixel size is
   introduced. */
.plan-chip--leading-icon {
  gap: var(--spacing-1);
}

/* Armed-confirm state for the "Let it run now" rescue chip (the two-step
   settings-UI confirm pattern: first tap arms, second commits). The rescue is a
   money action — it lets the device spend beyond today's budget — so the armed
   chip carries the warning tone, mirroring the Budget tab's two-step confirm
   button precedent. The state-pair tokens carry the per-theme contrast the bare
   `--warning` amber lacks. Colour-only emphasis, no layout shift. */
.plan-chip.confirming {
  background: var(--color-state-warning-bg);
  border-color: var(--color-state-warning-border);
  color: var(--color-state-warning-text);
}

/* "Let it run now" rescue chip + its armed-state consequence caption. The
   wrapper dissolves (`display: contents`) so the chip stays a normal flex item
   in the header's right-aligned chip block, while the caption can fold onto its
   own full-width row beneath it (`flex: 1 0 100%`). The caption is only present
   while armed — it carries the touch-reachable money-action consequence + the
   bounded "By {time}" horizon BEFORE the confirming tap, since a hover tooltip
   is unreachable on Homey's WebView. */
.plan-card__rescue {
  display: contents;
}

.plan-card__rescue-caption {
  flex: 1 0 100%;
  margin: var(--spacing-1) 0 0;
  text-align: left;
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-cozy);
  /* The warning tone ties the caption to the armed amber "Confirm" chip — this
     is a money action, and the soft-warning text token carries the per-theme
     contrast the bare amber lacks. */
  color: var(--color-state-warning-text);
  position: relative;
  z-index: 1;
}

/* The permissions this rescue grants and its power-limit note, under the
   caption. Deliberately muted rather than amber: the caption carries the
   money-action warning, while these are factual details — two amber blocks
   would flatten that hierarchy on a card that already
   has a chip row and a reason line. */
.plan-card__rescue-perms,
.plan-card__rescue-note {
  flex: 1 0 100%;
  margin: var(--spacing-1) 0 0;
  text-align: left;
  font-size: var(--font-size-xs);
  line-height: var(--font-line-height-cozy);
  color: var(--color-semantic-text-secondary);
  position: relative;
  z-index: 1;
}

.plan-chip__icon {
  flex-shrink: 0;
  /* Slightly larger than the xs chip label so the glyph stays legible at the
     480/320 px webview without dominating the word — an M3 assist-chip's
     leading icon runs marginally above its label cap-height. */
  font-size: var(--font-size-sm);
}

/* Selected/pressed link chip (e.g. the past-tasks device-filter row, PR-29).
   `aria-pressed="true"` is the single source of selection truth — the markup
   no longer layers a `--info` tone, which collided with the informational
   blue pill used elsewhere.

   PR2 colour policy (spec §8/§9, m3-critic P0): the pressed treatment is the
   SAME tonal selected-container the top nav uses — the `rgba(accent, 0.28)`
   tint fill (`--pels-segmented-selected-background`) plus a NEUTRAL
   outline-variant border. The earlier full-saturation `--accent` border made
   the short circular "All" chip read as a solid brand-green selection dot,
   recreating the green-overload the nav demotion just removed and forking a
   second selected-state language. Binding the border to the neutral
   `--pels-surface-outline` (the chip's own resting edge) keeps "selected" a
   control state — a tonal container, not a brand-green marker — at any chip
   width, so the nav and the filter chips speak ONE selected-state vocabulary.
   The `min-inline-size` floor on `.plan-chip--link` above keeps a short "All"
   label from collapsing into a circle that would re-read as a dot. */
.plan-chip--link[aria-pressed="true"] {
  background: var(--pels-segmented-selected-background);
  border-color: var(--pels-surface-outline);
  color: var(--pels-segmented-selected-text);
}

.plan-chip--link[aria-pressed="true"]:hover {
  background: var(--pels-shell-nav-tab-selected-hover-background);
  border-color: var(--color-border-medium);
}

/* `strong` inside a chip inherits the chip's colour + the chip's own
   `--chip-font-weight` (semibold). The earlier `.chip strong` cascade bumped
   weight to bold, but at xs font size the 1-step lift above the chip's own
   semibold is easily lost — visual emphasis intent is unclear. No current
   consumer renders `<strong>` inside a `.plan-chip`; if that changes, the
   tighter letter-spacing carries the typographic intent without leaning on
   a barely-perceptible weight step. */
.plan-chip strong {
  color: inherit;
  letter-spacing: -0.01em;
}

/* Liveness pulse for the "Building plan…" chip — the planner can spin for
   tens of seconds while waiting on price publishes / device samples, and a
   static pill reads identically whether planning just started or has been
   stuck. A low-key opacity loop tells the user the surface is alive without
   stealing attention from actionable content elsewhere on the page.
   Routed through the canonical `--pels-motion-pulse-duration` token shared
   with the device-loading notice and stepped-direction indicators so the
   cadence stays in lock-step with the other planner-liveness motions.
   Gated behind `prefers-reduced-motion: no-preference` and explicitly
   disabled under `reduce` so the chip stays still for users who opted out
   of motion. */
@media (prefers-reduced-motion: no-preference) {
  .plan-chip[data-pulse="true"] {
    animation: plan-chip-building-pulse var(--pels-motion-pulse-duration) ease-in-out infinite;
  }
}

@keyframes plan-chip-building-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

@media (prefers-reduced-motion: reduce) {
  .plan-chip[data-pulse="true"] {
    animation: none;
  }
}

.plan-hero__bar-group {
  display: grid;
  gap: var(--spacing-2);
}

/* Deadline-plan tab strip (Current plan / History) uses the canonical
   `.segmented` primitive; only layout overrides live here. */

/* Past plans list (History tab). Stacks vertically; each entry is a card. */
.plan-history-list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

/* `.plan-history-card` — decorator on `.pels-surface-card` (see canonical-
   primitives preamble above `.pels-surface-card`). Only overrides the
   default grid layout to a top-down flex column (the card stacks header +
   device label + progress lines in a tight rhythm) and tightens the inter-
   line gap. */
.plan-history-card {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

.plan-history-card__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--spacing-2);
}

.plan-history-card__deadline {
  font-weight: var(--font-weight-semibold);
  color: var(--pels-text-primary);
}

.plan-history-card__device {
  font-size: var(--font-size-sm);
  color: var(--pels-text-secondary);
}

.plan-history-card__progress {
  font-size: var(--font-size-sm);
  color: var(--pels-text-secondary);
}

.plan-history-card__reached {
  color: var(--pels-text-secondary);
}

.plan-history-card__coverage {
  font-size: var(--font-size-xs);
  color: var(--pels-text-tertiary, var(--pels-text-secondary));
  font-style: italic;
}

/* Muted "Cost ≈ X kr · Y kWh delivered" meta line from the persisted run
 * totals (`formatPlanHistoryListCostAndDelivered`, whole kroner to match the
 * ISO-week divider roll-up + detail cost chip). The past list is a calm glance
 * surface, so the cost reads as supporting metadata — the same secondary tone
 * the overshoot line carries — never competing with the deadline/progress
 * lines above it. Producer returns `null` when neither cost nor delivery was
 * recorded, so the line is suppressed rather than faking a 0 kr row. */
.plan-history-card__cost {
  font-size: var(--font-size-xs);
  color: var(--pels-text-supporting-color);
}

/* v2.9.x batch 47 — muted overshoot signal on Succeeded list cards whose
 * final reading exceeded the target by > 5 °C / > 10 %. Informational, not
 * alarming: same supporting tone as the coverage line below it so the user
 * scans past the common no-overshoot case without friction and catches the
 * outlier when it appears. Producer resolves `null` for non-met outcomes
 * and within-threshold runs (see `formatPlanHistoryOvershootLine`); the
 * view never reads `outcome` to decide whether to render. */
.plan-history-card__overshoot {
  font-size: var(--font-size-xs);
  color: var(--pels-text-supporting-color);
}

/* Smart-tasks polish PR-7 — "why" reason line under the progress/target line on
 * Missed list cards. Mirrors the blameless single-sentence "Why:" the detail
 * hero renders (sourced from `formatPlanHistoryMissedReason`) so the scanning
 * user sees the cause without tapping through. Producer resolves `null` on
 * non-missed outcomes; the view never branches on `outcome`.
 *
 * PR2 surface/colour system (spec §7): the reason is the single most useful
 * line on a Missed row, so it must OUT-RANK the row metadata (coverage /
 * overshoot) rather than dissolve into it. Promoted to the primary on-surface
 * tone at the supporting size — a small but legible block, brighter than the
 * secondary-tone coverage/overshoot lines below it. The warn-tone container the
 * Missed row now carries (`data-tone="warn"`) supplies the emotional colour;
 * this line carries the legibility. */
.plan-history-card__reason {
  font-size: var(--font-size-sm);
  color: var(--pels-text-primary);
}

/* "Why:" prefix on the reason line — a semibold lead-in so the reason reads
 * distinctly from the same-size coverage line that can sit below it. Mirrors
 * the detail hero's "Why:" framing; inherits the line's primary tone. */
.plan-history-card__reason-label {
  font-weight: var(--font-weight-semibold);
  color: var(--pels-text-primary);
}

/* `.plan-history-card--link` — link-role decorator on `.plan-history-card`
   (see canonical-primitives preamble above `.pels-surface-card`). Anchor-
   reset (no underline / inherit colour) marks the past-plan link as
   actionable. Focus ring + elevation lift ride the canonical
   `.pels-surface-card[data-interactive]` hook the host sets.
   PR2: the hover/focus background swap is dropped — each row now carries an
   outcome tonal container (`data-tone`, spec §7), and a flat hover fill would
   erase that colour on pointer-over. The canonical M3 elevation lift already
   signals interactivity, so the row keeps its tone while hovered. */
.plan-history-card--link {
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}

.plan-history-detail {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

.plan-history-detail__hero {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}

/* Outcome chip lifted out of the heading row so the answer to "did it
 * succeed?" reads ahead of the timestamp instead of being tucked to the
 * right. The chip primitive carries its own tone (ok / warn / muted); this
 * row exists purely as a layout container. */
.plan-history-detail__outcome {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
}

/* Raise the outcome chip's visual weight so it competes with the heading
 * timestamp — the user is here to confirm the outcome, not the moment.
 * Bumps font-size from chip's default (xs) up to sm so it reads as the
 * primary signal. */
.plan-history-detail__outcome-chip {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
}

.plan-history-detail__hero-header {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

/* Heading carries both device name and timestamp on a single line. Wrapping
 * is allowed (smart task device names can be long) so the timestamp falls
 * onto its own line on 320px rather than getting clipped. */
.plan-history-detail__heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--spacing-1);
}

/* Timestamp on the heading reads in muted weight so the device name is the
 * primary subject. The device-name span inherits the heading's
 * `--pels-text-card-title-color` and weight from `.plan-card__title`; only
 * the timestamp span needs to de-emphasise. tabular-nums keeps clock digits
 * aligned vertically when multiple history entries open in sequence. */
.plan-history-detail__heading-when {
  color: var(--muted);
  font-weight: var(--font-weight-medium);
  font-variant-numeric: tabular-nums;
}

.plan-history-detail__progress {
  font-size: var(--font-size-sm);
  color: var(--pels-text-secondary);
}

.plan-history-detail__reached {
  color: var(--pels-text-secondary);
}

/* Cross-link to the device's usage page. Without an explicit colour the
 * bare `<a>` falls back to the UA's `rgb(0,0,238)` browser-blue underline,
 * which clashes with the dark theme and reads as foreign chrome. Match the
 * canonical PELS inline-link treatment (`.field__hint a`): accent green with
 * a toned 2 px underline offset so it reads as an in-app link. */
.plan-history-detail__usage-link-anchor {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Missed-only postmortem sentence under the progress line. Tone matches the
 * outcome chip's warning rung so the "why" reads as an extension of the
 * "what happened" signal already on the hero. Sits in the supporting font
 * size so it doesn't compete with the headline; the warn color keeps it
 * legible against the hero background. Scoped to the `warn` tone so the
 * class can't leak a warn color onto succeeded/abandoned shapes if a
 * future variant reuses it. */
.plan-history-detail__hero[data-tone="warn"] .plan-history-detail__missed-reason {
  margin: var(--spacing-2) 0 0;
  font-size: var(--font-size-sm);
  color: var(--pels-status-on-warning);
}

/* Outcome headline — the answer to "what happened on this run?". Kept at
 * section-headline scale within the hero block, immediately
 * after the device-name header. Reading order inside the hero is
 * headline -> supporting (cost / delivered) -> why -> recourse -> progress,
 * with the chart card following. The live Smart-task page owns the large
 * display headline; historical receipts stay compact on mobile. */
.plan-history-detail__outcome-headline {
  margin: 0;
  color: var(--pels-text-section-headline-color);
  font-size: var(--pels-text-section-headline-font-size);
  font-weight: var(--pels-text-section-headline-font-weight);
  line-height: var(--pels-text-section-headline-font-line-height);
}

/* Secondary line under the outcome headline: cost + delivered (succeeded /
 * missed) or partial-delivery summary (abandoned). Muted so the outcome
 * headline stays primary; tabular-nums keeps cost digits aligned when
 * several past entries open in sequence. */
.plan-history-detail__secondary {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--pels-text-secondary);
  font-variant-numeric: tabular-nums;
}

/* v2.9.x batch 47 — muted overshoot signal on the history-detail hero for
 * Succeeded entries whose final reading exceeded the target by > 5 °C / > 10 %
 * (e.g. lived-state `29.3 → 77.7 °C · target 65 °C`). Informational, not
 * alarming: same supporting tone as the cost narrative below it so the user
 * scans past the common no-overshoot case without friction and catches the
 * outlier when it appears. Producer resolves `null` for non-met outcomes and
 * within-threshold runs (see `formatPlanHistoryOvershootLine`); the view never
 * reads `outcome` to decide whether to render. */
.plan-history-detail__overshoot {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--pels-text-supporting-color);
  font-variant-numeric: tabular-nums;
}

/* Recourse CTA modifier on the history-detail hero. The base
 * `.plan-hero__recourse` already adds `margin-top: var(--spacing-3)` for
 * the live-hero ordering where the button sits at the end. On the history
 * hero the button sits between postmortem and progress, so suppress that
 * margin-top — the parent hero's flex gap already provides the rhythm. */
.plan-hero__recourse.plan-history-detail__recourse {
  margin-top: 0;
}

/* Inline revision-history `<details>` panel rendered on the smart-task
 * detail page (live plan). Default-collapsed; the `<summary>` row carries the
 * title + count and opens the embedded `.plan-revision-log`. Shares the row
 * styles with the post-finalization history-detail page so both surfaces
 * look identical when narrating the same revision shape. */
.plan-revision-panel {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}

.plan-revision-panel__summary {
  /* Grid keeps the chevron right-anchored on the title row even when the
   * hint text wraps to a second line at 320 px (`Schedule revised — daily
   * budget shifted · 15:42 · (+1h)` is wider than the card). Two-column
   * grid with the title spanning column 1, the chevron pinned to the right
   * of column 2, and the hint dropping to a full-width second row. */
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--spacing-1) var(--spacing-2);
  /* M3 touch-target floor — binds to the canonical token so any future
   * adjustment lands consistently with the other disclosure summaries. */
  min-height: var(--pels-touch-target-min);
  padding: var(--spacing-1) 0;
  cursor: pointer;
  list-style: none;
}

.plan-revision-panel__summary::-webkit-details-marker {
  display: none;
}

/* Producer-resolved subline that narrates the latest *system* revision
 * (`Schedule revised — daily budget shifted · 15:42 · +1h`). Lives OUTSIDE
 * the `<details>` so it stays visible while the panel is collapsed — HTML
 * hides every child of `<details>` except `<summary>` when closed, so the
 * subline must be a sibling.
 *
 * Tokens only: muted tone via `--pels-text-supporting-color`; the card's
 * grid gap (`--pels-card-gap` on `.pels-surface-card`) governs the spacing
 * to the eyebrow above and the `<details>` block below, so the `<p>` only
 * needs its default browser margins zeroed. Wraps cleanly at 320 px by
 * inheriting the card's natural width. */
.plan-revision-panel__summary-subline {
  margin: 0;
  color: var(--pels-text-supporting-color);
}

/* Per-revision log card on the smart-task history detail page (v2.7.2 PR 5).
 * One row per replan; the producer (`formatPlanHistoryRevisionEntry`) resolves
 * the visible strings so the view layer only positions them. Rows stack
 * vertically; on the 320–480 px target widths the time/reason/diff segments
 * sit on one line whenever they fit and wrap onto the next line otherwise. */
.plan-revision-log {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

.plan-revision-row {
  /* Three columns: time (5ch tabular), reason (absorbs), diff (auto).
   * Switched from flex+wrap to grid so on 320 px viewports the reason can
   * wrap onto multiple lines without dragging the diff chip with it — the
   * diff column stays glued to the right and the reason just gets taller.
   * Replaces the earlier flex layout where a long reason pushed the chip
   * to the bottom-right of the wrapped row and read like it belonged to
   * the next row. */
  display: grid;
  grid-template-columns: 5ch 1fr auto;
  align-items: baseline;
  gap: var(--spacing-1) var(--spacing-2);
  padding: var(--spacing-1) var(--spacing-2);
  background: var(--pels-surface-container-low);
  border-radius: var(--radius-sm);
}

.plan-revision-time {
  color: var(--pels-text-supporting-color);
  font-variant-numeric: tabular-nums;
}

.plan-revision-reason {
  color: var(--pels-text-primary);
  /* min-width: 0 lets the grid track shrink below the reason's intrinsic
   * width so long labels wrap inside the column instead of expanding it. */
  min-width: 0;
}

.plan-revision-diff {
  color: var(--pels-text-supporting-color);
  font-variant-numeric: tabular-nums;
  font-size: var(--font-size-sm);
  /* Hint to the user that the chip carries an aria-label tooltip when they
   * hover. No-op visually but matches the m3 disclosure pattern for chips. */
  cursor: default;
}

/* v2.7.3 — Succeeded "receipt" timeline rendered below the outcome headline.
 * Three rows max, each carrying a leading dot + tabular-nums clock + label +
 * optional detail tail. tabular-nums keeps the time column aligned across
 * rows; the dot is a small accent-toned marker (no SVG/icon dependency) so
 * the timeline reads as a receipt rather than a generic list. */
.plan-history-detail__receipt {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

.plan-history-detail__receipt-row {
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: baseline;
  gap: var(--spacing-1) var(--spacing-2);
  padding: var(--spacing-1) 0;
}

.plan-history-detail__receipt-dot {
  /* 8 px dot — bound to `--spacing-2` so a token bump nudges this with the
   * rest of the receipt rhythm (v2.7.3 P2 token-binding fold-in). */
  width: var(--spacing-2);
  height: var(--spacing-2);
  border-radius: var(--radius-full);
  background: var(--color-role-accent);
  align-self: center;
}

.plan-history-detail__receipt-time {
  color: var(--pels-text-primary);
  font-variant-numeric: tabular-nums;
  font-weight: var(--font-weight-semibold);
  min-width: 4.5ch;
}

.plan-history-detail__receipt-label {
  color: var(--pels-text-primary);
}

.plan-history-detail__receipt-detail {
  /* Detail tail spans from the time column through the end of the row,
   * wrapping onto a fresh line beneath the time + label pair so it never
   * crowds the label on narrow widths. Muted weight keeps the time the
   * loudest token in the row. */
  grid-column: 2 / -1;
  color: var(--pels-text-secondary);
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
}

/* Cost narrative chip (Succeeded + Missed). Renders as a paragraph wrapping
 * the existing `.plan-chip--muted` primitive — the wrapper exists so the
 * chip has its own row, not so we override its tone. */
.plan-history-detail__cost-narrative {
  margin: 0;
}

.plan-history-detail__cost-narrative-chip {
  white-space: normal;
  font-size: var(--font-size-sm);
}

/* Missed shortfall summary chip. Same chip primitive as the cost narrative;
 * separate wrapper class lets us tune the row independently if the rhythm
 * needs adjustment later. */
.plan-history-detail__shortfall {
  margin: 0;
}

.plan-history-detail__shortfall-chip {
  white-space: normal;
  font-size: var(--font-size-sm);
}

/* Abandoned `<details>` disclosure — Android-flavoured Material expansion
 * (chevron, not iOS arrow). The summary is the affordance; the body is a
 * quiet list that the user opts into. */
.plan-history-detail__abandoned-details {
  margin: 0;
}

.plan-history-detail__abandoned-summary {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
  min-height: var(--pels-touch-target-min);
  padding: var(--spacing-1) var(--spacing-2);
  border-radius: var(--radius-md);
  color: var(--pels-text-secondary);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  list-style: none;
}

/* Hide the default disclosure triangle in WebKit so we can rely on the
 * `::before` chevron below. Firefox respects `list-style: none` already. */
.plan-history-detail__abandoned-summary::-webkit-details-marker {
  display: none;
}

.plan-history-detail__abandoned-summary::before {
  content: '';
  width: 8px;
  height: 8px;
  border-right: 2px solid currentcolor;
  border-bottom: 2px solid currentcolor;
  transform: rotate(-45deg);
  transition: transform var(--transition-fast);
}

.plan-history-detail__abandoned-details[open]
  > .plan-history-detail__abandoned-summary::before {
  transform: rotate(45deg);
}

.plan-history-detail__abandoned-summary:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.plan-history-detail__abandoned-body {
  list-style: none;
  margin: var(--spacing-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

.plan-history-detail__abandoned-line {
  color: var(--pels-text-secondary);
  font-size: var(--font-size-sm);
}

.pels-meter-track {
  position: relative;
  height: 14px;
  overflow: visible;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--pels-surface-container-highest) 80%, transparent);
}

/* Single-segment fill (energy meter). The width and tone are set inline by the
   component because the width depends on the dynamic scale. */
.pels-meter-track__fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: inherit;
  background: var(--pels-status-good);
  pointer-events: none;
}

.pels-meter-track__fill[data-tone="warning"] {
  background: var(--pels-status-warning);
}

.pels-meter-track__fill[data-tone="critical"] {
  background: var(--pels-status-critical);
}

/* Segmented power-bar fill: managed | background. Free space is the empty
   track remainder. The safe-pace marker rides on top. */
.pels-meter-segments {
  position: absolute;
  inset: 0;
  display: flex;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
}

.pels-meter-segments__seg {
  display: block;
  height: 100%;
}

.pels-meter-segments__seg--managed {
  background: var(--pels-status-good);
}

/* State-coloured fill per the hero spec's status tones: under safe pace the
   WHOLE fill reads success-toned — managed at full mint, background as a
   dimmer step of the same success tone — so an all-background household no
   longer sees a grey-on-grey gauge. The two segments stay distinguishable
   (bright vs dim), and the supporting "Managed X kW · Background Y kW" line
   carries the split meaning (colour never carries meaning alone). Above the
   safe pace the trailing segment flips to the warning tone below. */
.pels-meter-segments__seg--background {
  background: color-mix(in srgb, var(--pels-status-good) 42%, var(--pels-surface-container-high));
}

/* When current power crosses the safe pace, the trailing rendered segment
   carries the warning tone so the over-threshold portion reads as an overflow
   stripe. The tone applies to the background segment when it is present,
   otherwise to the managed segment so a managed-only overflow still surfaces
   visually. There is no instantaneous over-hard-cap tone — the cap is an
   hourly-average ceiling; the critical story lives on the chip/rim and the
   energy bar's projection marker. */
.pels-meter-segments__seg--background[data-over-safe-pace],
.pels-meter-segments__seg--managed[data-over-safe-pace] {
  background: var(--pels-status-warning);
}

/* v2.7.3 managed-segment breathing: a single live moment on the Overview hero
 * while PELS is actively limiting. Opacity-only oscillation (0.85 ↔ 1.0) over
 * 3.5s; no hue shift, no scale change. The hero is the single source of "PELS
 * is doing something right now" — keep the motion contained here. */
@keyframes pels-managed-breathing {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.85; }
}

@media (prefers-reduced-motion: no-preference) {
  .pels-meter-segments__seg--managed[data-limiting] {
    animation: pels-managed-breathing 3.5s ease-in-out infinite;
  }
}

/* Sublegend below a hero meter — renders only when the bar carries two or
   more markers (single-marker bars rely on the per-marker `aria-label` and
   the visible position alone). Swatch geometry mirrors the matching
   `.pels-meter-track__marker--*` rule so the legend reads as a key. */
.plan-hero__legend {
  display: flex;
  flex-flow: row wrap;
  gap: var(--spacing-1) var(--spacing-3);
  margin-top: var(--spacing-1);
  color: var(--text-secondary);
  font-size: var(--font-size-xs);
  line-height: var(--font-line-height-tight);
}

.plan-hero__legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
}

/* Legend swatches render as MINI-TRACKS: a short rounded track sample with
   the marker glyph riding on it, exactly as it appears on the real bar. A
   bare vertical tick or hollow dot floating beside its label reads as ASCII
   punctuation ("| Safe pace", "o Projected") — the track underneath is what
   makes each glyph read as a key to the meter. The default tone is the
   neutral secondary-text reference used by the target/safe-pace marker; the
   projected swatch overrides it per status tone below. */
.plan-hero__legend-swatch {
  --legend-tone: var(--pels-text-secondary);
  position: relative;
  display: inline-block;
  flex: 0 0 auto;
  width: 24px;
  height: 10px;
  border-radius: var(--radius-full);
  background: var(--pels-surface-container-highest);
  overflow: visible;
}

.plan-hero__legend-swatch::after {
  content: "";
  position: absolute;
}

/* Thin threshold tick, centred on the mini-track (mirrors --marker--target,
   including its surface-toned separation halo). */
.plan-hero__legend-swatch--target::after {
  top: 50%;
  left: 50%;
  width: 3px;
  height: 12px;
  transform: translate(-50%, -50%);
  border-radius: var(--radius-full);
  background: var(--legend-tone);
  box-shadow: 0 0 0 1px var(--pels-surface-container-highest);
}

/* Hard-cap reference tick: warning-toned, centred on the mini-track like the
   real marker (which renders wherever the cap value falls on the scale —
   legitimately even left of the safe-pace tick late in an under-used hour). */
.plan-hero__legend-swatch--cap::after {
  top: 50%;
  left: 50%;
  width: 4px;
  height: 12px;
  transform: translate(-50%, -50%);
  border-radius: var(--radius-full);
  background: var(--pels-status-warning);
  box-shadow: 0 0 0 1px var(--pels-surface-container-highest);
}

/* Hollow projected dot, centred on the mini-track (mirrors
   --marker--projected). */
.plan-hero__legend-swatch--projected::after {
  top: 50%;
  left: 50%;
  width: 8px;
  height: 8px;
  transform: translate(-50%, -50%);
  border: 2px solid var(--legend-tone);
  border-radius: var(--radius-full);
  background: var(--pels-surface-container-high);
}

.plan-hero__legend-swatch--projected[data-tone="good"] {
  --legend-tone: var(--pels-status-good);
}

.plan-hero__legend-swatch--projected[data-tone="warning"] {
  --legend-tone: var(--pels-status-warning);
}

.plan-hero__legend-swatch--projected[data-tone="critical"] {
  --legend-tone: var(--pels-status-critical);
}

.pels-meter-track__marker {
  --marker-tone: var(--text);
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  cursor: help;
  z-index: 1;
}

.pels-meter-track__marker[data-tone="good"] {
  --marker-tone: var(--pels-status-good);
}

.pels-meter-track__marker[data-tone="warning"] {
  --marker-tone: var(--pels-status-warning);
}

.pels-meter-track__marker[data-tone="limited"] {
  --marker-tone: var(--pels-status-limited);
}

.pels-meter-track__marker[data-tone="critical"] {
  --marker-tone: var(--pels-status-critical);
}

.pels-meter-track__marker::before {
  content: "";
  position: absolute;
  inset: -8px;
}

.pels-meter-track__marker--projected {
  width: 8px;
  height: 8px;
  border: 2px solid var(--marker-tone);
  border-radius: var(--radius-full);
  background: var(--pels-surface-container);
}

/* Target / cap markers extend a half-pixel on each side via box-shadow so
   sub-pixel rendering does not leave a hairline gap between the marker body
   and the trailing edge of the colored fill when the bar reaches (or just
   crosses) the marker position. The shadow inherits the marker tone so it
   reads as a continuation of the marker, not a halo.

   The target (safe-pace / budget-this-hour) marker is a neutral "stay under
   here" reference line, NOT a status. It uses the semantic secondary-text tone
   so it reads as a calm guideline that stays legible on both the filled and
   empty portions of the track in dark and light themes — and stays visually
   distinct from the warning-toned hard-cap marker. The legend swatch and
   per-marker aria-label/tooltip name it explicitly. */
.pels-meter-track__marker--target {
  width: 3px;
  height: 14px;
  border-radius: var(--radius-full);
  background: var(--pels-text-secondary);
  /* Ring in the track-surface tone (not the marker tone) so the neutral grey
     "stay under here" tick stays separated from whatever fill sits under it.
     In the over-threshold state the fill behind the marker is
     `--pels-status-warning` (amber), where a bare grey tick is only ~2:1; the
     surface-toned halo carves a gap on both the mobile-dark and desktop-light
     canvases so the marker reads against the amber (and the managed-green)
     fill. On the empty track the halo matches the track and is invisible. */
  box-shadow: 0 0 0 1px var(--pels-surface-container-highest);
}

.pels-meter-track__marker--cap {
  width: 4px;
  height: 14px;
  border-radius: var(--radius-full);
  background: var(--pels-status-warning);
  /* Surface-toned separation halo, same trick as the target marker above: the
     over-safe-pace overflow fill is ALSO `--pels-status-warning`, so a bare
     amber tick (or an amber-toned shadow) disappears into it exactly when the
     user goes looking for the cap line. The halo carves a track-toned gap on
     any fill; on the empty track it matches the surface and is invisible. */
  box-shadow: 0 0 0 1px var(--pels-surface-container-highest);
  transform: translate(-100%, -50%);
}

.plan-hero__chips {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--spacing-1) var(--spacing-2);
  margin-bottom: var(--spacing-4);
  min-width: 0;
}

.plan-hero__chip-rail {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-1) var(--spacing-2);
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
}

/* Info-icon button anchored to the top-right of the chip row. The Material
   icon-button state-layer fills the touch target; the icon glyph stays at
   20 px. Touch target follows the M3 minimum (`--pels-touch-target-min`,
   48 px) — the previous 36 px state layer was below the project floor and
   off-screen at 320 px because the surrounding overflow pushed it past the
   viewport. */
.plan-hero__info-button {
  flex: 0 0 auto;
  align-self: flex-start;
  /* The 48 px touch target must not inflate the chip row (chips are ~24 px):
     pull the state layer's vertical overflow out of layout — the standard M3
     icon-button overlap — so the header row hugs the chips instead of adding
     ~25 px of dead space above "Power now", and the glyph centers on the chip
     axis instead of sitting ~10 px below it. Hit area is unchanged. */
  margin-block: calc((var(--pels-touch-target-min) - var(--spacing-6)) / -2);
  --md-icon-button-state-layer-height: var(--pels-touch-target-min);
  --md-icon-button-state-layer-width: var(--pels-touch-target-min);
  --md-icon-button-icon-size: 20px;
  --md-icon-button-icon-color: var(--pels-text-secondary);
  --md-icon-button-hover-icon-color: var(--pels-text-primary);
  --md-icon-button-focus-icon-color: var(--pels-text-primary);
  --md-icon-button-pressed-icon-color: var(--pels-text-primary);
}

.plan-hero__info-icon {
  width: 20px;
  height: 20px;
  display: block;
}

.plan-hero__section {
  display: grid;
  gap: var(--pels-hero-section-gap);
}

.plan-hero__section + .plan-hero__section {
  padding-top: calc(var(--spacing-4) + var(--spacing-1));
}

/* Hero section label. Historically this carried the all-caps `.eyebrow`
   marketing overline above every hero heading. The M3 alignment pass (TODO #5)
   drops that idiom in favour of Homey-native single-weight sentence-case
   section labels so the display heading leads. The element keeps the `.eyebrow`
   class in markup (a11y `aria-labelledby` targets + the hero-rebind contract
   depend on it), but this rule overrides the eyebrow's uppercase transform and
   letter-spacing so it renders as a quiet, sentence-case label. */
.plan-hero__section-label {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--pels-text-section-label-font-size);
  font-weight: var(--pels-text-section-label-font-weight);
  line-height: var(--pels-text-section-label-font-line-height);
  color: var(--pels-text-section-label-color);
}

.plan-hero__headline-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-2);
  flex-wrap: wrap;
}

/* Device-hero decorator (after the canonical rule per the cascade idiom):
   the trailing power fact reads at the supporting size beside a
   display-size state word, and centre alignment floats it mid-cap-height —
   this hero's headline row aligns to baseline instead (the Overview metric
   row makes the same choice). */
.device-detail-live-status .plan-hero__headline-row {
  align-items: baseline;
}

/* M3: one tonal story per surface. Hero rim + status chip carry the warn/alert
   signal; the headline value stays neutral. */
.plan-hero__headline {
  /* `margin: 0` resets the legacy `.pels-hero h2 { margin: 4px 0 0 }`
     cascade so the consolidated primitive owns its own block-start spacing.
     Before the batch-7 rebind only Smart-tasks rendered the headline via
     `.plan-hero__headline`; afterwards Budget / Usage / Settings / Advanced
     all do too, so the legacy 4 px nudge would otherwise leak onto five
     surfaces. */
  margin: 0;
  color: var(--pels-text-display-color);
  font-size: var(--pels-text-display-font-size);
  font-weight: var(--pels-text-display-font-weight);
  line-height: var(--pels-text-display-font-line-height);
  letter-spacing: 0;
  /* Tabular numerals so metric headlines don't reflow/jitter as digits change
     between render cycles (e.g. "0.3" → "0.4" → "12.0"). */
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
}

/* Numeric-first metric headline: the value leads as the dominant element, the
   unit/qualifier trails smaller and quieter beneath it. Used by the Overview
   "Power now" + "Energy used this hour" heroes. */
.plan-hero__metric {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--spacing-1);
}

.plan-hero__metric-value {
  font-size: var(--pels-text-display-font-size);
  font-weight: var(--pels-text-display-font-weight);
  line-height: var(--pels-text-display-font-line-height);
}

.plan-hero__metric-qualifier {
  font-size: var(--pels-text-supporting-font-size);
  font-weight: var(--font-weight-medium);
  line-height: var(--pels-text-supporting-font-line-height);
  color: var(--pels-text-supporting-color);
}

/* Scale the hero display type down on the narrowest viewports (≤360px, the
   320px floor included) so long display headings and metric values never
   overflow the hero card. */
@media (max-width: 360px) {
  .plan-hero__headline,
  .plan-hero__metric-value {
    font-size: var(--font-size-headline-md);
  }
}

.plan-hero__subline {
  color: var(--pels-text-supporting-color);
  font-size: var(--pels-text-supporting-font-size);
  line-height: var(--pels-text-supporting-font-line-height);
}

/* M3: one tonal story per surface. The hero rim + status chip + power-bar
   overshoot carry the warn/alert signal — the subline stays neutral primary.
   `data-tone` is still honoured on the warn rung so projection sublines (which
   never escalate beyond warn) remain legible at a glance, but the critical /
   alert rung renders identical to the default subline copy: red headline +
   red subline + red chip + red rim was four redundant tonal voices. */
.plan-hero__subline[data-tone="warn"],
.pels-hero__subline[data-tone="warn"] {
  color: var(--pels-status-on-warning);
}

.plan-hero__subline--muted {
  color: var(--pels-text-supporting-color);
}

/* v2.7.3 anticipation subline: surfaces the next cheap window beneath the
 * energy section so the user can plan high-load appliances. Sits below the
 * energy bar (after `.plan-hero__bar-group`) with a small breathing margin so
 * it reads as a third tier of information rather than another header. Accent
 * color stays muted — anticipation is information, not a call to action. */
.plan-hero__subline--anticipation {
  margin-top: var(--spacing-2);
  color: var(--pels-text-supporting-color);
}

/* Queued-headline "why" subline (e.g. "Waiting for tomorrow's prices through
 * HH:MM"). Sits between the headline and the device/target subline so the
 * user reads it as a direct answer to the headline. Primary-supporting tone
 * keeps it readable without escalating to a warn voice — the hero is on-track
 * at this point. */
.plan-hero__subline--reason {
  color: var(--pels-text-primary);
}

/* Pending-hero "why is this still building?" body copy (e.g. "PELS needs a
 * current state of charge…", "Learning energy use — needs power readings from
 * this device."). Sits in the metaLine slot but, unlike the ready hero's
 * recap meta lines, this is the most actionable string on the surface — it
 * tells the user exactly which lever to pull. Bumped to `--pels-text-primary`
 * so it reads at default body weight instead of being demoted to the muted
 * secondary tone. Typography (size + line-height) inherits from the base
 * `.plan-hero__subline` rule so the cadence with the device/target subline
 * directly above it stays consistent. */
.plan-hero__subline--action {
  color: var(--pels-text-primary);
}

/* Smart-task detail hero density. The cannot-finish hero stacks a target/why
 * subline above a group of muted meta lines (`Needs … · kW · hours`, the
 * delivered-so-far line, and the cost line). At the section's 4 px grid gap
 * those collapse into one dense block on a 320–480 px webview. Add a tier
 * between the non-muted subline and the muted meta group, plus a hair more
 * between consecutive muted lines, so "what / why" reads apart from "the
 * numbers". These margins are intentionally *additive* to the section's
 * 4 px grid gap, so the effective separation is 12 px (subline → meta group)
 * and 8 px (between muted lines) — a deliberate two-step rhythm over the 4 px
 * baseline, not a replacement for it. Scoped to `#deadline-plan-panel` so the
 * Overview / Budget heroes (whose muted lines follow a `headline-row`, not a
 * subline) keep their tighter rhythm untouched. */
#deadline-plan-panel .plan-hero__subline--muted + .plan-hero__subline--muted {
  margin-top: var(--spacing-1);
}

#deadline-plan-panel .plan-hero__subline:not(.plan-hero__subline--muted)
  + .plan-hero__subline--muted {
  margin-top: var(--spacing-2);
}

/* Cannot-finish remedy line. On the alert hero the meta line opens with the
 * remedy sentence ("Not enough time for this target. Lower the target or move
 * the deadline.") — the most actionable string on the surface — so it renders
 * at the primary body tone instead of the muted supporting tone the recap
 * lines use. The adjacent-sibling selector targets exactly the meta line (the
 * first muted subline after the device/target subline); the delivered-so-far
 * and cost recap lines below it stay muted. Token swap only, mirroring
 * `.plan-hero__subline--action` on the pending hero. Bare `[data-tone="alert"]`
 * (instead of `.plan-hero[data-tone="alert"]`) keeps the selector inside the
 * stylelint specificity ceiling; within `#deadline-plan-panel` only the hero
 * section carries that tone. */
#deadline-plan-panel [data-tone="alert"]
  .plan-hero__subline:not(.plan-hero__subline--muted)
  + .plan-hero__subline--muted {
  color: var(--pels-text-primary);
}

/* Smart-task hero on-track status row. Hoists the trajectory's on-track
 * verdict onto the hero so "am I on track?" is answerable above the fold —
 * check glyph + "On track" in the success tone, then the projected-ready
 * supporting phrase in primary body copy. Baseline-aligned so the glyph sits
 * on the label's cap line; wraps the supporting phrase onto its own run at
 * narrow widths without dropping the glyph. */
.deadline-hero-status {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--spacing-1) var(--spacing-2);
  margin: var(--spacing-2) 0 0;
}

.deadline-hero-status__icon {
  color: var(--pels-status-on-good);
  font-size: var(--font-size-md);
  /* Nudge the round glyph onto the text baseline (icons sit low otherwise). */
  transform: translateY(0.12em);
}

.deadline-hero-status__label {
  color: var(--pels-status-on-good);
  font-weight: var(--font-weight-semibold);
}

.deadline-hero-status__supporting {
  color: var(--pels-text-primary);
  font-size: var(--pels-text-supporting-font-size);
  line-height: var(--pels-text-supporting-font-line-height);
}

/* Smart-task hero stat pairs (Needs / Est. cost). Replaces the old grey
 * `Needs … · N hours left · Auto` + cost-line metadata wall with compact
 * label-over-value pairs so the payoff numbers are the loud element, not a
 * muted sentence. `<dl>`/`<dd>` default margins are reset — the flex column
 * owns the spacing. */
.deadline-hero-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-2) var(--spacing-4);
  margin: var(--spacing-2) 0 0;
}

.deadline-hero-stats__pair {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  min-width: 0;
}

.deadline-hero-stats__label {
  color: var(--pels-text-supporting-color);
  font-size: var(--pels-text-supporting-font-size);
  line-height: var(--font-line-height-tight);
}

.deadline-hero-stats__value {
  margin: 0;
  color: var(--text);
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  line-height: var(--font-line-height-tight);
}

/* The task action row sits directly beneath the hero: Edit task as the filled
 * action, the destructive Clear pushed to the far edge so it can't be
 * fat-fingered from it (it wraps below on 320 px where the row breaks). The
 * edit form below it holds only Save and Discard. */
.smart-task-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-2);
}

.smart-task-actions > md-text-button {
  margin-left: auto;
}

/* Homey's host stylesheet resets every element's padding, which collapses the
 * label onto the pill's edges; restore the M3 lead/trail space (same idiom as
 * `.homes-settings md-filled-button`) and never shrink it beside Clear. */
.smart-task-actions > md-filled-tonal-button {
  flex: none;
  padding-inline: var(--spacing-6);
}

.smart-task-edit {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

/* Native time input, bound to the SAME container tokens the MD filled text
 * field beside it resolves to (`--color-surface-4` container, `--radius-md`
 * corner — see the `--md-filled-text-field-*` bindings at :root) so the two
 * fields in this one form speak one field language. `appearance: none` +
 * explicit disabled state keep UA chrome out; `color-scheme` follows the
 * app's dark scheme so the picker glyph / spinners stay visible on the dark
 * production theme. */
.smart-task-edit__time-input {
  /* The MD field defines this on its own element, so a native input next to it
   * inherits nothing; set the same token here (and the light-canvas override
   * with the MD fields below). */
  --md-filled-text-field-container-color: var(--color-surface-4);
  min-height: var(--pels-touch-target-min);
  padding: var(--spacing-2) var(--spacing-3);
  appearance: none;
  -webkit-appearance: none;
  background: var(--md-filled-text-field-container-color);
  color: var(--text);
  color-scheme: dark;
  border: none;
  border-radius: var(--radius-md);
  font-family: var(--font-sans);
  font-size: var(--font-size-md);
}

.smart-task-edit__time-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.smart-task-edit__time-input:disabled {
  opacity: 0.38; /* mirrors MD's disabled-content opacity */
}

/* Desktop ships the light canvas (Homey's shell invert produces dark — see
 * the theme header comment); the UA time-control internals must follow it. */
@media (hover: hover) and (pointer: fine) {
  .smart-task-edit__time-input {
    color-scheme: light;
  }
}

/* "Extra permissions" disclosure inside the open editor. Same `<details>` +
 * `.disclosure-chevron` idiom — and the same `.plan-card__title` summary rank —
 * as the revision panel further down the page, so every collapsible block here
 * opens the same way and the section title outranks the rows it contains. The
 * rows are the canonical `.md-switch-row` grammar and bring their own 48 px
 * floor.
 *
 * The wrapper exists so the granted-permissions line can sit OUTSIDE the
 * `<details>`: HTML hides every child but `<summary>` when closed, and a
 * collapsed disclosure that says nothing about what is granted forces a tap to
 * learn anything. Same reason `.plan-revision-panel__summary-subline` is a
 * sibling rather than a child. */
.smart-task-edit__permissions-block {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

.smart-task-edit__permissions {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

.smart-task-edit__permissions-summary {
  /* Chevron stays right-anchored while the title takes the remaining width —
   * matches `.plan-revision-panel__summary`. */
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--spacing-1) var(--spacing-2);
  min-height: var(--pels-touch-target-min);
  padding: var(--spacing-1) 0;
  cursor: pointer;
  list-style: none;
}

.smart-task-edit__permissions-summary::-webkit-details-marker {
  display: none;
}

/* At-rest readout of what the draft currently grants, in the canonical
 * budget → limit → pause order the read-only row and the rescue sheet use.
 * Exists only for the CLOSED state — with the rows on screen it would restate
 * their labels verbatim under them, reading as an orphan fourth label — so the
 * sibling combinator retires it the moment the disclosure opens. */
.smart-task-edit__permissions-granted {
  margin: 0;
  color: var(--pels-text-supporting-color);
  font-size: var(--font-size-sm);
}

.smart-task-edit__permissions[open] ~ .smart-task-edit__permissions-granted {
  display: none;
}

.smart-task-edit__preview {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

.smart-task-edit__previewing,
.smart-task-edit__preview-when,
.smart-task-edit__preview-cost {
  margin: 0;
  color: var(--pels-text-supporting-color);
  font-size: var(--pels-text-supporting-font-size);
}

.smart-task-edit__error {
  margin: 0;
  color: var(--color-state-negative-text);
  font-size: var(--font-size-sm);
}

.smart-task-edit__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-2);
}

/* A preview's verdict line, coloured by `resolveSmartTaskPreviewTone`, the one
 * mapping the create and edit previews share. */
:is(.smart-task-edit, .smart-task-create) .pels-text-status-line[data-tone='good'] {
  color: var(--color-state-positive-text);
}

:is(.smart-task-edit, .smart-task-create) .pels-text-status-line[data-tone='warn'] {
  color: var(--color-state-warning-text);
}

:is(.smart-task-edit, .smart-task-create) .pels-text-status-line[data-tone='alert'] {
  color: var(--color-state-negative-text);
}

:is(.smart-task-edit, .smart-task-create) .pels-text-status-line[data-tone='info'] {
  color: var(--color-state-info-text);
}

/* "When the task is done": who starts the device after the task, with a link
 * to its settings. A card on the task page (the surface card's grid), a block
 * in the create preview. The link keeps its own width at the start edge. */
.smart-task-after-task > :is(h2, p),
.smart-task-create__after-task > p,
.smart-task-create__preview > p {
  margin: 0;
}

.smart-task-after-task > md-text-button {
  justify-self: start;
}

.smart-task-create__after-task {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-1);
}

/* The rule under Power-limit control is its own sentence, not more hint text:
 * a little air above it keeps it from reading as lines 6-7 of the hint. */
#device-detail-start-rule {
  margin-top: var(--spacing-1);
}

.deadlines-list-create-action {
  display: flex;
  justify-content: flex-end;
}

.smart-task-create,
.smart-task-create__picker,
.smart-task-create__form,
.smart-task-create__preview {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

.smart-task-create__bar,
.smart-task-create__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-2);
}

.smart-task-create__actions {
  flex-direction: column;
  align-items: stretch;
}

.smart-task-create__actions > md-filled-button {
  width: 100%;
}

.smart-task-create__device {
  width: 100%;
  min-height: var(--pels-touch-target-min);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.smart-task-create__device > span {
  color: var(--pels-text-supporting-color);
}

/* The device page's Smart task box, last on the page: a title, one line, and
 * a quiet text action at the start edge. */
.device-detail-smart-task {
  gap: var(--spacing-2);
  height: max-content;
}

.device-detail-smart-task > h3,
.device-detail-smart-task > p {
  margin: 0;
}

.device-detail-smart-task > md-text-button {
  justify-self: start;
}

/* Recourse action row beneath the cannot-finish meta line. The 48 px floor
 * matches the touch-target token used elsewhere on PELS — see
 * `feedback_design_tokens.md` plus the upstream "Lift `.pels-icon-toggle` and
 * `.pels-device-card__detail-button` to 48 px" item. The action is rendered as
 * a `<button type="button">` rather than a `<md-text-button>` so the click
 * dispatcher in `deadlinePlanMount.ts` sees the native event regardless of
 * shadow-DOM composition. */
.plan-hero__recourse {
  margin-top: var(--spacing-3);
}

/* `.pels-button` — canonical native `<button>` primitive for PELS settings UI.
 * Phase 2 of the primitive-unification work (batch 10): the deadline-plan
 * cannot-finish recourse CTA and the history-detail review-device CTA both
 * render a native `<button type="button">` (not `<md-text-button>`) so the
 * delegated click handler in `deadlinePlanMount.ts` can see the event without
 * crossing the MD Web shadow boundary. Before this rebind they each carried
 * a per-page recourse-button class (scoped + doubled under
 * `#deadline-plan-panel`); now they share one shell.
 *
 * Why Option A (MD Web wrappers for MD Web buttons, `.pels-button` only for
 * native buttons): MD Web's `<md-text-button>` etc. already ship M3-correct
 * focus rings, state-layer ripples, ARIA, and the 48 px touch-target floor —
 * reinventing them in a custom primitive would lose accessibility for no
 * stylistic gain. The native `.pels-button` only exists for the small set of
 * actions that need a real `<button>` (event delegation, no shadow DOM).
 *
 * Shape (padding, border, background, radius, touch-target floor, focus
 * ring, disabled state) lives on the bare `.pels-button` selector so the
 * class carries its full visual contract on any surface. The doubled-class
 * + panel-scoped block below repeats the same declarations to beat Homey's
 * host `button { ... }` stylesheet (and any `.homey-app button` scoped rule)
 * without `!important`, matching the segmented-control cascade strategy.
 * Kept byte-equivalent so future shape edits stay in sync; new consumer
 * panels can add their own scoped duplicate or move to a shared
 * `.<panel-id> button.pels-button.pels-button` list. */
.pels-button {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-height: var(--pels-touch-target-min);
  height: auto;
  margin: 0;
  padding: var(--spacing-2) var(--spacing-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--pels-surface-outline);
  background: var(--pels-surface-container-high);
  color: var(--pels-text-primary);
  font-family: inherit;
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.pels-button:hover,
.pels-button:focus-visible {
  background: var(--pels-surface-container-highest);
  border-color: var(--accent);
}

.pels-button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.pels-button:disabled {
  opacity: var(--opacity-disabled);
  cursor: not-allowed;
}

#deadline-plan-panel button.pels-button.pels-button {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  min-height: var(--pels-touch-target-min);
  height: auto;
  margin: 0;
  padding: var(--spacing-2) var(--spacing-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--pels-surface-outline);
  background: var(--pels-surface-container-high);
  color: var(--pels-text-primary);
  font-family: inherit;
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

#deadline-plan-panel button.pels-button.pels-button:hover,
#deadline-plan-panel button.pels-button.pels-button:focus-visible {
  background: var(--pels-surface-container-highest);
  border-color: var(--accent);
}

#deadline-plan-panel button.pels-button.pels-button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

#deadline-plan-panel button.pels-button.pels-button:disabled {
  opacity: var(--opacity-disabled);
  cursor: not-allowed;
}

/* Chart toggle on the Succeeded receipt shape ("View details" / "Hide
 * details"). Lives next to the H2 chart-card title; rebound onto the
 * canonical `.pels-button` primitive (batch 12 follow-up to the batch 10
 * primitive consolidation) but keeps its quiet ghost identity through
 * this decorator chain so it doesn't visually compete with the H2.
 * Going filled (the canonical visual) would have promoted a disclosure
 * toggle to look like a primary CTA — wrong information hierarchy for
 * the surface. The label reads "details" not "schedule" so the toggle
 * doesn't overload the deadline-plan "schedule" vocabulary (v2.7.2/PR10).
 *
 * The decorator MUST live below the `.pels-button` cascade above
 * (including the `#deadline-plan-panel button.pels-button.pels-button`
 * panel-scoped block): the panel-scoped chart-toggle selector below
 * carries the same specificity `(1,2,1)` as the panel-scoped
 * `.pels-button` block, so source order is the tie-breaker. The bare
 * doubled-class selector matches the `(0,2,0)` decorator pattern used
 * elsewhere in the file and beats the unscoped `.pels-button {…}`
 * cascade `(0,1,0)` without needing `!important`.
 *
 * Properties NOT redeclared here ride the canonical `.pels-button`:
 * min-height (touch-target floor), border-radius, color, font-family,
 * cursor, transition, disabled state (opacity + not-allowed cursor),
 * appearance reset. Properties redeclared override the filled visuals
 * back to the ghost identity: transparent background, narrower padding,
 * inherited font-weight (so the button label doesn't compete with the
 * H2), and an info-color focus outline (the canonical accent green
 * would clash on the ghost ground).
 *
 * width / flex: the narrow-viewport `.pels-button { width: 100% }` rule
 * (max-width: 480px, meant for the full-width recourse CTA) would also
 * stretch this toggle to fill the `.budget-card-header` flex row, leaving
 * the sibling H2 title 0 px wide. The decorator's higher specificity
 * ((0,2,0) bare / (1,2,1) panel-scoped, both > the media-query rule's
 * (0,1,0)) holds the toggle to its intrinsic content width so the title
 * keeps its flex-grow space. */
.plan-history-detail__chart-toggle.plan-history-detail__chart-toggle {
  align-self: flex-start;
  width: auto;
  flex: 0 0 auto;
  padding: var(--spacing-1) var(--spacing-3);
  border-color: var(--color-border-default);
  background: transparent;
  font-weight: inherit;
}

.plan-history-detail__chart-toggle.plan-history-detail__chart-toggle:hover,
.plan-history-detail__chart-toggle.plan-history-detail__chart-toggle:focus-visible {
  background: color-mix(in srgb, var(--pels-surface-container) 92%, var(--color-base-white) 8%);
  border-color: var(--color-border-default);
}

.plan-history-detail__chart-toggle.plan-history-detail__chart-toggle:focus-visible {
  outline: 2px solid var(--color-state-info-border);
  outline-offset: 2px;
}

#deadline-plan-panel button.plan-history-detail__chart-toggle.plan-history-detail__chart-toggle {
  align-self: flex-start;
  width: auto;
  flex: 0 0 auto;
  padding: var(--spacing-1) var(--spacing-3);
  border-color: var(--color-border-default);
  background: transparent;
  font-weight: inherit;
}

#deadline-plan-panel button.plan-history-detail__chart-toggle.plan-history-detail__chart-toggle:hover,
#deadline-plan-panel button.plan-history-detail__chart-toggle.plan-history-detail__chart-toggle:focus-visible {
  background: color-mix(in srgb, var(--pels-surface-container) 92%, var(--color-base-white) 8%);
  border-color: var(--color-border-default);
}

#deadline-plan-panel button.plan-history-detail__chart-toggle.plan-history-detail__chart-toggle:focus-visible {
  outline: 2px solid var(--color-state-info-border);
  outline-offset: 2px;
}

.plan-hero__energy-support {
  color: var(--pels-text-supporting-color);
  font-size: var(--pels-text-supporting-font-size);
  line-height: var(--pels-text-supporting-font-line-height);
}

.plan-hero__decision {
  margin: calc(var(--spacing-4) + var(--spacing-1)) 0 0;
  color: var(--pels-text-status-line-color);
  font-size: var(--pels-text-status-line-font-size);
  line-height: var(--pels-text-status-line-font-line-height);
}

.plan-hero__decision[data-positive] {
  color: var(--pels-text-status-line-positive-color);
}


/* ── Overview smart-task row ──────────────────────────────────────────────────
   One slim tappable pointer row between the hero and the device cards
   (tap → Smart tasks). Exception-first: the ladder + copy live in
   shared-domain/overviewSmartTaskRow.ts. The status dot reinforces the tone
   but never carries it alone — the status word is in the text. */
/* Double class beats the LATER `.pels-surface-card { display: grid }` base
   rule (equal single-class specificity would lose the cascade). */
.pels-surface-card.plan-smart-task-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  inline-size: 100%;
  min-block-size: var(--pels-touch-target-min);
  padding: var(--spacing-3) var(--spacing-4);
  /* Match the 8 px stack rhythm of `.plan-cards` on both sides: without the
     block-start margin the row sits flush against the hero's bottom edge
     (0 px) while every other card pair on the page gets 8 px. Both margins
     collapse against their neighbours' (hero: none, `.plan-cards`:
     margin-top --spacing-2), so the rendered gap is 8 px above and below. */
  margin-block: var(--spacing-2);
  text-align: start;
  font-size: var(--font-size-sm);
  color: var(--text);
  cursor: pointer;
}

.plan-smart-task-row__dot {
  inline-size: var(--spacing-2);
  block-size: var(--spacing-2);
  border-radius: var(--radius-full);
  flex-shrink: 0;
  background: var(--pels-status-positive, var(--accent));
}

.plan-smart-task-row[data-tone="warn"] .plan-smart-task-row__dot {
  background: var(--pels-status-warning);
}

.plan-smart-task-row[data-tone="alert"] .plan-smart-task-row__dot {
  background: var(--danger);
}

/* The device name may ellipsize at 320 px; the status text never truncates. */
.plan-smart-task-row__text {
  flex: 1;
  display: flex;
  align-items: baseline;
  min-inline-size: 0;
  white-space: nowrap;
}

.plan-smart-task-row__device {
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: var(--font-weight-semibold);
}

/* Preact renders the leading " — " inside the status span; `pre` keeps that
   significant leading space from collapsing, and the status text never
   truncates (the device name is the ellipsis victim). */
.plan-smart-task-row__status {
  flex-shrink: 0;
  white-space: pre;
}

.plan-smart-task-row[data-tone="warn"] .plan-smart-task-row__device,
.plan-smart-task-row[data-tone="warn"] .plan-smart-task-row__status {
  color: var(--pels-status-on-warning);
}

.plan-smart-task-row[data-tone="alert"] .plan-smart-task-row__device,
.plan-smart-task-row[data-tone="alert"] .plan-smart-task-row__status {
  color: var(--danger);
}

.plan-smart-task-row[data-tone="muted"] .plan-smart-task-row__text {
  color: var(--text-secondary);
}

.plan-smart-task-row__chevron {
  color: var(--text-secondary);
  font-size: var(--font-size-lg);
  line-height: 1;
  flex-shrink: 0;
}

.plan-cards {
  display: grid;
  gap: var(--spacing-2);
  margin-top: var(--spacing-2);
}

/* ===========================================================================
 * Canonical primitives — rule of thumb
 * ---------------------------------------------------------------------------
 * Several PELS UI primitives (`.pels-surface-card`, `.pels-hero` / `.plan-
 * hero`, `.plan-chip`, `.pels-button`, `.segmented`) own a single visual
 * contract — padding, gap, border, radius, surface tier, focus ring, touch
 * target — that every consumer surface inherits. Per-page classes layered on
 * top (e.g. `.deadline-list-card`, `.plan-history-card`, `.plan-history-card
 * --link`, `.detail-diagnostics-card`, `.budget-redesign-card`) are
 * DECORATORS: they only override what their role specifically needs
 * (typically layout / hover / role-specific decoration) and never redeclare
 * the contract their canonical primitive carries.
 *
 * Two cascade idioms recur on top of this:
 *  - Doubled-class selectors (`.X.X`, specificity `(0,2,0)`) beat the
 *    unscoped canonical without `!important` when a decorator needs to
 *    override a canonical declaration — see `.plan-history-detail__chart-
 *    toggle.plan-history-detail__chart-toggle` for the pattern.
 *  - Panel-scoped overrides (`#some-panel .selector`, specificity `(1,1,0)`)
 *    win against the bare canonical but tie with other panel-scoped rules,
 *    so source order is the tie-breaker — put the decorator AFTER the
 *    canonical cascade in the file.
 *
 * Tonal state on the card / hero / chip primitives uses one shared API
 * (`data-tone="good|warn|alert|info|muted"`); the legacy `.plan-card[data-
 * state-kind="…"]` aliases on the device-row card resolve onto the same
 * `--color-state-*-bg/-border` cascade so existing markup keeps working.
 *
 * The canonical-primitives region begins here with `.pels-surface-card`;
 * `.pels-hero` / `.plan-chip` / `.pels-button` live earlier in the file
 * with their own per-primitive notes. Per-decorator blocks under each
 * primitive carry only a one-line pointer to this preamble plus any
 * decorator-specific contract details.
 * ===========================================================================
 */

/* ── Canonical typography utilities ──────────────────────────────────────────
 * One class per --pels-text-* role bundle so pages stop hand-rolling
 * font-size/-weight/-line-height. Each class applies only the sub-tokens that
 * exist for its role. See notes/settings-ui-consolidation.md. */
.pels-text-display {
  color: var(--pels-text-display-color);
  font-size: var(--pels-text-display-font-size);
  font-weight: var(--pels-text-display-font-weight);
  line-height: var(--pels-text-display-font-line-height);
}
.pels-text-headline {
  color: var(--pels-text-headline-color);
  font-size: var(--pels-text-headline-font-size);
  font-weight: var(--pels-text-headline-font-weight);
  line-height: var(--pels-text-headline-font-line-height);
}
.pels-text-section-headline {
  color: var(--pels-text-section-headline-color);
  font-size: var(--pels-text-section-headline-font-size);
  font-weight: var(--pels-text-section-headline-font-weight);
  line-height: var(--pels-text-section-headline-font-line-height);
}
.pels-text-title {
  color: var(--pels-text-title-color);
  font-size: var(--pels-text-title-font-size);
  font-weight: var(--pels-text-title-font-weight);
  line-height: var(--pels-text-title-font-line-height);
}
.pels-text-card-title {
  color: var(--pels-text-card-title-color);
  font-size: var(--pels-text-card-title-font-size);
  font-weight: var(--pels-text-card-title-font-weight);
  line-height: var(--pels-text-card-title-font-line-height);
}
.pels-text-section-label {
  color: var(--pels-text-section-label-color);
  font-size: var(--pels-text-section-label-font-size);
  font-weight: var(--pels-text-section-label-font-weight);
  line-height: var(--pels-text-section-label-font-line-height);
}
.pels-text-answer {
  color: var(--pels-text-answer-color);
  font-size: var(--pels-text-answer-font-size);
  font-weight: var(--pels-text-answer-font-weight);
  line-height: var(--pels-text-answer-font-line-height);
}
.pels-text-body {
  color: var(--pels-text-body-color);
  font-size: var(--pels-text-body-font-size);
  font-weight: var(--pels-text-body-font-weight);
  line-height: var(--pels-text-body-font-line-height);
}
.pels-text-supporting {
  color: var(--pels-text-supporting-color);
  font-size: var(--pels-text-supporting-font-size);
  line-height: var(--pels-text-supporting-font-line-height);
}
.pels-text-card-supporting {
  color: var(--pels-text-card-supporting-color);
  font-size: var(--pels-text-card-supporting-font-size);
  line-height: var(--pels-text-card-supporting-font-line-height);
}
.pels-text-status-line {
  color: var(--pels-text-status-line-color);
  font-size: var(--pels-text-status-line-font-size);
  line-height: var(--pels-text-status-line-font-line-height);
}
.pels-text-caption {
  color: var(--pels-text-caption-color);
  font-size: var(--pels-text-caption-font-size);
  font-weight: var(--pels-text-caption-font-weight);
  line-height: var(--pels-text-caption-font-line-height);
}
/* Semantic shared class: a metric / field / step / cell label. One role
   (caption) for every "label" of this kind across pages — see
   notes/settings-ui-semantic-map.md. Page blocks keep only structural props. */
.metric-label {
  color: var(--pels-text-caption-color);
  font-size: var(--pels-text-caption-font-size);
  font-weight: var(--pels-text-caption-font-weight);
  line-height: var(--pels-text-caption-font-line-height);
}
/* Semantic shared class: a device / entity name as the primary identifier in a
   list row. The answer role (md + semibold + primary): a prominent list identifier — it is the row's
   main label, not a secondary one. (Device names used as a secondary reference,
   e.g. plan-history, stay on their muted role.) See settings-ui-semantic-map. */
.entity-name {
  color: var(--pels-text-answer-color);
  font-size: var(--pels-text-answer-font-size);
  font-weight: var(--pels-text-answer-font-weight);
  line-height: var(--pels-text-answer-font-line-height);
}
.pels-text-settings-label {
  color: var(--pels-text-settings-label-color);
  font-weight: var(--pels-text-settings-label-font-weight);
}
.pels-text-settings-trailing {
  color: var(--pels-text-settings-trailing-color);
  font-weight: var(--pels-text-settings-trailing-font-weight);
}

/* `.pels-surface-card` is the canonical card primitive. Every card-shaped
   surface across the settings UI (device cards, smart-task list cards, past-
   plan history cards, device-detail diagnostics cards, budget redesign cards,
   usage cards) walks this shell — padding, gap, radius, border, surface
   tier, isolation, and the default `--md-elevation-level: 1` are owned here.
   Per-page decorators (`.plan-card`, `.deadline-list-card`, `.plan-history-
   card`, `.usage-card`, `.budget-redesign-card`, `.budget-chart-card`,
   `.budget-confidence-card`, `.deadline-horizon-card`, `.pels-device-card`,
   etc.) only override layout / hover / role-specific decoration on top of
   this surface — see the canonical-primitives preamble above for the
   primitive / decorator rule of thumb. */
.pels-surface-card {
  --md-elevation-level: 1;
  --md-ripple-hover-color: var(--pels-status-good);
  --md-ripple-pressed-color: var(--pels-status-good);
  --md-ripple-hover-opacity: 0.06;
  --md-ripple-pressed-opacity: 0.1;
  position: relative;
  display: grid;
  gap: var(--pels-card-gap);
  padding: var(--pels-card-padding);
  overflow: hidden;
  isolation: isolate;
  border: 1px solid var(--pels-surface-outline);
  border-radius: var(--pels-card-radius);
  background: var(--pels-surface-container-lowest);
  transition: --md-elevation-level var(--transition-emphasized);
}

.pels-surface-card > md-elevation,
.pels-surface-card > md-ripple {
  border-radius: inherit;
}

/* The card-level `md-elevation` never renders a visible shadow: the shadow
 * pseudos paint OUTSIDE their `inset: 0` box, and the primitive's
 * `overflow: hidden` clips that region entirely (verified empirically —
 * even a forced `--md-elevation-level: 5` paints nothing outside or inside
 * the card). Its only observable output is a compositing leak: Chromium
 * rasterizes the clipped shadow layer at fractional device-pixel offsets
 * and tints card interiors ~2/255 darker in invalidation-dependent bands,
 * which reads as faint empty rectangles inside card headers (Usage "Today
 * so far" at 480 px). Suppress the paint; the element stays in the
 * templates so a future non-clipped elevation treatment can re-enable it.
 * The `--md-elevation-level` hover/active chain further down (the
 * `data-interactive` / `.plan-card` lift rules) is likewise intentionally
 * retained-but-inert behind this `display: none` for the same reason. */
.pels-surface-card > md-elevation {
  display: none;
}

.pels-surface-card > md-ripple {
  z-index: 0;
}

/* Canonical tonal API — shared with `.plan-hero[data-tone=…]` and
   `.plan-chip[data-tone=…]`. M3 status pattern (TODO M3 alignment items
   #3/#7 "reduce orange weight", refined 2026-05-30): the tonal card stays a
   PLAIN neutral surface with a neutral hairline on all four sides — no
   saturated tint and no role-coloured left state-rail (the leading colour bar
   read as the Material-2 / iOS accent-list idiom). The state is carried inline
   by the consumer's tone chip (`.plan-chip--{tone}` in the card header), so
   `good` / `warn` / `alert` / `info` need no per-tone surface override and
   flow through the base `.pels-surface-card`. */

/* Deliberate NEUTRAL tonal container — Abandoned / Replaced / Unknown history
   outcomes (resolved to `muted` by `getPlanHistoryOutcomeCardTone`). The
   positive/warning tones above lift the base card surface with a faint chroma
   wash; the neutral tone is the same *family* of toned container (one step up
   from the base `--color-surface-1` card onto `--pels-surface-container-low`,
   with a slightly stronger neutral edge) but carries NO chroma, so an Abandoned
   row reads as the most muted member of the tonal-card set rather than an
   unstyled base card sitting bare among styled siblings (PR2 spec §7,
   m3-critic P0). Must stay distinct from BOTH the page background and the base
   `.pels-surface-card` tier above. */
.pels-surface-card[data-tone="muted"] {
  background: var(--pels-surface-container-low);
  border-color: var(--color-border-medium);
}

.plan-card {
  padding-left: calc(var(--spacing-4) + (var(--chip-border-width) * 2));
  /* Reserve a uniform vertical rhythm across the device-card stack regardless
     of which conditional rows (temperature line, reason line, stepped control,
     ev state) are present. Peer card heights cluster around 4× --spacing-8
     (128 px) in the live walk, so anchor the floor there to avoid the 99-px
     vs 163-px jaggies called out in the 2026-05-16 audit. Spec: TODO #7. */
  min-height: calc(var(--spacing-8) * 4);
}

/* Hover / focus / active elevation lift opts in via `data-interactive` on
   the canonical primitive (matches the M3 "clickable card" affordance).
   `.plan-card` carries it implicitly through its consumer's `role="button"`
   + `tabindex="0"` markup; new clickable cards (deadline list links, past-
   plan history links) set `data-interactive` on the host so they inherit
   the same hover / focus elevation contract from one source. */
.pels-surface-card[data-interactive]:hover,
.pels-surface-card[data-interactive]:focus-visible,
.plan-card:hover,
.plan-card:focus-visible {
  --md-elevation-level: 3;
}

.pels-surface-card[data-interactive]:focus-visible,
.plan-card:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.pels-surface-card[data-interactive]:active,
.plan-card:active {
  --md-elevation-level: 2;
}

/* Device-row card state (`.plan-card[data-state-kind="…"]`). M3 status
   pattern (TODO M3 alignment items #3/#7 "reduce orange weight", refined
   2026-05-30): the device card stays a PLAIN neutral surface — no
   role-coloured left state-rail. `held` / `resuming` carry their state inline
   through role-toned status text (reason / status-line / output-state, see
   the colour rules below), which is the card's chip-equivalent since the row
   has no header chip. The `unavailable` opacity shading + the `unknown`
   opacity-only fade are plan-card-specific device-state semantics (not a
   tone) and stay. */
.plan-card[data-state-kind="unavailable"] {
  opacity: 0.78;
}

.plan-card--dim {
  opacity: 0.74;
}

.plan-card__header {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  justify-content: space-between;
  /* Allow the chip block to drop onto a second line when the title + chips
     can't share one row at narrow widths. A long unbreakable device title
     (e.g. "Varmtvannsbereder") plus a wide action chip ("Exempt from budget")
     otherwise pushes the chips past the card's right edge at 320px — the chips
     can't fold within their own block because each `.plan-chip` is
     `white-space: nowrap`. Wrapping the header keeps the row honest. */
  flex-wrap: wrap;
}

.plan-card__title {
  margin: 0;
  min-width: 0;
  color: var(--pels-text-card-title-color);
  font-size: var(--pels-text-card-title-font-size);
  font-weight: var(--pels-text-card-title-font-weight);
  line-height: var(--pels-text-card-title-font-line-height);
}

.pels-card-supporting {
  margin: 0;
  color: var(--pels-text-card-supporting-color);
  font-size: var(--pels-text-card-supporting-font-size);
  line-height: var(--pels-text-card-supporting-font-line-height);
}

.plan-card__title-wrap {
  min-width: 0;
  flex: 1 1 auto;
}

.plan-card__retry {
  margin-top: var(--spacing-2);
  align-self: flex-start;
}

.plan-card__chips {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  flex-wrap: wrap;
  /* Let the chip block shrink within the header flex row so its own
     `flex-wrap` can fold chips onto a second line at narrow widths. Without a
     zero min-width, a long unbreakable device title (`flex: 1 1 auto`
     title-wrap) starves the chip block of space and a wide action chip (e.g.
     "Exempt from budget") spills past the card's right edge at 320px. The
     block still won't grow (`flex-grow: 0`), so a calm card keeps its chips
     hugged to the right. */
  min-width: 0;
  flex: 0 1 auto;
}

.plan-state-chip-wrap {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.plan-state-chip__timer {
  --md-circular-progress-size: 18px;
  --md-circular-progress-active-indicator-width: 12;
  --md-circular-progress-active-indicator-color: var(--pels-status-good);
  flex: none;
}

.plan-state-chip__timer[data-tone="held"] {
  --md-circular-progress-active-indicator-color: var(--pels-status-limited);
}

.plan-state-chip__timer[data-tone="resuming"] {
  --md-circular-progress-active-indicator-color: var(--color-role-good);
}

.plan-state-chip__timer[data-tone="muted"] {
  --md-circular-progress-active-indicator-color: var(--text-secondary);
}

/* ── Loading skeleton primitives ───────────────────────────────────────────────
 * Used as first-paint placeholders for the Overview panel before the bootstrap
 * fetch resolves. Other four panels (Budget, Usage, Smart tasks, Settings) can
 * wire the same primitive in a follow-up PR.
 * Replaces flat `Awaiting data…` text with a token-driven M3 shimmer so the
 * shape of the panel is visible while data loads. The `--hero` modifier sits
 * inside `.plan-hero` placeholders; `--card` sits inside surface cards.
 * Honors `prefers-reduced-motion: reduce` (no animation).
 */
.pels-skeleton {
  display: block;
  width: 100%;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--pels-surface-container-high) 70%, transparent);
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--pels-on-surface) 8%, transparent) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  background-position: -150% 0;
  /* Keep the placeholder readable to screen readers via aria-busy on the
   * parent; the block itself is decorative. */
}

@media (prefers-reduced-motion: no-preference) {
  .pels-skeleton {
    animation: pels-skeleton-shimmer 1.4s ease-in-out infinite;
  }
}

@keyframes pels-skeleton-shimmer {
  0% { background-position: -150% 0; }
  100% { background-position: 150% 0; }
}

.pels-skeleton--hero {
  height: 96px;
}

.pels-skeleton--headline {
  height: 28px;
  width: 70%;
}

.pels-skeleton--subline {
  height: 16px;
  width: 50%;
}

.pels-skeleton--card {
  height: 72px;
  border-radius: var(--radius-lg);
}

.pels-skeleton-stack {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

.plan-card__reason {
  position: relative;
  z-index: 1;
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  line-height: var(--font-line-height-relaxed);
}

/* ── Temperature card (binary thermostats, heat pumps, water heaters) ────────── */

.plan-card--temperature .plan-card__temp-line {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  line-height: var(--font-line-height-cozy);
  position: relative;
  z-index: 1;
  /* Reserve the line's vertical space even when temperatureLine is null so
     every temperature card renders at the same height. Tracks the rule's own
     font-size × line-height so changes to `cozy` cannot un-stick the two. */
  min-height: calc(var(--font-size-sm) * var(--font-line-height-cozy));
}

.plan-card--temperature .plan-card__temp-reason {
  margin: 0;
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  line-height: var(--font-line-height-cozy);
  position: relative;
  z-index: 1;
}

/* Idle-classification copy carries its own tone through the shared reason
   slot (the retired `__idle-line--warning` element's role): an unresponsive
   device's "Not drawing power (…)" keeps its warning emphasis. */
.plan-card--temperature .plan-card__temp-reason[data-tone="warning"] {
  color: var(--pels-status-on-warning);
}

/* ── Stepped card (Connected 300 and other stepped load devices) ────────────── */

.plan-card--stepped .plan-card__stepped-body {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  position: relative;
  z-index: 1;
}

.plan-card__state-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--spacing-2);
}

.plan-card__state-label {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  color: var(--text);
}

.plan-card__state-power {
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  white-space: nowrap;
  flex-shrink: 0;
}

/* Live measured draw is real, present-tense evidence — render it at full body
 * emphasis. The expected ("~1.0 kW when active") reading is a projection, so it
 * stays at the dimmer secondary tone (the base rule above). A reported draw on a
 * paused device is an unexpected conflict, so it takes the warning tone. */
.plan-card__state-power[data-variant="live"] {
  color: var(--text);
  font-weight: var(--font-weight-medium);
}

.plan-card__state-power[data-variant="reported"] {
  color: var(--pels-status-warning);
}

.plan-card__secondary-line {
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  line-height: var(--font-line-height-cozy);
}

/* Typography from `.pels-text-status-line` (markup); structural props only. */
.plan-card__status-line {
  margin: 0;
}

.plan-card__step-rail {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  padding-top: var(--spacing-1);
  padding-bottom: var(--spacing-1);
  /* The rail is a status indicator, not an interactive control. The parent
   * `.device-row.clickable` sets `cursor: pointer` for the entire card; reset
   * it back to default on the rail so individual stops/labels don't promise
   * tap-to-select behavior they do not implement. A real step-selector lives
   * in the device-detail drawer. */
  cursor: default;
}

/* Only the two endpoint labels render (at every width): an 11-step charger's
 * intermediate ampere labels collided and read as slider tick-stops. */
.plan-card__step-labels {
  display: flex;
  justify-content: space-between;
  margin-inline: var(--spacing-2);
}

/* Typography from `.metric-label` (markup); structural overrides only. */
.plan-card__step-label {
  white-space: nowrap;
  line-height: var(--font-line-height-flat);
}

/* Thin segmented track. Discrete steps are conveyed by faint tick separators
 * (a repeating gradient keyed to `--step-count`) — not by stop dots or a thumb,
 * which read as a draggable slider. The fill reaches the current step. */
.plan-card__step-track {
  position: relative;
  height: 4px;
  background: color-mix(in srgb, var(--text) 12%, transparent);
  border-radius: var(--radius-xs);
  margin: var(--spacing-2) var(--spacing-3);
}

.plan-card__step-track::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    to right,
    var(--panel) 0,
    var(--panel) 1px,
    transparent 1px,
    transparent calc(100% / max(calc(var(--step-count, 1) - 1), 1))
  );
}

.plan-card__step-filled {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-xs);
}

/* At the lowest step the fill is 0% wide and the track reads as uniform grey,
 * hiding the current position. Guarantee a hairline stub whenever a real step
 * is known so the leftmost level still reads as "here". An unknown level
 * (no data-position) keeps a genuinely empty track. */
.plan-card__step-filled[data-position="true"] {
  min-width: 3px;
}

@media (max-width: 480px) {
  .tabs {
    gap: var(--spacing-1);
    --md-primary-tab-container-height: var(--pels-shell-nav-tab-height-compact);
    --md-primary-tab-label-text-size: var(--pels-shell-nav-tab-label-size-compact);
  }

  .tab {
    text-align: center;
    justify-content: center;
    padding-inline: var(--pels-shell-nav-tab-padding-inline-compact);
    font-size: var(--pels-shell-nav-tab-label-size-compact);
    min-width: 0;
  }

  .plan-hero,
  .pels-hero {
    gap: var(--pels-hero-gap);
    padding: var(--pels-hero-padding);
  }

  .plan-card__title {
    font-size: var(--font-size-md);
  }

  .plan-hero__legend {
    font-size: var(--font-size-xs);
  }

  /* Canonical `.pels-button` shell — stretch to full-width inside the
   * recourse row at narrow viewports so the CTA reads as a tappable bar
   * instead of a left-aligned token. The `.plan-hero__recourse` parent
   * keeps the row layout; the button just fills it. */
  .pels-button {
    width: 100%;
  }

  .plan-cards {
    margin-top: var(--spacing-2);
  }

  .plan-card {
    padding: var(--spacing-3);
  }

  #overview-panel,
  #budget-panel {
    padding: 0;
  }

  .plan-card__header {
    align-items: flex-start;
  }
}

/* Narrow breakpoint: keep all five destinations on one row at ~320 px, each
   label on a SINGLE line at one shared baseline. Instead of the equal-width
   flex split (which starved "Smart tasks" into a two-line wrap), tabs size to
   their label (`flex: 1 1 auto`) so the longer label takes the room it needs
   and the shorter ones give it up — at 320 px all five labels fit with room
   to spare (measured: rightmost edge ≈ 311 px). Touch target stays ≥ 48 px
   via the tab container token. */
@media (max-width: 360px) {
  #shell-nav[data-shell-nav-compact] {
    gap: 0;
    padding: 2px;
  }

  #shell-nav[data-shell-nav-compact] > md-primary-tab {
    flex: 1 1 auto;
  }

  #shell-nav[data-shell-nav-compact] .tab {
    padding-inline: var(--spacing-1);
    font-size: var(--font-size-xs);
    letter-spacing: -0.01em;
    white-space: nowrap;
    line-height: var(--font-line-height-flat);
  }

  #shell-nav[data-shell-nav-compact] > md-primary-tab:not(:last-child) {
    margin-inline-end: 0;
  }
}

/* `plan-state-chip` chip primitive removed 2026-05-17 (TODO #2). The device
   state chip now renders as `.plan-chip.plan-chip--{good|warn|limited|...}`
   so the Overview cards use a single chip family. The wrap (`.plan-state-chip-wrap`)
   and cooldown progress (`.plan-state-chip__timer`) selectors remain because
   they style the chip + progress-circle pair, not the chip primitive itself. */

.device-row:last-child {
  border-bottom: none;
}

/* Typography from `.entity-name` (markup); structural props only. */
.device-row__name {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  min-width: 0;
  margin-bottom: var(--spacing-1);
}

.usage-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-3);
  align-items: center;
  margin: var(--spacing-2) 0;
  font-size: var(--font-size-xs);
}

.usage-legend__item {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
}

.usage-legend__swatch {
  width: 12px;
  height: 8px;
  border-radius: var(--radius-sm);
  background: var(--accent);
  border: 1px solid var(--panel-border);
}

/*
 * Match the heatmap cell shape exactly so the legend reads as "this is what
 * that means" (TODO 573 — fixed v2.7.0). Heatmap cells in
 * `powerWeekChartEcharts.ts` use:
 *   background     = --pels-chart-unreliable-cell
 *   border (1 px)  = --pels-chart-heatmap-border
 *   border-radius  = var(--pels-chart-cell-radius)
 * Render the swatch as a 10x10 square (the cell is ~10 px tall at 240 px chart
 * height) with the same fill + border + radius. The radius token is
 * chart-scoped (not the generic `--radius-xs`) so changing the cell shape
 * here and in the renderer touches a single source of truth.
 */
.usage-legend__swatch--unreliable {
  width: 10px;
  height: 10px;
  border-radius: var(--pels-chart-cell-radius);
  background: var(--pels-chart-unreliable-cell);
  border: 1px solid var(--pels-chart-heatmap-border);
}

.power-meter {
  position: relative;
  width: 100%;
}

.mode-target-input {
  width: var(--temp-input-width);
  padding: var(--spacing-1) 28px var(--spacing-1) var(--spacing-2) !important;
  font-size: var(--font-size-sm);
  flex: 0 0 auto;
}

.priority-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  padding: var(--spacing-1) var(--spacing-2);
  text-align: center;
  margin-left: var(--spacing-1);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-bold);
  flex: 0 0 auto;
  background: var(--color-state-positive-bg);
  color: var(--color-state-positive-text);
  border: 1px solid var(--color-state-positive-border);
  border-radius: var(--radius-full);
}

.mode-row .device-row__name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-tight);
}

.device-row__title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Compact variant of the canonical `.plan-chip` primitive used by the
   device-list state chips (Unavailable / Budget exempt / Flow-backed).
   Only padding + line-height tighten — the chip primitive already lands
   `--chip-font-size` (xs) / `--chip-font-weight` (semibold). */
.device-row__state-chip {
  padding: 2px var(--spacing-2);
  line-height: var(--font-line-height-compact);
}

.panel.hidden {
  display: none;
}

/* Legacy `.chip` primitive removed 2026-05-24 (chip primitive consolidation).
   `.plan-chip` is now the single canonical chip across every settings-UI
   surface; the `chip--neutral` / `chip--ok` / `chip--boost` / `chip--alert`
   tonal variants map onto `plan-chip--muted` / `plan-chip--ok` /
   `plan-chip--warn` / `plan-chip--alert` respectively. `.chip[data-tooltip]`
   help cursor moved to `.plan-chip[data-tooltip]` above; `.chip strong` text
   colour moved to `.plan-chip strong`. */

.tippy-box {
  position: relative;
  outline: 0;
}

.tippy-box[data-state='hidden'] {
  opacity: 0;
}

.tippy-box[data-theme~='pels'] {
  background: var(--pels-surface-elevated);
  color: var(--text);
  border: 1px solid var(--color-border-medium);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-normal);
  z-index: 1000;
}

.tippy-box[data-theme~='pels'] .tippy-content {
  padding: var(--spacing-2) var(--spacing-3);
  white-space: pre-line;
}

.inline-actions {
  display: flex;
  gap: var(--spacing-2);
  flex-wrap: wrap;
  margin-top: var(--spacing-2);
}

.inline-actions md-filled-text-field {
  min-width: 140px;
}

.list__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-3);
  margin-top: var(--spacing-3);
  flex-wrap: wrap;
}

#overview-panel {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
  padding: 0;
}

#overview-panel > .list__footer {
  padding: var(--spacing-4);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
}

.toast {
  position: fixed;
  bottom: var(--spacing-4);
  left: 50%;
  background: var(--color-overlay-toast);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  padding: var(--spacing-3) var(--spacing-3);
  color: var(--text);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-fast), transform var(--transition-fast);
  transform: translate(-50%, var(--spacing-1));
  max-width: min(560px, calc(100vw - var(--spacing-4) * 2));
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  z-index: 20;
}

.toast.show {
  opacity: 1;
  pointer-events: auto;
  transform: translate(-50%, 0);
}

.toast[data-tone="ok"] {
  border-color: var(--accent);
}

.toast[data-tone="warn"] {
  background: var(--color-state-negative-bg);
  border-color: var(--color-state-negative-border);
  color: var(--color-state-negative-text);
}

.toast__message {
  flex: 1;
  min-width: 0;
}

.toast__action {
  background: transparent;
  border: none;
  padding: var(--spacing-1) var(--spacing-2);
  margin: calc(var(--spacing-1) * -1) calc(var(--spacing-1) * -1) calc(var(--spacing-1) * -1) 0;
  color: var(--accent);
  font: inherit;
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.toast__action:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

/* Toggle row for feature enable/disable */

/* Price tab styles */

#tibber-token-field a {
  color: var(--accent);
  text-decoration: none;
}

#tibber-token-field a:hover {
  text-decoration: underline;
}

#tibber-token-field a:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.section-title {
  margin: 0;
  font-size: var(--pels-text-title-font-size);
  font-weight: var(--pels-text-title-font-weight);
  line-height: var(--pels-text-title-font-line-height);
  color: var(--pels-text-title-color);
}

.section-hint {
  color: var(--muted);
  font-size: var(--font-size-xs);
}

@media (max-width: 319px) {
  .section-hint {
    display: none;
  }
}

/* Price summary section.
 * Radius and padding bound to the shared `.price-notice-info` / `.price-notice-warning`
 * family tokens — the previous 8 px radius drifted 2 px below the sibling notices'
 * `--radius-md` (10 px), so the rebind both removes the literal AND normalizes the
 * card-family corner shape. */

/* Inline contextual notices — same M3 status pattern as the tonal cards /
   heroes (TODO M3 alignment items #3/#7 "reduce orange weight"): a calm
   neutral surface + neutral hairline on three sides + a 4px role-coloured
   left state-rail. The notice text itself is already role-toned, so the rail
   completes the status read without a full role-coloured edge or tint. */
/* Railed info-notice primitive, generalized from the price surface exactly as
   `.pels-notice-warning` was from `.price-notice-warning`: the left info rail
   + contained surface carry the signal, so `.pels-notice-info` keeps body text
   in the normal tone; the original `.price-notice-info` alias retains its
   info-toned text + bottom margin for the existing price surfaces. */
.pels-notice-info,
.price-notice-info {
  font-style: normal;
  font-size: var(--font-size-sm);
  padding: var(--spacing-3);
  background: var(--pels-surface-container-low);
  border-width: 1px 1px 1px var(--spacing-1);
  border-style: solid;
  border-color: var(--color-border-default) var(--color-border-default)
    var(--color-border-default) var(--color-state-info-border);
  border-radius: var(--radius-md);
}

.pels-notice-info {
  color: var(--text);
}

/* Railed warning-notice primitive: the left warning rail + contained surface
   carry the signal. The generalized `.pels-notice-warning` keeps body text in
   the normal tone (no wall-of-yellow prose); the original `.price-notice-warning`
   alias below retains its warn-toned text + top margin for the existing price
   surfaces. */
.pels-notice-warning,
.price-notice-warning {
  font-style: normal;
  font-size: var(--font-size-sm);
  padding: var(--spacing-3);
  background: var(--pels-surface-container-low);
  border-width: 1px 1px 1px var(--spacing-1);
  border-style: solid;
  border-color: var(--color-border-default) var(--color-border-default)
    var(--color-border-default) var(--color-state-warning-border);
  border-radius: var(--radius-md);
}

.pels-notice-warning {
  color: var(--text);
}

/* Global banner for warnings/notices */
.banner {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-4);
  background: var(--pels-surface-container-highest);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
}

.banner[hidden] {
  display: none;
}

.banner--warning {
  background: var(--color-state-warning-bg);
  border: 1px solid var(--color-state-warning-border);
  color: var(--color-state-warning-text);
}

.banner__icon {
  flex-shrink: 0;
}

#dry-run-banner .banner__icon {
  color: var(--color-role-warn);
  font-size: var(--font-size-md);
}

.banner__text {
  flex: 1;
  /* A flex:1 item shrinks to its longest word before the wrapping row ever
     overflows, so a banner with an inline action would collapse the message
     into a word-per-line tower at 320 px instead of wrapping the button. The
     ch floor makes the row overflow first: the action drops to its own line
     and the message keeps a readable measure. (The nowrap simulation banner
     overrides this back to min-width: 0 below.) */
  min-width: 18ch;
}

#setup-recommendations-banner-root:empty {
  display: none;
}

.setup-recommendations-banner {
  margin-bottom: var(--spacing-3);
  background: var(--color-state-info-bg);
  border: 1px solid var(--color-state-info-border);
  color: var(--color-state-info-text);
}

.setup-recommendations-list {
  display: grid;
  gap: var(--spacing-3);
}

.setup-recommendation-card__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-2);
  flex-wrap: wrap;
}

.setup-recommendation-card__actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  flex-wrap: wrap;
}

.setup-recommendations-empty {
  margin: 0;
}

/* First-run setup path. Its rows share the Settings-hub row rules (joined to
   the `.settings-nav-card` selectors above) under their own class: they are not
   hub rows, and `.settings-nav-card[data-settings-target]` must keep meaning
   exactly one hub row. This block adds only what the hub does not have — a
   status icon tone, and a flat list inside a card that already carries the
   elevation. */
.setup-path {
  margin-block-end: var(--spacing-2);
}

.setup-path__steps {
  box-shadow: none;
}

.setup-path__icon[data-status="done"],
.setup-path__icon[data-status="next"] {
  color: var(--accent);
}

.setup-recommendations-loading {
  margin: var(--spacing-4) 0 0;
}

.setup-recommendations-dismissed {
  margin-top: var(--spacing-4);
}

/* Collapsible settings sections */
.settings-collapse {
  background: var(--panel);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  margin-bottom: var(--spacing-4);
}

.settings-collapse summary {
  cursor: pointer;
  padding: var(--spacing-4);
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-3);
}

.settings-collapse summary::-webkit-details-marker {
  display: none;
}

/* Shared chevron behavior for any disclosure that opts in via a Material
   `expand_more` SVG marked with the `disclosure-chevron` class. Applies to
   the 5 advanced settings disclosures and to the budget-confidence
   "What this means" disclosure. */
summary .disclosure-chevron {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
  transition: transform var(--transition-fast);
}

.settings-collapse summary .disclosure-chevron {
  color: var(--muted);
}

details[open] > summary .disclosure-chevron {
  transform: rotate(180deg);
}

.settings-collapse .collapse-content {
  padding: var(--spacing-4) var(--spacing-4) var(--spacing-4);
  border-top: 1px solid var(--panel-border);
}

.detail-diagnostics-card h4 {
  margin: 0 0 var(--spacing-3);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--text);
}

.settings-collapse .collapse-content h4 {
  margin: var(--spacing-4) 0 var(--spacing-3);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.settings-collapse .collapse-content h4:first-child {
  margin-top: var(--spacing-3);
}

/* ========================================
   Device Detail Panel (Slide-over)
   ======================================== */

.overlay {
  position: fixed;
  inset: 0;
  background: var(--color-overlay-backdrop);
  z-index: 1000;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-slow), visibility var(--transition-slow);
}

.overlay:not([hidden]) {
  opacity: 1;
  visibility: visible;
}

.slide-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  max-width: 480px;
  background: var(--pels-surface-container-high);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform var(--transition-panel);
  box-shadow: var(--shadow-panel);
}

.overlay:not([hidden]) .slide-panel {
  transform: translateX(0);
}

/* The slide panel's sticky chrome strip hosts the shared `.pels-appbar`
   back+title row (the device name is the bar title — the old boxed
   "DEVICE / name" hero card inside the scroll body is gone). Keeps its own
   surface + hairline because content scrolls beneath it. */
.slide-panel__header {
  padding: var(--spacing-1) var(--spacing-3);
  border-bottom: 1px solid var(--panel-border);
  background: var(--panel);
}

.slide-panel__content {
  flex: 1;
  overflow-y: auto;
  padding: var(--spacing-4);
}

.detail-section {
  margin-bottom: var(--spacing-6);
}

.detail-section:last-child {
  margin-bottom: 0;
}

.detail-collapse {
  margin-bottom: 0;
}

/* Statement shown when the limiting control has nothing to choose (one action,
   or limiting switched off) — sentence-toned, not a control. */
.detail-shed-statement {
  margin: 0;
}

.detail-mode-list,
.detail-stepped-list,
.detail-deltas {
  display: block;
}

.detail-mode-list[hidden],
.detail-stepped-list[hidden],
.detail-deltas[hidden] {
  display: none !important;
}

.detail-mode-row,
.detail-stepped-row {
  margin-bottom: 0;
  background: transparent;
  border-radius: 0;
}

.detail-mode-row[hidden],
.detail-stepped-row[hidden] {
  display: none !important;
}

.detail-mode-row__name {
  display: grid;
  gap: 2px;
  align-items: start;
  min-width: 0;
  margin-bottom: 0;
}

.detail-mode-row__priority {
  margin: 0;
  font-size: var(--font-size-xs);
  color: var(--muted);
  font-weight: var(--font-weight-normal);
}

.detail-setup-list {
  display: grid;
  gap: var(--spacing-4);
}

.detail-setup-list .field {
  margin: 0;
}

.detail-setup-notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-3);
  background: var(--color-state-info-bg);
  border: 1px solid var(--color-state-info-border);
  border-radius: var(--radius-md);
  color: var(--color-state-info-text);
}

.detail-setup-notice[hidden] {
  display: none;
}

.detail-setup-notice__content {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  flex: 1 1 auto;
  min-width: 0;
}

.detail-setup-notice__title {
  font-weight: var(--font-weight-semibold);
  /* The flow-conflict title interpolates a user-authored Flow name; break a
     long unbroken name instead of clipping it at the 320px viewport. */
  overflow-wrap: anywhere;
}

.detail-setup-notice__body {
  font-size: var(--font-size-sm);
  color: var(--color-state-info-text);
  overflow-wrap: anywhere;
}

.detail-setup-notice__action {
  flex: 0 0 auto;
  min-height: var(--pels-touch-target-min);
}

/* Warning variant (a battery's takeover notice): the warning state tokens the
   `.banner--warning` banner uses, with the body kept in the normal secondary
   tone so only the title carries the amber text. */
.detail-setup-notice--warning {
  background: var(--color-state-warning-bg);
  border-color: var(--color-state-warning-border);
  color: var(--color-state-warning-text);
}

.detail-setup-notice--warning .detail-setup-notice__body {
  color: var(--text-secondary);
}

.detail-stepped-boost {
  display: grid;
  gap: var(--spacing-3);
  margin-top: var(--spacing-3);
}

.detail-stepped-boost[hidden] {
  display: none !important;
}

.device-detail-readout {
  gap: var(--spacing-2);
}

.device-detail-readout__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-3);
}

/* Value + trailing action on one line (the Charging card's control readout):
   a block-level text button below the row read as a stray centered link. */
.device-detail-readout__trailing {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  min-width: 0;
}

.device-detail-readout__value {
  color: var(--text);
  font-weight: var(--font-weight-semibold);
}

/* The trailing text button (Change, Reorder): restore the M3 lead/trail
   padding Homey's universal `* { padding: 0 }` reset strips from the host box
   (the `.homes-settings md-text-button` idiom), so the label sits inside the
   button's shape rather than spilling past it, and never let the row shrink
   the button below its label: the value wraps instead. */
.device-detail-readout__trailing md-text-button {
  flex: none;
  padding-inline: var(--spacing-3);
}

/* Canonical M3 outlined segmented buttons. Used everywhere a view filter
   needs a small-set, mutually-exclusive choice: Plan/Adjust, Yesterday/Today/
   Tomorrow, Progress/Hourly plan, 7d/14d, All/Weekday/Weekend, Current/History
   plan, device-detail When-limiting. Top-level navigation stays on `md-tabs`. */
.segmented {
  display: inline-flex;
  width: 100%;
  min-height: var(--pels-touch-target-min);
  padding: 0;
  gap: 1px;
  border-radius: var(--radius-full);
  border: 1px solid var(--pels-segmented-container-border);
  background: var(--pels-segmented-container-border);
  overflow: hidden;
}

/* Specificity note: Homey injects a host stylesheet (`_base.css`) inside the
   settings WebView with default light-theme button rules
   (`button:not(.hy-nostyle):not([class*='homey-button']):not([class*='hy-button'])`,
   specificity (0,3,1)) that out-weigh single-class `.segmented__option`
   selectors. To win on the cascade tie without `!important`, the base / hover
   / disabled selectors below combine `.segmented` (class), `button` (type),
   and the duplicate-class trick `.segmented__option.segmented__option`
   (two classes for one element) — total specificity (0,3,1), same as Homey's
   rule, and PELS's stylesheet loads after Homey's so PELS wins on the tie.
   The selected-state selectors stack a duplicate-attribute trick
   (`[aria-pressed="true"][aria-pressed]`) on top to reach (0,4,1), comfortably
   above any host rule. Captured Homey host CSS lives under
   `packages/settings-ui/test/fixtures/homey-wrap/homey-host-base.css` so the
   `PELS_E2E_SIMULATE_HOMEY=light|dark` static-server mode reproduces the
   cascade locally; without that fixture the bug was invisible until deploy.

   Update 2026-05-16: the base (inactive) rule needs (0,4,1) — not (0,3,1) —
   because Homey's `_base.css` actually loads AFTER PELS's `style.css` in the
   live iframe, so a (0,3,1) tie goes to Homey. (The fixture initially
   simulated the opposite order, hiding the bug locally; that's now fixed
   in `static-server.mjs`.) Hover / focus-visible / disabled / selected
   variants below pick up an extra pseudo-class or attribute and sit at
   (0,4,1)+ comfortably; only this base rule needs the extra `.segmented__option`
   duplicate. */
.segmented button.segmented__option.segmented__option.segmented__option {
  flex: 1 1 0;
  /* Reserve enough room for the longest segmented label in the app
   * ("Conservative", 12 chars @ font-size-sm ≈ 88px) so the budget
   * Background-usage-reserve control doesn't wrap mid-word inside the 480 px
   * dialog. The button still shrinks to fit when its container can't satisfy
   * the request — the flex container clips, but the label stays whole rather
   * than breaking like `Conservati|ve`. The narrow-viewport override below
   * relaxes this floor at 320 px where 3 options × 96 px would push the
   * container past the viewport. */
  min-width: 96px;
  min-height: var(--pels-touch-target-min);
  appearance: none;
  border: 0;
  background: var(--pels-segmented-container-background);
  color: var(--pels-segmented-label-color);
  font: inherit;
  font-size: var(--font-size-sm);
  font-weight: var(--pels-segmented-label-font-weight);
  padding: var(--spacing-2) var(--spacing-3);
  cursor: pointer;
  text-align: center;
  /* Segment labels never break mid-word ("Weekday/s" at 320 px). The
   * earlier `overflow-wrap: break-word` still broke as a last resort once
   * the 96 px floor was relaxed at narrow widths; `nowrap` + the reduced
   * narrow-viewport padding below keep every label on one line, and
   * `ellipsis` degrades gracefully if a label ever outgrows its segment. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: var(--font-line-height-tight);
  transition: background var(--transition-fast), color var(--transition-fast);
}

.segmented button.segmented__option.segmented__option[aria-checked="true"][aria-checked],
.segmented button.segmented__option.segmented__option[aria-pressed="true"][aria-pressed] {
  background: var(--pels-segmented-selected-background);
  color: var(--pels-segmented-selected-text);
}

.segmented button.segmented__option.segmented__option:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: -2px;
}

.segmented button.segmented__option.segmented__option:disabled,
.segmented button.segmented__option.segmented__option[hidden] {
  cursor: not-allowed;
}

.segmented button.segmented__option.segmented__option:disabled:not([aria-checked="true"]):not([aria-pressed="true"]) {
  /* Disabled text uses the reserved dim tier, not the raised muted tier — a
     disabled control should recede, not read brighter than active helper copy. */
  color: var(--pels-text-disabled);
  opacity: 0.6;
}

.segmented button.segmented__option.segmented__option:hover:not(:disabled):not([aria-checked="true"]):not([aria-pressed="true"]) {
  color: var(--pels-text-primary);
  background: color-mix(in srgb, var(--pels-segmented-selected-background) 40%, transparent);
}

.segmented button.segmented__option.segmented__option[hidden] {
  display: none;
}

/* At 320 px the 96 px per-option floor would push a 3-option control past
 * the viewport (3 × 96 + 2 px gap = 290 px > available column). Relax the
 * floor so the buttons can shrink to fit the container, and slim the
 * horizontal padding so the longest stock label ("Weekdays") still fits on
 * one line inside a three-way split without truncating. */
@media (max-width: 360px) {
  .segmented button.segmented__option.segmented__option.segmented__option {
    min-width: 0;
    padding-inline: var(--spacing-2);
  }
}

/* Optional dual-label support: when a segmented option provides both a full
 * `--full` label and a `--short` abbreviation, show the full label at
 * comfortable widths and swap to the abbreviation at ≤360 px where the full
 * word ("Yesterday", "Tomorrow") would otherwise wrap mid-word. The button's
 * accessible name comes from `aria-label` so screen readers always speak the
 * full word; both visible spans are `aria-hidden="true"`. */
.segmented__option-label--short {
  display: none;
}

@media (max-width: 360px) {
  .segmented__option-label--full {
    display: none;
  }

  .segmented__option-label--short {
    display: inline;
  }
}

.detail-mode-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 80px;
  align-items: center;
  gap: var(--spacing-3) var(--spacing-4);
  padding: var(--spacing-2) 0;
}

.detail-mode-row__header {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
}

/* Non-interactive "Active" indicator on the currently-active mode row.
   Uses the canonical M3 md-assist-chip primitive so shape, padding, and
   typography come from the chip system; tonal teal via custom-property
   overrides matches the eco-palette good-state surface. */
.detail-mode-row__header .detail-mode-row__active-chip {
  /* Accent role, not the raw mint base colour: the mint dissolves as small
     foreground text on the mode-row container, and the accent green is the
     colour the surrounding switches already use for "on". */
  --md-assist-chip-label-text-color: var(--color-role-accent);
  --md-assist-chip-hover-label-text-color: var(--color-role-accent);
  --md-assist-chip-focus-label-text-color: var(--color-role-accent);
  --md-assist-chip-pressed-label-text-color: var(--color-role-accent);
  --md-assist-chip-container-color: color-mix(in srgb, var(--color-role-accent) 12%, transparent);
  --md-assist-chip-outline-color: transparent;
  --md-assist-chip-hover-state-layer-color: transparent;
  --md-assist-chip-pressed-state-layer-color: transparent;
  --md-assist-chip-label-text-font: var(--font-family-sans);
  --md-assist-chip-label-text-size: var(--font-size-xs);
  --md-assist-chip-label-text-weight: var(--font-weight-semibold);
  --md-assist-chip-container-height: 22px;
  pointer-events: none;
}

.detail-mode-row md-filled-text-field {
  width: 80px;
  justify-self: end;
  text-align: center;
}

.detail-diagnostics-status {
  margin-bottom: var(--spacing-3);
  color: var(--muted);
  font-size: var(--font-size-sm);
}

.detail-diagnostics-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--spacing-3);
}

/* `.detail-diagnostics-card` — decorator on `.pels-surface-card` (see
   canonical-primitives preamble above `.pels-surface-card`). Previously
   forked bg/border with a hardcoded `10px` radius (a token violation) and
   `12px` literal padding; rebinding onto the canonical dropped those forks.
   Sub-element typography (e.g. `.detail-diagnostics-card h4`) stays here. */

/* The diagnostics grid stretches row siblings to equal height, and the
   canonical card is a grid whose auto rows absorb that extra space evenly —
   which floated the shorter Held-back card's list off its title. Pack the
   stretched card's rows to the top, scoped to the grid that causes the
   stretch (the card's own base rule stays retired — cardPrimitiveRebind
   guard); the list already packs its own rows (`.detail-diagnostics-list`
   below). */
.detail-diagnostics-grid > .detail-diagnostics-card {
  align-content: start;
}

.detail-diagnostics-list {
  display: grid;
  gap: var(--spacing-2);
  margin: 0;
  align-content: start;
}

.detail-diagnostics-list div {
  display: grid;
  gap: 2px;
}

.detail-diagnostics-list dt {
  color: var(--muted);
  font-size: var(--font-size-xs);
}

.detail-diagnostics-list dd {
  margin: 0;
  color: var(--text);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
}

/* Device activity log — recent overview transitions for one device. A plain
   neutral list (no coloured state-rail per the card convention); state is
   carried by the per-entry status chip, which reuses the live device-card
   `.plan-chip--{modifier}` tones. */
.device-log__list {
  display: grid;
  gap: var(--spacing-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.device-log__entry {
  overflow-wrap: anywhere;
  display: grid;
  gap: var(--spacing-1);
  padding: var(--spacing-3);
  border: 1px solid var(--pels-surface-outline);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container-low);
}

.device-log__entry-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-2);
}

.device-log__time {
  flex: 0 0 auto;
}

.device-log__line {
  margin: 0;
}

.device-log__empty {
  margin: 0;
}

.activity-history__content {
  display: grid;
  gap: var(--spacing-4);
  min-width: 0;
}

.activity-history__content md-filled-select {
  width: 100%;
  min-width: 0;
}

/* Restore Material's button padding after Homey's host reset. */
.activity-history__actions md-outlined-button,
.activity-history__content > md-outlined-button {
  padding-inline: var(--spacing-6);
}

.activity-history__actions {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--spacing-3);
}

.device-log__session {
  padding-block: var(--spacing-2);
}

.detail-stepped-actions {
  display: flex;
  gap: var(--spacing-2);
  flex-wrap: wrap;
  margin-bottom: var(--spacing-3);
}

/* "+ Add step" sits below the last row, leading-aligned, quiet. */
.detail-stepped-add {
  display: flex;
  margin-bottom: var(--spacing-3);
}

/* Save/Undo bar: the profile's primary action pair. Save is tonal (secondary
 * emphasis) and disabled until an edit exists, so it reads as inert at rest. */
.detail-stepped-actions--save {
  justify-content: flex-end;
}

.detail-stepped-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(110px, 140px) var(--pels-touch-target-min);
  gap: var(--spacing-3);
  align-items: center;
  padding: 0 0 var(--spacing-2);
  border-bottom: 1px solid var(--panel-border);
  font-size: var(--font-size-xs);
  color: var(--muted);
}

.detail-stepped-list {
  margin-bottom: var(--spacing-3);
}

.detail-stepped-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(110px, 140px) var(--pels-touch-target-min);
  gap: var(--spacing-3);
  align-items: center;
  padding: var(--spacing-2) 0;
}

.detail-stepped-row > md-filled-text-field {
  width: 100%;
  max-width: none;
}

/* Per-step remove: a muted trash `md-icon-button.detail-stepped-remove` created
 * in `steppedLoadDraft.ts`. Neutral at rest so a full profile is not a wall of
 * red "Remove" links; the error tone only surfaces on hover/focus/press. */
.detail-stepped-remove {
  --md-icon-button-icon-color: var(--text-secondary);
  color: var(--text-secondary);
}

.detail-stepped-remove:hover,
.detail-stepped-remove:focus-visible,
.detail-stepped-remove:active {
  --md-icon-button-icon-color: var(--danger);
  color: var(--danger);
}

.detail-stepped-remove[disabled] {
  opacity: 0.4;
}

/* Push the per-step remove control to the row's trailing edge. */
.detail-stepped-row > .detail-stepped-remove {
  justify-self: end;
}

/* Make device rows clickable */
.device-row.clickable {
  cursor: pointer;
  transition: background var(--transition-fast);
}

.device-row.clickable:hover {
  background: var(--pels-surface-container-low);
}

.device-row.clickable:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
  background: var(--pels-surface-container-low);
}

.device-row.clickable:active {
  background: var(--pels-surface-container-high);
}

@media (max-width: 360px) {
  .device-detail-row,
  .detail-mode-row,
  .detail-stepped-row {
    grid-template-columns: 1fr;
  }

  .detail-stepped-header {
    display: none;
  }

  /* Narrow-viewport override: the stepped-row trash button (created by
   * `steppedLoadDraft.ts`) shifts to leading-edge alignment because the row
   * collapses to a single column at 360 px. */
  .detail-stepped-row .detail-stepped-remove {
    justify-self: start;
  }

  .device-detail-row > md-filled-text-field,
  .device-detail-row > md-filled-select,
  .detail-mode-row md-filled-text-field {
    justify-self: stretch;
    width: 100%;
    max-width: none;
  }
}

/* Usage summary cards */
/* `.summary-value--empty` is the only live class in this family — toggled
   onto already-themed metric values inside `.usage-card` blocks when the
   underlying stats series has no data. The companion `.usage-summary` /
   `.summary-card` / `.summary-label` / `.summary-value` rules were carried
   over from a retired v1 "summary cards" layout; none of them are emitted
   in markup any more (verified `grep -rn 'summary-card\|summary-label\|
   "summary-value"' packages/settings-ui/`). Dropped on the card
   consolidation pass. */
.summary-value--empty {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-normal);
  font-style: italic;
  color: var(--muted);
}

.budget-redesign-view {
  display: grid;
  gap: var(--spacing-3);
}

/* Honest Main-home scope line under the Budget header (multi-home): quiet
   supporting text on the page background, spaced as its own row between the
   header and whichever local view renders below. */
.budget-home-scope-line {
  margin: var(--spacing-2) 0 var(--spacing-3);
}

/* Budget page header now consumes the shared `.plan-hero` / `.pels-hero`
   primitive — eyebrow + headline + chip-row inherit the same typography,
   surface, and tone scaffolding the four sibling panels render. The
   Plan/Adjust toggle sits flush to the right of the headline row through
   the parent's `justify-content: space-between`; in the chip-rail row the
   chip-rail's `flex: 1 1 auto` consumes the inline space so the toggle sits
   at the trailing edge without an explicit `margin-left: auto`. Dropping
   that auto margin avoids a cosmetic asymmetry where, when the chip-rail row
   narrowed enough for the toggle to wrap to its own flex line, the auto
   margin pushed the toggle to the right edge while the chip stayed at the
   left — leaving an unbalanced gap between them. */
.budget-page-header__action {
  flex: 0 0 auto;
  align-self: center;
  /* Homey's host stylesheet opens with a universal `* { padding: 0 }` reset,
     and document rules targeting a custom-element host beat the component's
     own `:host` padding — so a naked md-outlined-button collapses to its
     bare label (the same bleed every other outlined button here works
     around with host-drawn padding). Restore the M3 lead/trail space on the
     host, where this class outranks the universal reset. */
  padding-inline: var(--spacing-4);
}

/* Two-step discard confirm on the Budget header toggle: the armed state
   ("Tap again to discard") carries the warning tone so it reads as a
   destructive choice. Label colour reaches the shadow-DOM text via the
   `--md-outlined-button-*` custom properties, not the host `color`. The
   state-pair tokens (`--color-state-warning-text` / `-border`) match the
   warning-tinted `.dry-run-banner__action` banner action (a text button) and
   carry the per-theme contrast the bare `--warning` amber lacks. */
#budget-redesign-mode-toggle.confirming {
  --md-outlined-button-label-text-color: var(--color-state-warning-text);
  --md-outlined-button-hover-label-text-color: var(--color-state-warning-text);
  --md-outlined-button-pressed-label-text-color: var(--color-state-warning-text);
  --md-outlined-button-focus-label-text-color: var(--color-state-warning-text);
  --md-outlined-button-outline-color: var(--color-state-warning-border);
  --md-outlined-button-hover-state-layer-color: var(--color-state-warning-text);
  --md-outlined-button-pressed-state-layer-color: var(--color-state-warning-text);
  --md-outlined-button-focus-state-layer-color: var(--color-state-warning-text);
}

/* Shared "stacked" banner layout (icon + title-over-body), used by the budget
   allocation warning and the weather over-hard-cap banner. */
.banner--stacked {
  align-items: flex-start;
}

.banner--stacked .banner__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

.banner--stacked .banner__body > * {
  margin: 0;
}

.banner--stacked .banner__title {
  font-weight: var(--font-weight-semibold);
}

.banner--stacked .banner__text {
  flex: initial;
}

/* Slim simulation banner: let the message shrink and wrap inside the nowrap
   flex row (min-width:0) so the fixed 48 px text action never overflows at
   320 px. Placed here — after the base `.banner__text` + `.banner--stacked`
   rules — to satisfy stylelint no-descending-specificity. */
#dry-run-banner .banner__text {
  flex: 1 1 auto;
  min-width: 0;
}

/* "as-is" must never split at its hyphen — at 360 px the wrap otherwise lands
   as "…stay as-" / "is", orphaning "is" on its own line. */
.banner__no-break {
  white-space: nowrap;
}

/* The Main-scoped multi-meter wording is deliberately more explicit than the
   single-home banner, and the banner's action is fixed-width, so in a flex row
   that longer message gets squeezed into a word-per-line tower. Give the scoped
   variant a full-width text column with its action below.

   The bound was 360 px, which inverted the cliff: the row came back at 361 px
   but the wording still did not fit it, so the banner towered from 361 px to
   ~430 px (6-7 lines, TALLER at 361 px than at 320 px) across exactly the
   widths most phones use. It fits the row again from ~440 px, which is where
   this stops. The single-home banner keeps its slim single row at every width
   (layout-regressions.spec.ts pins that); only the wider wording stacks.

   Playwright projects only run 320 and 480, so the failing band was never
   asserted -- dry-run-banner.spec.ts walks 361/393/412 as the guard. */
@media (max-width: 440px) {
  #dry-run-banner[data-home-scope="main"]:not([hidden]) {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    gap: 0 var(--spacing-2);
    min-height: 0;
    padding-block: var(--spacing-2);
  }

  #dry-run-banner[data-home-scope="main"] .banner__icon {
    grid-column: 1;
    grid-row: 1;
    margin-block-start: var(--spacing-1);
  }

  #dry-run-banner[data-home-scope="main"] .banner__text {
    grid-column: 2;
    grid-row: 1;
    align-self: center;
    min-width: 0;
  }

  #dry-run-banner[data-home-scope="main"] .dry-run-banner__action {
    grid-column: 2;
    grid-row: 2;
    justify-self: start;
    margin-inline-start: calc(-1 * var(--spacing-3));
  }
}

.budget-redesign-card {
  min-width: 0;
}

#budget-redesign-adjust-view .budget-redesign-card {
  background: var(--pels-surface-container);
  border-color: var(--pels-surface-outline);
  box-shadow: var(--shadow-sm);
}

#budget-redesign-adjust-view details.budget-redesign-card[open] {
  background: var(--pels-surface-container-high);
}

.budget-card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-3);
  min-width: 0;
}

/* The flex-grow on title side. `> h2` matches DeadlinePlan / DeadlinePlanHistoryDetail
 * where the title is a direct child; `> div:first-child` matches BudgetOverview where
 * the h3 + subtitle pair is wrapped in a div (the wrapping div is the flex item, so
 * targeting the h3 directly is dead code — caught in PR #1241 gemini review). */
.budget-card-header > h2,
.budget-card-header > div:first-child,
.budget-card-header > .section-title {
  flex: 1 1 auto;
  min-width: 0;
}

.budget-card-header > button,
.budget-card-header > .budget-toggle-mount {
  flex-shrink: 0;
}

.budget-card-header h3,
.budget-card-header .section-title {
  margin-top: 0;
}

/* The kWh⇄kr unit toggle sits on its own row under the chart header, right-
 * aligned beneath the mode toggle so the title row stays uncrowded at 320 px.
 * `.budget-chart-card` is a grid, so this row spans the column; the grid gap
 * supplies the vertical rhythm (no margins). */
.budget-chart-unit-row {
  display: flex;
  justify-content: flex-end;
  min-width: 0;
}

/* Compact the unit toggle to its content so flex-end right-aligns it as a
 * SECONDARY control — visually subordinate to the full-width Progress/Hourly
 * mode toggle above it (the base `.segmented` is width:100%, which would
 * otherwise make the two toggles read as co-equal). Touch-target min-height is
 * inherited from the base, so each segment stays tappable. */
.budget-chart-unit-row .segmented {
  width: auto;
}

.budget-chart-card {
  display: grid;
  gap: var(--pels-card-gap);
}

.budget-confidence-card {
  display: grid;
  gap: var(--spacing-3);
}

.budget-confidence-card__value {
  display: inline-flex;
  align-items: baseline;
  gap: var(--spacing-2);
  justify-self: end;
  padding: 2px var(--spacing-2);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--pels-surface-container-highest) 62%, transparent);
  color: var(--pels-text-settings-trailing-color);
  font-weight: var(--font-weight-semibold);
  white-space: nowrap;
}

.budget-confidence-card__value small {
  color: var(--pels-text-muted);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-normal);
}

.budget-confidence-card__details {
  display: grid;
  gap: var(--spacing-2);
}

/* Title row collapses the previous three-line layout (title / supporting copy /
   "What this means" disclosure) into one row: title + value chip + chevron at
   480 px. The supporting copy moves inside the disclosure body. */
.budget-confidence-card__details summary,
.budget-confidence-card__summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  list-style: none;
}

.budget-confidence-card__details summary::-webkit-details-marker {
  display: none;
}

.budget-confidence-card__summary .budget-confidence-card__summary-title {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

.budget-confidence-card__explainer {
  color: var(--accent);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
}

.budget-confidence-card__details[open] .budget-confidence-card__explainer {
  /* Hide the inline affordance once expanded — the body content takes over. */
  display: none;
}

.budget-confidence-card__explanation {
  margin: 0;
}

.budget-chart-card .budget-card-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.budget-chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-3);
  align-items: center;
  color: var(--pels-text-muted);
  font-size: var(--font-size-xs);
}

.budget-chart-legend__item {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
}

/* Base swatch carries geometry only — every usage picks its fill/stroke via
   a modifier below, so a default fill would be unreachable ink. */
.budget-chart-legend__swatch {
  width: 12px;
  height: 8px;
  border-radius: var(--radius-sm);
}

/* Solid mint Actual line — matches the progress chart's width-3 stroke. */
.budget-chart-legend__swatch--actual {
  height: 3px;
  background: var(--pels-chart-actual);
}

/* Hourly-mode Actual overlay: on-surface ink riding the mint/slate stack
   (a mint swatch here would claim the Managed colour). */
.budget-chart-legend__swatch--actual-overlay {
  height: 2px;
  background: var(--text);
}

.budget-chart-legend__swatch--background {
  background: var(--pels-chart-background);
}

.budget-chart-legend__swatch--managed {
  background: var(--pels-chart-managed);
}

/* Ice-blue planned-allocation bar (Budget hourly chart, no-split path). */
.budget-chart-legend__swatch--plan {
  background: var(--pels-chart-plan);
}

/* Dashed slate budget reference — the progress chart's Budget stroke. */
.budget-chart-legend__swatch--reference {
  height: 2px;
  border-top: 2px dashed var(--pels-chart-budget-reference);
  background: transparent;
}

/* Dotted ice-blue projection — matches the plotted dotted stroke. */
.budget-chart-legend__swatch--forecast {
  height: 2px;
  border-top: 2px dotted var(--pels-chart-forecast);
  background: transparent;
}

.budget-chart-legend__swatch--price {
  height: 2px;
  border-top: 2px solid var(--pels-chart-price-line);
  background: transparent;
}

/* Budget hero managed/background split: a compact labeled stacked bar in the
   hero (replaces the former dim "Managed … · Background …" meta text). Track
   reuses the shared hero-meter primitive AT ITS FULL 14px HEIGHT (the 480px
   Overview meters' size — also what keeps the slate segment legible against
   the empty track); segments and label swatches bind to the same chart
   tokens the Budget hourly stack and Usage stack render with, so
   "managed = mint, background = slate" reads as one convention. Segments
   scale against the day's budget, so the empty track remainder carries the
   Overview-established meaning: room left. */
.budget-hero-split {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  margin-top: var(--spacing-2);
}

/* Budget hero metadata role tiers. The running-total fact ("N kWh used so far
 * today · estimated M kr today") reads at primary tone — it is the card's key
 * "where am I now?" answer and summarises the split bar directly below it.
 * The price-context notes below keep their `--muted` supporting tone, so the
 * two roles (primary fact vs muted notes) no longer read as one flat grey
 * block — no extra class needed, the `--muted` variant already carries them. */
.budget-hero__usage-line {
  color: var(--pels-text-primary);
}

/* Headline-row estimated cost (`≈ 6.50 kr`): same row as the kWh comparison,
   secondary tone so the kWh figure keeps top billing while money reads at a
   glance (parity with the Budget-and-Price widget's headline). */
.plan-hero__headline-cost {
  font-size: var(--font-size-md);
  color: var(--text-secondary);
  white-space: nowrap;
  align-self: baseline;
}

/* The grouped price-context readout (shaping tagline + export price): one
   muted block whose items read as one unit and wrap between items at 320 px,
   never mid-item. */
.budget-hero__price-readout {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-1) var(--spacing-3);
}

.budget-hero__price-readout > span {
  white-space: nowrap;
}

/* Over-budget one-tap recourse under the decision sentence. */
.budget-hero__recourse {
  display: flex;
  justify-content: flex-start;
  margin-top: var(--spacing-1);
}

/* Header action pair: the transient "Keep editing" escape sits beside the
   armed Done toggle without re-flowing the chip rail. */
.budget-page-header__action-group {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
}

/* Settings-launched Adjust editor: the armed confirm's escape renders as a
   slim row under the app bar (the back arrow itself carries the destructive
   half). */
.budget-page-header__appbar-escape {
  display: flex;
  justify-content: flex-end;
  padding: 0 var(--spacing-2);
}

.budget-hero-split__seg--managed {
  background: var(--pels-chart-managed);
}

.budget-hero-split__seg--background {
  background: var(--pels-chart-background);
}

/* Day already over budget: the trailing rendered segment carries the warning
   overflow tone — the same overflow grammar as the Overview power bar's
   `data-over-safe-pace` treatment, keyed to the budget semantics. */
.budget-hero-split__seg--managed[data-over-budget],
.budget-hero-split__seg--background[data-over-budget] {
  background: var(--pels-status-warning);
}

.budget-hero-split__labels {
  /* The legend primitive's muted text is too dim for a hero data row —
     lift to the secondary tone so the split is readable at arm's length. */
  color: var(--pels-text-secondary);
}

.budget-hero-split__prefix {
  color: var(--pels-text-muted);
}

#budget-redesign-adjust-view {
  /* On-primary / on-secondary text sits on the saturated accent green —
     use `--color-base-accent-on` (a theme-invariant near-black) instead
     of `--color-base-black`, which the desktop palette flips to a near-
     white page wash and would otherwise produce near-white text on a
     saturated green container (~3.30 : 1, fails AA for normal text). */
  --pels-md-on-primary: var(--color-base-accent-on);
  --pels-md-on-secondary: var(--color-base-accent-on);
  padding-bottom: var(--spacing-8);
}

#budget-redesign-adjust-view .plan-card__title {
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
  color: var(--pels-on-surface-variant, var(--color-semantic-text-secondary));
  margin-bottom: var(--spacing-1);
}

.budget-redesign-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-2);
  align-items: center;
  margin-top: var(--spacing-2);
}

#budget-redesign-adjust-view .budget-redesign-actions {
  position: sticky;
  bottom: 0;
  margin-top: 0;
  padding: var(--spacing-3) var(--spacing-2);
  background: var(--color-overlay-toast);
  border-top: 1px solid var(--panel-border);
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  z-index: 5;
  backdrop-filter: blur(8px);
}

#budget-redesign-adjust-view .budget-redesign-actions md-filled-button,
#budget-redesign-adjust-view .budget-redesign-actions md-text-button {
  flex: 1 1 auto;
}

/* While the Adjust panel has an unsaved/pending state, the sticky actions bar
   sits at the bottom of the viewport. Raise the snackbar above it so the bar
   stays tappable (the Undo toast itself uses pointer-events: auto). */
body:has(#budget-redesign-adjust-view .budget-redesign-actions) .toast {
  bottom: calc(var(--spacing-4) + var(--spacing-8));
}

.budget-setting-row--editable {
  align-items: center;
}

.budget-setting-row--editable > span:first-child {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  flex: 1;
  min-width: 0;
}

@media (max-width: 380px) {
  #budget-redesign-adjust-view .budget-setting-row--editable {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--spacing-2);
  }

  #budget-redesign-adjust-view .budget-setting-row--editable > .budget-redesign-field,
  #budget-redesign-adjust-view .budget-setting-row--editable > md-switch {
    justify-self: start;
    width: 100%;
    max-width: 100%;
  }

  #budget-redesign-adjust-view .budget-redesign-field--kwh,
  #budget-redesign-adjust-view .budget-redesign-field--select {
    width: 100%;
  }
}

.budget-redesign-field--kwh {
  width: 140px;
}

.budget-redesign-comparison .budget-comparison__current {
  color: var(--color-semantic-text-secondary);
}

.budget-redesign-comparison .budget-comparison__arrow {
  margin: 0 var(--spacing-1);
  color: var(--color-semantic-text-muted);
}

.budget-redesign-comparison .budget-comparison__candidate {
  color: var(--accent);
  font-weight: var(--font-weight-semibold);
}

.budget-redesign-comparison .budget-comparison__delta {
  margin-left: var(--spacing-2);
  color: var(--color-semantic-text-secondary);
  font-variant-numeric: tabular-nums;
}

.budget-comparison-chart {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  margin-top: var(--spacing-3);
}

.budget-comparison-chart__title {
  margin: 0;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-semantic-text-secondary);
}

.budget-comparison__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-3);
  align-items: center;
  margin-top: var(--spacing-2);
  color: var(--muted);
  font-size: var(--font-size-xs);
}

.budget-comparison__legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
}

.budget-comparison__legend-swatch {
  width: 12px;
  height: 8px;
  border-radius: var(--radius-sm);
  background: var(--pels-chart-plan);
}

.budget-comparison__legend-swatch--background {
  background: var(--pels-chart-background);
}

.budget-comparison__legend-swatch--managed {
  background: var(--pels-chart-managed);
}

.budget-comparison__legend-swatch--price {
  height: 2px;
  border-top: 2px solid var(--pels-chart-price-line);
  background: transparent;
}

.budget-redesign-chart {
  width: 100%;
  height: 210px;
  min-width: 0;
  overflow: hidden;
  border-radius: var(--radius-md);
}

.budget-chart-caveat,
.budget-next-action {
  margin: 0;
}

.budget-settings-list {
  display: grid;
  gap: 0;
  border-top: 1px solid color-mix(in srgb, var(--pels-surface-outline) 72%, transparent);
}

.budget-settings-list--compact {
  margin-top: var(--spacing-3);
}

.budget-setting-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--spacing-1) var(--spacing-3);
  align-items: center;
  min-width: 0;
  padding: var(--spacing-3) 0;
  border-bottom: 1px solid color-mix(in srgb, var(--pels-surface-outline) 72%, transparent);
  background: transparent;
}

.budget-setting-row > span:first-child {
  min-width: 0;
}

/* Stacked variant: short option sets (2-3 values) render as a full-width
 * segmented control under the label, giving the selected state clear contrast
 * and a predictable hit area at 480 px / 320 px webviews. These rules live
 * after the base `.budget-setting-row` declarations on purpose — the stacked
 * grid template must override the two-column base, so source order matters
 * (the modifier selector tier matches the base, so the cascade resolves on
 * order alone). The "Conservative" segmented option used to wrap mid-word
 * inside the 480 px dialog when the base layout won and squeezed the toggle. */
.budget-setting-row--stacked {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--spacing-2);
  align-items: start;
}

.budget-setting-row--stacked > span:first-child {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  min-width: 0;
}

.budget-setting-row--stacked > .segmented {
  width: 100%;
}

.budget-setting-row__label {
  display: block;
  color: var(--pels-text-settings-label-color);
  font-weight: var(--pels-text-settings-label-font-weight);
}

.budget-setting-row__value {
  justify-self: end;
  width: fit-content;
  max-width: 100%;
  padding: 2px var(--spacing-2);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--pels-surface-container-highest) 62%, transparent);
  white-space: nowrap;
  color: var(--pels-text-settings-trailing-color);
  font-weight: var(--pels-text-settings-trailing-font-weight);
  cursor: default;
}

.budget-settings-list--compact .budget-setting-row {
  grid-template-columns: minmax(0, 1fr) auto;
}

.budget-planning-behavior {
  padding: 0;
}

.budget-planning-behavior__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-3);
  padding: var(--spacing-4);
  cursor: pointer;
}

.budget-planning-behavior__heading {
  display: grid;
  gap: var(--spacing-1);
  min-width: 0;
}

.budget-planning-behavior__summary::-webkit-details-marker {
  display: none;
}

.budget-planning-behavior__summary::after {
  content: "";
  width: 8px;
  height: 8px;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  color: var(--muted);
  transform: rotate(45deg);
  transition: transform var(--transition-fast);
}

.budget-planning-behavior[open] .budget-planning-behavior__summary::after {
  transform: rotate(225deg);
}

.budget-planning-behavior > .budget-settings-list {
  padding: 0 var(--spacing-4) var(--spacing-4);
}

.budget-context-action {
  width: 100%;
  justify-self: stretch;
  background: var(--pels-surface-container-high) !important;
  border-color: var(--pels-surface-outline) !important;
}

.deadline-plan-panel {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
  background: var(--bg);
  /* No top inset: the panel is a normal in-flow sibling of the tab panels
   * inside `main.screen`, so it always sits below the tab bar and any
   * banners — never at the viewport top. The former
   * `max(--pels-homey-mobile-chrome, env(safe-area-inset-top))` padding
   * (added by eb8f0720 when this view could still open at the viewport top)
   * became vestigial when the panel moved in-flow below the always-visible
   * tab bar (ce3357cf) and rendered only as a dead ~64 px band between the
   * banner and the back button. Institutional note: nothing in the app
   * reserves a top inset for Homey's native mobile chrome anymore — the
   * `homey-mobile-chrome` token was deleted from tokens/component.json
   * along with this padding. If Homey ever overlays chrome inside the
   * iframe again, an app-wide inset strategy is needed, not a per-panel
   * padding. */
  padding: 0;
}

.deadline-plan-panel__surface {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

.deadline-horizon-card {
  gap: var(--spacing-3);
}

.deadline-horizon-chart {
  width: 100%;
  min-width: 0;
  /* 240 px container holds: top legend (auto-wraps to two lines at 320–480 px
   * when series names are long), price grid (live chart only), load+progress
   * grid, x-axis tick labels under containLabel. Previously 220 px assumed a
   * single-line legend that truncated long localized labels with `…`.
   * History-detail charts only — the live page now uses the two
   * single-question containers below. */
  height: 240px;
}

/* Live-page schedule chart ("When will it run, and at what price?"): one
 * grid, no legend, so the container is shorter than the history chart.
 * Heights are physical-pixel chart viewports (cf. `pels.chart.week-height`)
 * and must match SCHEDULE_CHART_HEIGHT / TRAJECTORY_CHART_HEIGHT in
 * views/DeadlinePlan.tsx, which seed the cold-mount fallback size. */
.deadline-schedule-chart {
  width: 100%;
  min-width: 0;
  height: 190px;
  /* Scrubbing the chart must read as a horizontal drag, not a page scroll.
   * Keep vertical panning available (pan-y) so the page still scrolls when
   * the gesture starts vertically. */
  touch-action: pan-y;
}

/* Live-page trajectory chart ("Will it reach 65 °C in time?"). */
.deadline-trajectory-chart {
  width: 100%;
  min-width: 0;
  height: 160px;
  touch-action: pan-y;
}

/* History-detail trajectory chart ("Did it heat up as planned?"). Shorter
 * than the legacy 240 px `.deadline-horizon-chart` — the ECharts legend rows
 * moved into the DOM legend above the chart. Height must match
 * HISTORY_TRAJECTORY_CHART_HEIGHT in views/DeadlinePlanHistoryDetail.tsx
 * (the cold-mount fallback size). */
.deadline-history-trajectory-chart {
  width: 100%;
  min-width: 0;
  height: 180px;
  touch-action: pan-y;
}

/* Compact DOM legend row above the history trajectory chart:
 * Measured / Planned / Target 65.0 °C. Wraps cleanly at 320 px. */
.deadline-history-legend {
  display: flex;
  flex-flow: row wrap;
  gap: var(--spacing-2) var(--spacing-4);
  align-items: center;
  font-size: var(--pels-text-supporting-font-size);
  color: var(--pels-text-supporting-color);
}

.deadline-history-legend__item {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
}

/* Line-sample swatches. Heights express the series stroke weights (measured
 * 2.5 px accent, planned 1.5 px muted, target dashed muted) via border-top
 * so the swatch reads as the actual line. */
.deadline-history-legend__swatch {
  display: inline-block;
  width: 1.25em;
  height: 0;
}

.deadline-history-legend__swatch[data-series="measured"] {
  border-top: 3px solid var(--color-role-accent);
  border-radius: var(--radius-xs);
}

.deadline-history-legend__swatch[data-series="planned"] {
  border-top: 2px solid var(--pels-text-supporting-color);
}

.deadline-history-legend__swatch[data-series="target"] {
  border-top: 2px dashed var(--pels-text-supporting-color);
}

/* Pinned point readout under a chart (shared primitive — smart-task pages
 * via `.deadline-readout`, Usage-tab charts via `.chart-readout`). Card-inset
 * surface one tier above the card so it reads as the chart's caption slot;
 * fixed min-height (touch-target token) so swapping one-line and two-line
 * content never reflows the page mid-scrub. */
.chart-readout,
.deadline-readout {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  justify-content: center;
  min-height: var(--pels-touch-target-min);
  padding: var(--spacing-2) var(--spacing-3);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container-low);
}

/* The Usage cards lay children out in normal flow (no flex gap), so the
 * readout carries its own breathing room from the chart above. */
.chart-readout {
  margin-top: var(--spacing-2);
}

/* The budget chart card is a grid with its own gap — the normal-flow
 * breathing room above would double the spacing there. */
.budget-chart-card .chart-readout {
  margin-top: 0;
}

/* Desktop (hover-capable fine pointer): the floating ECharts tooltip is
 * active there, so the pinned row would caption the same point twice. Touch
 * keeps the row — it replaces the disabled tooltip. Scoped to
 * `.chart-readout` only: the smart-task pages' `.deadline-readout` has no
 * floating tooltip and must stay visible on desktop. */
@media (hover: hover) and (pointer: fine) {
  .chart-readout {
    display: none;
  }
}

.chart-readout__primary,
.deadline-readout__primary {
  font-size: var(--pels-text-body-font-size);
  font-weight: var(--font-weight-semibold);
}

.chart-readout__secondary,
.deadline-readout__secondary {
  font-size: var(--pels-text-supporting-font-size);
  color: var(--pels-text-supporting-color);
}

.chart-readout__value--warn {
  color: var(--color-role-warn);
}

/* Stateline under the trajectory chart: "51.1 °C now · on track — projected
 * ready ≈ …". The emphasis span carries the tone; the remainder stays at the
 * default on-surface tone. */
.deadline-stateline {
  margin: 0;
  font-size: var(--pels-text-body-font-size);
}

.deadline-stateline__emphasis {
  font-weight: var(--font-weight-semibold);
  color: var(--color-role-good);
}

.deadline-stateline--danger .deadline-stateline__emphasis {
  color: var(--color-role-danger);
}

/* Trust caption under the live horizon chart: "Picked the N cheapest of M
 * hours · avg P kr/kWh vs Q kr/kWh window average". Sits between the chart and
 * the card edge with a small gap so it reads as a footnote to the bars above. */
.deadline-horizon-caption {
  margin-top: var(--spacing-2);
}

/* ─── Per-hour bar strip (v2.7.3) ────────────────────────────────────────────
 *
 * Postmortem strip rendered below the trajectory chart. Reads as "when did
 * each hour run, and what did each hour cost?" — owner walk #11 + #14.
 * Bars are shaded by price tier (cheap / normal / expensive); planned-
 * but-skipped hours render a dashed outline at the baseline; the single
 * cheapest delivered hour gets a soft accent glow ("this is the one we
 * waited for").
 */

.hourly-strip {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  width: 100%;
  min-width: 0;
}

.hourly-strip__legend {
  display: flex;
  flex-flow: row wrap;
  gap: var(--spacing-2);
  align-items: center;
}

/* Legend chip overrides on top of `.plan-chip.plan-chip--muted`. We reuse
   the chip primitive's padding / radius / font tokens; the only override
   is the embedded tone bar's gap. */
.hourly-strip__legend-item {
  gap: var(--spacing-1);
}

/* Embedded tone bar inside a legend chip. Sized off the chip's font line
   height so the swatch scales with chip text rather than carrying its own
   hardcoded dimensions; `--radius-xs` keeps the bar visually crisp at the
   small footprint. */
.hourly-strip__legend-bar {
  display: inline-block;
  width: 0.75em;
  height: 0.75em;
  border-radius: var(--radius-xs);
}

.hourly-strip__legend-bar[data-tone="cheap"] {
  background: var(--pels-chart-hour-tone-cheap);
}

.hourly-strip__legend-bar[data-tone="normal"] {
  background: var(--pels-chart-hour-tone-normal);
}

.hourly-strip__legend-bar[data-tone="expensive"] {
  background: var(--pels-chart-hour-tone-expensive);
}

/* Dashed sample for the "planned, didn’t run" legend chip — mirrors the
 * skipped-bucket outline grammar on the strip itself. */
.hourly-strip__legend-bar[data-tone="skipped"] {
  background: transparent;
  border: 1px dashed var(--pels-chart-hour-tone-skipped-outline);
}

.hourly-strip__legend-label {
  color: inherit;
}

.hourly-strip__bars {
  display: flex;
  flex-direction: row;
  align-items: flex-end;
  /* The strip baseline matches the bottom of the trajectory chart's x-axis
   * so the two surfaces read on the same time grid. 64 px holds the
   * tallest bar plus a small floor for outlined-only buckets without
   * dominating the chart card. */
  height: 64px;
  width: 100%;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Hour labels under the buckets ("19 … 00", mock-style). Mirrors the bar
 * row's flex distribution + gap so each label centres under its bucket; the
 * producer thins the cadence (first/last/every-2nd) when the buckets get
 * narrow, emitting empty cells for unlabelled buckets. */
.hourly-strip__axis {
  display: flex;
  flex-direction: row;
  width: 100%;
  gap: 2px;
  margin-top: var(--spacing-1);
}

.hourly-strip__axis-label {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-align: center;
  font-size: var(--font-size-xs);
  color: var(--pels-text-supporting-color);
}

.hourly-strip__bucket {
  /* Each bucket fills 1/N of the row width — flex distribution keeps the
   * strip aligned across DST 23/25-hour windows without re-deriving width. */
  flex: 1 1 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  height: 100%;
  min-width: 0;
  position: relative;
  /* Tap target: selecting a bucket drives the pinned readout below the
   * strip (the floating tooltip was retired with the Phase 1B one-grammar
   * pass — same interaction as the trajectory chart above). */
  cursor: pointer;
  outline: none;
}

.hourly-strip__bucket:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

.hourly-strip__bar {
  display: block;
  width: 100%;
  /* Baseline tick: even a zero-height gap bucket keeps a sliver of bar so
   * the selection outline below has something visible to hug. */
  min-height: 2px;
  border-radius: var(--radius-xs);
  background: var(--pels-surface-container-high);
  transition: background var(--transition-fast);
}

/* Selected bucket: visible outline hugging the BAR fill — not the full 64 px
 * column, which read as a phantom full-height value on short bars. Zero/gap
 * buckets keep a 2 px baseline tick via the bar's `min-height` above, so the
 * selection stays visible on them too. */
.hourly-strip__bucket[data-selected="true"] .hourly-strip__bar {
  outline: 2px solid var(--text);
  outline-offset: 1px;
}

.hourly-strip__bucket[data-tone="cheap"] .hourly-strip__bar {
  background: var(--pels-chart-hour-tone-cheap);
}

.hourly-strip__bucket[data-tone="normal"] .hourly-strip__bar {
  background: var(--pels-chart-hour-tone-normal);
}

.hourly-strip__bucket[data-tone="expensive"] .hourly-strip__bar {
  background: var(--pels-chart-hour-tone-expensive);
}

/* Gap buckets carry `data-tone="gap"` — no fill (the strip still reserves
 * space so the time axis stays intact across DST 23/25-hour windows). */
.hourly-strip__bucket[data-tone="gap"] .hourly-strip__bar {
  background: transparent;
}

/* Planned-but-skipped: dashed 1 px outline box at the PLANNED kWh height
 * (the view sizes the bar from the bucket's planned energy), no fill. Reads
 * as "schedule said yes, machinery did not" — restraint, not action. The
 * 8 px min-height keeps a zero-planned outline visible as the old baseline
 * sliver. */
.hourly-strip__bucket[data-outline="true"] .hourly-strip__bar {
  background: transparent;
  min-height: 8px;
  border: 1px dashed var(--pels-chart-hour-tone-skipped-outline);
}

/* Soft accent glow on the single cheapest delivered hour. "This is the
 * one we waited for." 4 px box-shadow at the configured alpha so the
 * highlight reads as warmth, not as a primary call-to-action. */
.hourly-strip__bucket[data-cheapest="true"] .hourly-strip__bar {
  box-shadow: 0 0 4px 0 var(--pels-chart-hour-tone-glow);
}

@media (prefers-reduced-motion: reduce) {
  /* Suppress the bar transition for motion-sensitive users; the glow stays
   * (it's a static box-shadow, not an animation). */
  .hourly-strip__bar {
    transition: none;
  }
}

/* Second question title inside the chart card ("When did each hour run, and
 * what did it cost?") — breathing room above so the strip reads as its own
 * section while staying on the shared time axis. */
.plan-history-detail__strip-title {
  margin-top: var(--spacing-3);
}

.plan-inputs {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

.plan-inputs .pels-card-supporting {
  margin: 0;
}

.plan-inputs__section {
  border-top: 1px solid var(--pels-surface-outline);
  padding-top: var(--spacing-3);
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}

.plan-inputs__list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  margin: 0;
}

.plan-inputs__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-3);
}

.plan-inputs__row-label {
  color: var(--pels-text-supporting-color);
  font-size: var(--font-size-sm);
  margin: 0;
}

.plan-inputs__row-value {
  color: var(--text);
  font-weight: var(--font-weight-medium);
  margin: 0;
  min-width: 0;
  text-align: right;
  overflow-wrap: break-word;
  font-variant-numeric: tabular-nums;
}

/* Stale-sample affordance for `KwhPerUnitProvenanceRow` — mirrors the
 * `.deadline-list-card__when-row--warn dd` token pair used by the sibling
 * Smart-task list "Ready by" row when the chip tone is warn. Resolved
 * producer-side via `formatLastSampleValue` so the view never re-derives
 * staleness from the value text. */
.plan-inputs__row-value--warn {
  color: var(--pels-status-on-warning);
  font-weight: var(--font-weight-semibold);
}

.plan-inputs__row-note {
  color: var(--pels-text-secondary);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-normal);
  line-height: var(--font-line-height-tight);
  margin-top: var(--spacing-1);
}

@media (max-width: 480px) {
  .plan-inputs__row {
    flex-direction: column;
    gap: var(--spacing-1);
  }

  .plan-inputs__row-value {
    text-align: left;
  }
}

.plan-inputs__learning .plan-inputs__list {
  margin-top: var(--spacing-3);
}

.plan-inputs > .plan-inputs__list .plan-inputs__row,
.plan-inputs__section > .plan-inputs__list .plan-inputs__row {
  flex-direction: row;
}

.plan-inputs > .plan-inputs__list .plan-inputs__row-value,
.plan-inputs__section > .plan-inputs__list .plan-inputs__row-value {
  flex-shrink: 0;
}

/* Day view foundation */

.budget-chart-card .budget-card-header .segmented {
  max-width: 220px;
  justify-self: start;
}

#budget-panel {
  --day-view-color-primary: var(--color-role-good);
  --day-view-color-planned: var(--color-role-good);
  --day-view-color-actual: var(--text);
  --day-view-color-budget: var(--info);
  --day-view-color-warn: var(--pels-status-warning);
  --day-view-color-cheap: var(--pels-status-good);
  --day-view-color-normal: var(--color-surface-5);
  --day-view-color-expensive: var(--pels-status-critical);
  --day-view-color-now: var(--color-state-info-border);
  padding: 0;
  border-color: transparent;
  background: transparent;
  box-shadow: none;
  backdrop-filter: none;
}

#device-control-legend,
#device-card-list {
  min-width: 0;
  max-width: 100%;
}

#device-card-list {
  display: grid;
  gap: var(--spacing-3);
  margin-top: var(--spacing-2);
}

#usage-panel {
  --day-view-color-primary: var(--color-role-info);
  --day-view-color-budget: var(--color-role-accent);
  --day-view-color-warn: var(--color-role-danger);
  --day-view-color-now: var(--color-state-info-border);
}

/* Usage page stacks its sections on the shared `.card.panel` rhythm
   (`gap: --spacing-3`) like every other page — no per-page override. */

.day-view-chart {
  display: grid;
  gap: var(--spacing-2);
  margin-bottom: var(--spacing-4);
  min-width: 0;
}

.day-view-bars-wrap {
  position: relative;
  height: 160px;
  min-width: 0;
  overflow: hidden;
}

.day-view-bars {
  height: 100%;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  align-items: end;
  gap: var(--spacing-1);
}

.usage-day-bars--echarts {
  display: block;
  position: relative;
  min-height: 100%;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
}

.day-view-labels {
  display: grid;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: var(--spacing-1);
  font-size: var(--font-size-xs);
  color: var(--muted);
  text-align: center;
  line-height: var(--font-line-height-tight);
  padding-top: var(--spacing-1);
  overflow: hidden;
}

.day-view-label {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Usage stats charts */
.usage-chart {
  position: relative;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
}

.hourly-pattern,
.usage-history-chart {
  margin: 0;
  height: 196px;
}

.hourly-pattern--echarts,
.daily-history--echarts {
  min-width: 0;
}

.hourly-pattern__empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  text-align: center;
  padding: var(--spacing-4);
  color: var(--muted);
  font-size: var(--font-size-sm);
  font-style: italic;
}

/* ─── Electricity prices / Price-aware devices (Preact) ─────────────────── */

/* Live "Right now" summary card at the top of the Electricity prices panel.
   Decorator on `.settings-form-card`; lays its status rows out as a compact
   column. The current-price row carries either the canonical `.plan-chip`
   (cheap/expensive) or calm toned text (normal / awaiting), so it never grows
   a coloured state-rail — state lives in the chip/text alone. */
.electricity-prices-live-summary {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}

.price-config-source-status {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  padding: var(--spacing-2) var(--spacing-3);
  background: var(--pels-surface-container-lowest);
  border-radius: var(--radius-sm);
  border: 1px solid var(--panel-border);
}

.price-config-status-row {
  display: flex;
  justify-content: space-between;
  gap: var(--spacing-2);
  font-size: var(--font-size-sm);
}

.electricity-prices-live-summary .price-config-status-row {
  align-items: center;
}

.price-config-status-label {
  color: var(--pels-text-muted);
}

.price-config-status-value {
  font-weight: var(--font-weight-semibold);
  text-align: right;
}

.price-config-status-value.ok {
  color: var(--accent);
}

/* The status token, as the page's alert hints and warn chips use: one amber
   on the price page, not two. */
.price-config-status-value.warn {
  color: var(--pels-status-on-warning);
}

.eyebrow--inline {
  margin: var(--spacing-2) 0 0;
}

.price-aware-grid {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}

.price-aware-grid__head,
.price-aware-grid__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--pels-control-track-3x) var(--pels-control-track-3x);
  gap: var(--spacing-3);
  align-items: center;
}

/* Typography from `.eyebrow` (markup); only structural row-header props here. */
.price-aware-grid__head {
  padding-bottom: var(--spacing-1);
  border-bottom: 1px solid var(--panel-border);
}

.price-aware-grid__col-head {
  text-align: center;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-1);
}

.price-aware-grid__row {
  padding-top: var(--spacing-3);
  padding-bottom: var(--spacing-1);
}

/* Typography from `.entity-name` (markup); structural only. */
.price-aware-grid__name {
  overflow-wrap: anywhere;
}

.price-aware-grid__tone-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
}

.price-aware-grid__tone-dot--cheap {
  background: var(--pels-status-good);
}

.price-aware-grid__tone-dot--expensive {
  background: var(--pels-status-critical);
}

.price-aware-grid__cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-1);
  justify-self: center;
}

/* Typography from `.eyebrow` (markup); hidden until the mobile stacked layout. */
.price-aware-grid__cell-label {
  display: none;
}

@media (max-width: 560px) {
  .price-aware-grid__head {
    display: none;
  }

  /* The two adjusters sit side by side only when each fits at its full width
     (two 48 px touch targets and a value up to "↓19.5 °C"); otherwise each
     takes its own line. Fixed columns made a 320 px phone's row wider than the
     card, which pushed every card on the page past the screen edge. */
  .price-aware-grid__row {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(7 * var(--spacing-6))), 1fr));
    gap: var(--spacing-2);
    padding: var(--spacing-3) 0 var(--spacing-1);
    border-top: 1px solid var(--panel-border);
  }

  .price-aware-grid__row:first-of-type {
    padding-top: var(--spacing-2);
    border-top: none;
  }

  .price-aware-grid__name {
    grid-column: 1 / -1;
    font-size: var(--font-size-md);
    line-height: var(--font-line-height-tight);
  }

  .price-aware-grid__cell-label {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-1);
  }
}

.value-adjuster {
  display: inline-flex;
  align-items: center;
  width: 100%;
  max-width: calc(7 * var(--spacing-6));
  min-height: var(--pels-touch-target-min);
  background: var(--pels-surface-container-lowest);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-full);
  padding: 2px;
  gap: 0;
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.value-adjuster:focus-within {
  border-color: var(--accent);
}

.value-adjuster__btn {
  width: var(--pels-touch-target-min);
  height: var(--pels-touch-target-min);
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--pels-text-secondary);
  border-radius: 50%;
  cursor: pointer;
  padding: 0;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.value-adjuster__btn:disabled {
  /* Reserved dim tier for disabled content (not the raised muted helper tier). */
  color: var(--pels-text-disabled);
  cursor: not-allowed;
  opacity: 0.45;
}

.value-adjuster__btn:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.value-adjuster__btn:hover:not(:disabled) {
  background: var(--pels-surface-container);
  color: var(--pels-text-primary);
}

.value-adjuster__btn:active:not(:disabled) {
  background: var(--pels-surface-container-high);
}

.value-adjuster__btn svg {
  display: block;
  width: 16px;
  height: 16px;
}

.value-adjuster__value {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--pels-text-primary);
  white-space: nowrap;
  cursor: default;
  user-select: none;
}

.value-adjuster__value:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.value-adjuster--up .value-adjuster__value {
  color: var(--accent);
}

.value-adjuster--down .value-adjuster__value {
  color: var(--color-semantic-warning, var(--pels-text-primary));
}

/* Smart tasks list (top-level Smart tasks tab) */

/* Populated-state hero rendered by `DeadlinesList.tsx` above the card list.
 * Reuses the `.plan-hero` / `.pels-hero` primitive (shape, padding, tone rim,
 * surface tier) so the four sibling panels keep an identical hero rhythm.
 * The wrapper class only adds the spacing rule below — without it the hero
 * sits flush against the first card and reads as part of the list, not as
 * the page-level answer to "are my deadlines on track?". */
.deadlines-list-hero {
  margin-bottom: var(--spacing-3);
}

/* Honest Main-only notice under the Smart tasks header (multi-home): rides
   the shared `.pels-notice-info` primitive, spaced as its own row between
   the hero and the task cards (or the Past tasks archive when empty). */
.deadlines-home-scope-notice {
  margin-bottom: var(--spacing-3);
}

/* Honest scope note on the Simulation-mode settings page (multi-home):
   spaced below the Main home's switch card it qualifies. */
.simulation-home-scope-note {
  margin-top: var(--spacing-3);
}

/* Hero subline affordance: when the subline names a specific card, the
 * subline wraps in this button so the user can scroll the named card into
 * view. Flat dark-theme treatment — the subline reads as ordinary
 * de-emphasised hero body copy (`color: inherit` picks up the
 * `.plan-hero__subline` supporting tone), with a trailing chevron as the only
 * tappability cue. Press feedback comes from the nested `md-ripple`, so there
 * is no permanent light/inverted container competing with the headline. The
 * negative-margin / padding pattern keeps the visual line tight while the
 * `min-height` token expands the hit area to the shared Android Material
 * 48 dp touch-target floor; `position: relative` anchors the ripple overlay. */
.deadlines-list-hero__nav-target {
  /* Neutral, text-toned ripple — a quiet subline affordance, not the green
     accent the surface cards use. Opacities match `.pels-surface-card` so the
     press feel is consistent across interactive surfaces. */
  --md-ripple-hover-color: var(--pels-text-supporting-color);
  --md-ripple-pressed-color: var(--pels-text-supporting-color);
  --md-ripple-hover-opacity: 0.06;
  --md-ripple-pressed-opacity: 0.1;
  position: relative;
  display: inline-flex;
  align-items: baseline;
  gap: var(--spacing-1);
  margin: calc(-1 * var(--spacing-2)) calc(-1 * var(--spacing-2));
  padding: var(--spacing-2) var(--spacing-2);
  min-height: var(--pels-touch-target-min);
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.deadlines-list-hero__nav-target:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
}

.deadlines-list-hero__nav-target-chevron {
  color: var(--muted);
  font-size: var(--font-size-md);
  line-height: 1;
  /* Real Material chevron (SVG at 1em) rather than a raw glyph, matching the
     list cards. Center it against the (possibly multi-line) subline text and
     keep it from shrinking so it always terminates the tappable row. */
  align-self: center;
  flex-shrink: 0;
}

.deadline-list {
  display: grid;
  gap: var(--spacing-2);
}

/* `.deadline-list-card` — layout + link decorator on `.pels-surface-card`
   (see canonical-primitives preamble above `.pels-surface-card`). Overrides
   the default grid template to a two-column [content | chevron] shape,
   tightens the inline gap, and carries the link-anchor reset (`color:
   inherit`, `text-decoration: none`) so the `<a>` host inherits text
   styling. Hover/focus elevation lift + outline ride the canonical
   `data-interactive` hook; only the accent border swap is local.

   Surface tier is bumped to `--pels-surface-container-high` (one step above
   the base `.pels-surface-card` `--color-surface-1`) so the live "happening
   now" deadlines read as the primary content tier — mirroring the hero-above-
   cards rhythm on Overview / Budget. The past-task `.plan-history-card` rows
   below intentionally stay on the base surface, so the active-vs-history split
   is a class-level tier distinction (no per-card "active" flag). The hero
   directly above shares this tier but keeps its inset highlight + larger
   padding / toned gradients as its own differentiator, so an active card never
   reads as identical to the hero. The tonal `--warn` / `--alert` treatments
   live on `__when-row` text only (never a card background), so this surface
   bump layers cleanly beneath them. */
.deadline-list-card {
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--spacing-2) var(--spacing-3);
  align-items: start;
  background: var(--pels-surface-container-high);
  color: var(--text);
  text-decoration: none;
}

.deadline-list-card:hover,
.deadline-list-card:focus-visible {
  border-color: var(--accent);
}

.deadline-list-card__header {
  grid-column: 1 / 2;
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  flex-wrap: wrap;
  min-width: 0;
}

/* Typography from `.pels-text-card-title` (markup); structural props only. */
.deadline-list-card__title {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Chips sit as one wrapping group. `flex: 0 1 auto` lets the whole block drop
   below the title when the kind + status (+ confidence) chips can't share its
   row, instead of a lone chip dangling at the title's edge at 320 px. The
   inner wrap keeps the chips legible (no shrink) and aligned to the title
   start once they own their row. */
.deadline-list-card__chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-2);
  /* `flex-basis: 100%` drops the chip group onto its own row beneath the title
     at every width (not only when it can't share the title's row), so the
     status + type chips read as a coherent block below the name and never
     crowd the chevron in the trailing column. */
  flex: 0 0 100%;
  min-width: 0;
}

/* A neutral muted status chip (e.g. "Building plan…", `--muted`) defaults to a
   container-high fill — the SAME surface the card itself uses — so it read as
   an invisible-fill pill barely distinguishable from the transparent outline
   type chip beside it. Lift the list-card status chip a surface step so the
   tonal (filled) status pill is clearly distinct from the outline type pill
   even in the common neutral state. Tonal status variants (ok/warn/alert)
   already carry their own container and are unaffected. */
.deadline-list-card__chips .plan-chip--muted {
  background: var(--pels-surface-container-highest);
  border-color: var(--color-border-strong);
}

.deadline-list-card__chevron {
  grid-column: 2 / 3;
  grid-row: 1 / -1;
  align-self: center;
  color: var(--muted);
  width: 1.25rem;
  height: 1.25rem;
}

.deadline-list-card__target {
  grid-column: 1 / 2;
  display: flex;
  align-items: baseline;
  gap: var(--spacing-2);
  font-variant-numeric: tabular-nums;
}

.deadline-list-card__target-label {
  color: var(--muted);
  font-size: var(--font-size-sm);
}

.deadline-list-card__target-value {
  color: var(--text);
  font-size: var(--font-size-md);
  font-weight: var(--font-weight-semibold);
}

/* "currently 18.5 °C" / "currently 45 %" line — sits to the right of the
 * Target value on the same baseline. Muted so the target stays the primary
 * value; tabular-nums keeps the digits aligned across cards in the list.
 */
.deadline-list-card__current {
  color: var(--muted);
  font-size: var(--font-size-sm);
  font-variant-numeric: tabular-nums;
}

.deadline-list-card__when {
  grid-column: 1 / 2;
  margin: 0;
  display: grid;
  gap: var(--spacing-1);
}

.deadline-list-card__when-row {
  display: grid;
  grid-template-columns: minmax(4.5rem, auto) minmax(0, 1fr);
  gap: var(--spacing-2);
  font-size: var(--font-size-sm);
}

.deadline-list-card__when-row dt {
  color: var(--muted);
}

.deadline-list-card__when-row dd {
  margin: 0;
  color: var(--text);
  min-width: 0;
  /* `break-word` (not `anywhere`) so the browser prefers word-boundary
     wraps and only splits mid-word as a last resort. The extra-permissions
     row's worst-case payload ("May go over daily budget if at risk · May
     limit lower-priority devices if at risk") used to split hyphenated
     compounds like "lower-pri | ority" at 320 px under `anywhere`; with
     `break-word` it wraps at the spaces / separator dot instead. */
  overflow-wrap: break-word;
  font-variant-numeric: tabular-nums;
}

/* Healthy / pending / queued / satisfied: the "Ready by" answer stays NEUTRAL
 * (primary text, no colour) and leans on weight alone for emphasis. Colour on
 * this line is reserved to signal a problem (see `--warn` / `--alert` below),
 * so the brand green isn't overloaded onto a passive timestamp the user can't
 * act on — it already carries nav-selection and primary actions. */
.deadline-list-card__when-row--neutral dd {
  color: var(--text);
  font-weight: var(--font-weight-semibold);
}

/* On at-risk / cannot-meet cards the "Ready by" line carries a PROBLEM tone so
 * the deadline row signals trouble without relying on the chip alone.
 * `--warn` matches the chip's warning token; `--alert` matches the critical
 * token. Resolved producer-side via `resolveSmartTaskListReadyByTone` so the
 * view dispatches on a stable slug. */
.deadline-list-card__when-row--warn dd {
  color: var(--pels-status-on-warning);
  font-weight: var(--font-weight-semibold);
}

.deadline-list-card__when-row--alert dd {
  color: var(--pels-status-on-critical);
  font-weight: var(--font-weight-semibold);
}

.deadline-list-card__when-row--muted dd {
  color: var(--muted);
  font-weight: var(--font-weight-medium);
}

/* Extra-permissions strip below the timestamp `<dl>`. Pulled out of the
 * grid because the joined value ("May go over daily budget · May limit
 * lower-priority devices") is much longer than the timestamp `dd`s and
 * previously wrapped into a narrow ragged column, reading as overflow
 * rather than deliberate metadata. The strip spans the card width so the
 * value gets the full content column and wraps at the natural separator. */
.deadline-list-card__extras {
  grid-column: 1 / 2;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  font-size: var(--font-size-sm);
}

/* `__extras-label` now composes the `.eyebrow` primitive in markup (it always
   mirrored that typography); no per-block type needed. The block class stays as
   the structural hook (symmetric with `__extras-value`, future tone modifier). */

.deadline-list-card__extras-value {
  color: var(--text);
  overflow-wrap: break-word;
}

.deadlines-history {
  margin-top: var(--spacing-4);
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

/* "Past tasks" archive entry point. PR2 spec §10: the archive needs a clear
 * entry point, so the heading reads as a real section header — primary text
 * tone at the section-headline role, not the previous muted title that blended
 * into the cards below. The top divider sits on the outline-variant role so the
 * separator reads as structure, not a hairline lost on the canvas. */
.deadlines-history__heading {
  margin: 0;
  font-size: var(--pels-text-section-headline-font-size);
  font-weight: var(--pels-text-section-headline-font-weight);
  line-height: var(--pels-text-section-headline-font-line-height);
  color: var(--pels-text-primary);
  border-top: 1px solid var(--pels-surface-outline);
  padding-top: var(--spacing-4);
}

/* First-run empty-state copy ("No completed tasks yet …"). Body type size with
 * the secondary text colour — deliberately NOT the dimmest `.muted` metadata
 * tone, because on a true first run this is the only Past-tasks copy on screen
 * and the spec (smart-tasks-surface-spec.md invariant 6) requires it stay
 * legible. It sits a step below the active list's "Add your first smart task"
 * primary CTA. */
.deadlines-history__empty {
  margin: 0;
  font-size: var(--pels-text-body-font-size);
  line-height: var(--pels-text-body-font-line-height);
  color: var(--pels-text-secondary);
}

/* v2.7.3 — weekly archive section breaks in the past-tasks list. The group
 * wraps the week heading + the card list for that week. */
.deadlines-history__week-group {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}

/* PR2 spec §10: the week divider must OUT-RANK the rows beneath it, so it reads
 * as a section break rather than a margin note. Promoted from the prior muted /
 * regular-weight treatment to the primary text tone at the title role
 * (semibold), and given an outline-variant rule above it so the eye lands on
 * the week boundary before scanning the rows. The first group's rule is
 * suppressed so it doesn't double the "Past tasks" heading's own top border. */
.deadlines-history__week {
  margin: 0;
  color: var(--pels-text-primary);
  font-size: var(--pels-text-title-font-size);
  font-weight: var(--pels-text-title-font-weight);
  line-height: var(--pels-text-title-font-line-height);
  font-variant-numeric: tabular-nums;
  padding-top: var(--spacing-3);
  border-top: 1px solid color-mix(in srgb, var(--pels-surface-outline) 70%, transparent);
}

.deadlines-history__week-group:first-of-type .deadlines-history__week {
  padding-top: 0;
  border-top: none;
}

/* v2.7.3 — per-device miss-streak summary above the weekly archive. Quiet
 * list of "Device — N of last M runs missed" rows so the recovering-from-
 * mistake user gets a pattern-at-a-glance signal without rescanning the
 * card stack. Plain block rows (no flex) because the row content includes
 * a literal em-dash text node between the two spans; the dash needs to flow
 * inline with the surrounding text. */
.deadlines-history__miss-streaks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}

.deadlines-history__miss-streak {
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-normal);
}

.deadlines-history__miss-streak-device {
  color: var(--text);
  font-weight: var(--font-weight-medium);
}

.deadlines-history__miss-streak-count {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* v2.7.4 — 7-day hit-rate strip above the past-tasks list (PR-10). Quiet,
 * single-line pill that gives the recovering-from-mistake user a first-
 * impression "how have my deadlines been doing this week?" signal without
 * scanning the per-row chips. Producer composes the text (`shared-domain/
 * deferredPlanHistoryReceipt.ts`); the view only paints it. Wraps on narrow
 * viewports — at 320 px the producer's `·`-joined string flows onto two
 * lines rather than truncating. */
.deadlines-history__summary-strip {
  margin: 0;
  color: var(--muted);
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-normal);
  font-variant-numeric: tabular-nums;
}

/* PR2 spec §7 — colour the recap counts to match the history-row badges so
 * the at-a-glance strip and the rows below speak the same colour language.
 * Tones bind to the same `--color-state-*-text` tokens the chips/containers
 * use. Counts read semibold against the muted strip base so the numbers pop;
 * the neutral lead label + hit-rate fragment stay in the strip's base tone. */
.deadlines-history__summary-count {
  font-weight: var(--font-weight-semibold);
}

.deadlines-history__summary-count--neutral {
  font-weight: inherit;
  color: var(--muted);
}

.deadlines-history__summary-count--positive {
  color: var(--color-state-positive-text);
}

.deadlines-history__summary-count--warning {
  color: var(--color-state-warning-text);
}

.deadlines-history__summary-count--muted {
  color: var(--pels-text-secondary);
}

.deadlines-history__summary-sep {
  color: var(--muted);
  font-weight: var(--font-weight-normal);
}

/* v2.7.4 — past-tasks device-filter chip row (PR-19). Sits above the weekly
 * archive so the user can collapse the list to one device. Wraps at the 320
 * px floor when many devices have history — each `.plan-chip--link` already
 * enforces the 48 dp tap-target minimum, so the row only owns spacing here. */
.deadlines-history__filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-2);
}


/* ─── Usage panel redesign ─────────────────────────────────────────────────── */

/* Cross-navigation return row (deep link back to the smart task that opened
   the Usage view). Un-carded, matching the `.pels-appbar` chrome grammar:
   arrow + label on a transparent row, context as quiet supporting text. */
.usage-return-link {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  flex-wrap: wrap;
  min-height: var(--pels-touch-target-min);
  color: var(--text);
}

.usage-return-link[hidden] {
  display: none;
}

.usage-return-link__anchor {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
  min-height: var(--pels-touch-target-min);
  padding: 0 var(--spacing-1);
  color: var(--text);
  font-weight: var(--font-weight-bold);
  text-decoration: none;
}

.usage-return-link__anchor svg {
  width: 1.25rem;
  height: 1.25rem;
  flex: 0 0 auto;
}

.usage-return-link__anchor:focus-visible {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.usage-return-link__context {
  min-width: 0;
  color: var(--muted);
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-cozy);
}

.usage-hero {
  position: relative;
}

.usage-hero .plan-hero__headline {
  font-variant-numeric: tabular-nums;
}

.usage-hero__delta {
  font-variant-numeric: tabular-nums;
}

.usage-hero__stats {
  display: grid;
  /* Two stats (This week / This month) — "Daily avg" was folded out as
   * it duplicated the "Typical weekday/weekend: X kWh" subline 24 px
   * above (TODO 2026-05-16). */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-2);
  padding-top: var(--spacing-3);
  border-top: 1px solid color-mix(in srgb, var(--pels-surface-outline) 70%, transparent);
}

.usage-hero__stat {
  display: grid;
  gap: var(--spacing-1);
  min-width: 0;
}

.usage-hero__stat-label {
  font-size: var(--pels-text-section-label-font-size);
  font-weight: var(--pels-text-section-label-font-weight);
  line-height: var(--pels-text-section-label-font-line-height);
  letter-spacing: 0;
  color: var(--pels-text-secondary);
}

.usage-hero__stat-value {
  color: var(--pels-text-section-headline-color);
  font-size: var(--pels-text-section-headline-font-size);
  font-weight: var(--pels-text-section-headline-font-weight);
  line-height: var(--pels-text-section-headline-font-line-height);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Role-toned status text for the M3 device-card surfaces (TODO M3 alignment
   items #3/#7 "reduce orange weight"). The device card is a plain neutral
   surface (no tonal container, no left state-rail — see the `.plan-card`
   comment above the `[data-state-kind=…]` opacity rules); its state reads
   through the bold state WORD plus this role-toned status text, so colour
   never carries the meaning alone. Heroes deliberately do NOT tone their
   headline: per the "one tonal story per surface" rule the hero rim/rail +
   status chip carry the signal and the metric value stays neutral
   (`--pels-text-display-color`) so the biggest number on the screen is also
   the most legible. Declared here — after the plain
   `.plan-card__reason|__status-line|__temp-reason|__state-label` rules — so
   the cascade stays specificity-ascending (stylelint
   `no-descending-specificity`). Applies to both the mobile dark and desktop
   light canvases. */
.plan-card[data-state-kind="held"] .plan-card__reason,
.plan-card[data-state-kind="held"] .plan-card__status-line,
.plan-card[data-state-kind="held"] .plan-card__temp-reason,
.plan-card[data-state-kind="held"] .plan-card__state-label {
  color: var(--color-state-warning-text);
}

.plan-card[data-state-kind="resuming"] .plan-card__reason,
.plan-card[data-state-kind="resuming"] .plan-card__status-line,
.plan-card[data-state-kind="resuming"] .plan-card__temp-reason,
.plan-card[data-state-kind="resuming"] .plan-card__state-label {
  color: var(--color-state-positive-text);
}

.plan-card[data-state-kind="unavailable"] .plan-card__reason,
.plan-card[data-state-kind="unavailable"] .plan-card__status-line,
.plan-card[data-state-kind="unavailable"] .plan-card__temp-reason,
.plan-card[data-state-kind="unavailable"] .plan-card__state-label {
  color: var(--color-state-negative-text);
}

.usage-card {
  gap: var(--pels-card-gap);
}

.usage-card__header {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--spacing-3);
  min-width: 0;
}

.usage-card__heading {
  display: grid;
  gap: var(--spacing-1);
  min-width: 0;
}

.usage-card__actions {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  flex-wrap: wrap;
  justify-content: flex-start;
}

.usage-metric-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: var(--spacing-3);
  min-width: 0;
}

.usage-metric {
  display: grid;
  gap: var(--spacing-1);
  min-width: 0;
}

/* Typography from `.metric-label` (markup). */
.usage-metric__label {
  letter-spacing: 0;
}

.usage-metric__value {
  font-size: var(--font-size-md, 1rem);
  font-weight: var(--font-weight-semibold);
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.usage-card--collapsible {
  padding: 0;
  gap: 0;
}

.usage-card--collapsible > md-elevation {
  border-radius: inherit;
}

.usage-card__summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-3);
  list-style: none;
  cursor: pointer;
  padding: var(--pels-card-padding);
  margin: 0;
  user-select: none;
}

.usage-card__summary::-webkit-details-marker {
  display: none;
}

.usage-card__summary-affordance {
  width: 1.25rem;
  height: 1.25rem;
  color: var(--pels-text-secondary);
  transition: transform var(--transition-fast);
}

.usage-card--collapsible[open] .usage-card__summary-affordance {
  transform: rotate(180deg);
}

.usage-card__collapsible-content {
  display: grid;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--pels-card-padding) var(--pels-card-padding);
  border-top: 1px solid color-mix(in srgb, var(--pels-surface-outline) 60%, transparent);
}

.usage-card__actions--standalone {
  justify-content: flex-start;
}

.usage-week-nav {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
}

/* Hourly-heatmap week-navigation chevrons (prev / next week). Before this
 * decorator, the bare `<md-text-button>` chevrons rendered as accent-tinted
 * 24 px icons with no visible button chrome — they read as decoration, not
 * affordance (TODO ~1599, live-walk 2026-05-16). Doubled-class selector
 * (0,2,0) beats MD Web's shadow `:host` defaults and Homey's host
 * `button { ... }` stylesheet without `!important`; no panel scope needed
 * since nothing else competes for these chevrons. Properties NOT redeclared
 * ride the canonical `md-text-button { min-height: var(--pels-touch-target-
 * min); ... }` rule (~line 364), so the 48 px touch-target floor stays
 * guaranteed.
 *
 * `min-width: var(--pels-touch-target-min)` squares the hit-box so a
 * single-glyph chevron meets the M3 48 × 48 floor; `--md-sys-color-primary`
 * routes the SVG fill through MD Web's CSS-custom-property API so the icon
 * resolves to `--text` (gray-50 ≈ #e6ecf5) on the `--pels-surface-container-
 * high` ground (≈ #232b38) — ~12:1 contrast, comfortably WCAG AAA. We avoid
 * the accent-green default that competed with the heatmap-cell palette. */
.heatmap-nav-chevron.heatmap-nav-chevron {
  --md-sys-color-primary: var(--text);
  min-width: var(--pels-touch-target-min);
  border: 1px solid var(--pels-surface-outline);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container-high);
}

.heatmap-nav-chevron.heatmap-nav-chevron[disabled] {
  opacity: var(--opacity-disabled);
}

.usage-footer {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-1) var(--spacing-1);
}

/* Drop the stat font earlier than the original 380 px breakpoint — the Homey
 * settings dialog is 480 px and two-digit kWh totals ("1642.1 kWh") overflow
 * the 124 px column and ellipsis-truncate before the original media query
 * fires. Tighten to ≤ 480 px so the smaller font carries the dialog's actual
 * width. */
@media (max-width: 480px) {
  .usage-hero__stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--spacing-1);
  }
  .usage-hero__stat-value {
    font-size: var(--pels-text-title-font-size);
  }
}

/* Solar card mount chain must be able to shrink below its content's
 * min-content width — see the Solar card section below. Declared before the
 * higher-specificity data-loading rule targeting the same element. */
.solar-usage-mount,
#solar-usage-card {
  min-width: 0;
}

/* Usage panel first-paint skeleton: while `#usage-panel[data-loading="true"]`
 * is set, hide the static `-- kWh` hero/cards/footer so the panel shows a
 * single shimmer placeholder instead of a half-populated wall. `power.ts`
 * flips to `data-loading="false"` after `renderPowerStatsFromRead` resolves. */
#usage-panel[data-loading="true"] > .usage-return-link,
#usage-panel[data-loading="true"] > .usage-hero,
#usage-panel[data-loading="true"] > .usage-card,
#usage-panel[data-loading="true"] > .solar-usage-mount,
#usage-panel[data-loading="true"] > .usage-footer {
  display: none;
}

#usage-panel[data-loading="false"] > .usage-loading-skeleton {
  display: none;
}

/* Per-home Usage honest state (multi-home): while the selected part of the
 * home cannot be served (`homeScope: unavailable`), the data sections hide and
 * the `#usage-scope-unavailable` notice card is the only content — zeros would
 * read as that home's measurements. Mirrors the `data-loading` contract above;
 * `power.ts` stamps `data-scope-read` on every stats render. */
#usage-panel[data-scope-read="unavailable"] > .usage-return-link,
#usage-panel[data-scope-read="unavailable"] > .usage-hero,
#usage-panel[data-scope-read="unavailable"] > .usage-card:not(.usage-scope-unavailable),
#usage-panel[data-scope-read="unavailable"] > .solar-usage-mount,
#usage-panel[data-scope-read="unavailable"] > .usage-footer {
  display: none;
}

/* ---------------------------------------------------------------------------
 * Usage tab — Solar card (display-only rows; see views/SolarUsageCard.tsx)
 *
 * Every column in the previous-days grid is minmax(0, …) and every cell may
 * wrap: the earlier min-content grid (with per-row nowrap prose) could not
 * shrink below ~316 px and blew the card out of the 320 px panel. The mount
 * chain carries min-width: 0 (declared above the data-loading block for
 * specificity ordering).
 * --------------------------------------------------------------------------- */

.solar-money {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  margin-top: var(--spacing-3);
}

.solar-money__line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-3);
}

.solar-money__label {
  color: var(--pels-text-supporting-color);
  font-size: var(--pels-text-supporting-font-size);
  line-height: var(--pels-text-supporting-font-line-height);
}

.solar-money__value {
  color: var(--pels-text-settings-trailing-color);
  font-weight: var(--pels-text-settings-trailing-font-weight);
  font-size: var(--pels-text-body-font-size);
  line-height: var(--pels-text-body-font-line-height);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.solar-money__note {
  margin: 0;
}

.solar-days {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  margin-top: var(--spacing-3);
  min-width: 0;
}

.solar-days__title {
  margin: 0 0 var(--spacing-1);
}

.solar-day-row {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--spacing-2);
  align-items: baseline;
  min-width: 0;
}

.solar-day-row--export-only {
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
}

.solar-day-row__header-cell {
  text-align: right;
}

.solar-day-row__day {
  color: var(--pels-text-caption-color);
  font-size: var(--pels-text-caption-font-size);
  font-weight: var(--pels-text-caption-font-weight);
  line-height: var(--pels-text-caption-font-line-height);
  overflow-wrap: anywhere;
}

.solar-day-row__value {
  color: var(--pels-text-secondary);
  font-size: var(--pels-text-caption-font-size);
  line-height: var(--pels-text-caption-font-line-height);
  font-variant-numeric: tabular-nums;
  text-align: right;
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
 * Device detail panel — Material Web 3 theming + layout
 * --------------------------------------------------------------------------- */

#device-detail-panel {
  /* Re-theme @material/web on this panel. On-primary / on-secondary /
     on-error sit on the saturated accent and error containers — use
     `--color-base-accent-on` (theme-invariant near-black) instead of
     `--color-base-black`, which the desktop palette flips light. */
  --pels-md-on-primary: var(--color-base-accent-on);
  --pels-md-primary-container: color-mix(in srgb, var(--accent) 20%, transparent);
  --pels-md-on-secondary: var(--color-base-accent-on);
  --pels-md-on-surface-variant: var(--color-semantic-text-secondary);
  --pels-md-on-error: var(--color-base-accent-on);
}

/* M3 switch: the component's default unselected track reads off
 * `--md-sys-color-surface-container-highest`, which sits within a step of the
 * PELS dark card surfaces — an unselected switch (e.g. "Compare with initial
 * plan", the Budget toggles) was nearly invisible at rest. Bind the
 * unselected track/outline/handle to tokens with real contrast against the
 * card so the off state is discoverable. Token-only per
 * `feedback_design_tokens`. */
md-switch {
  --md-switch-track-color: var(--pels-surface-container-high);
  --md-switch-track-outline-color: var(--pels-md-outline);
  --md-switch-handle-color: var(--pels-md-on-surface-variant);
}

/* Switch row: label + supporting hint on the leading side, switch trailing
 * right — the one canonical PELS switch grammar (matches the Daily-budget
 * enable/cheaper-hours rows). The switch is authored first in the DOM for a
 * stable label association, then flex `order` moves it to the trailing edge so
 * every enable-toggle in the app sits in the same position. The row's
 * `min-height` keeps the whole tap surface at the M3 48 px minimum. */
.md-switch-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-2) 0;
  min-height: var(--pels-touch-target-min);
  cursor: pointer;
}

.md-switch-row md-switch {
  order: 2;
  flex-shrink: 0;
  margin-left: auto;
}

.md-switch-row__content {
  order: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

/* Gated row: the switch is `disabled`, so the row must READ disabled too. The
 * whole content column dims together — dimming only the label inverted the
 * hierarchy (a ghost title over a darker description); one opacity over both
 * keeps the label the stronger line. `0.38` is the same disabled-content
 * opacity `.smart-task-edit__time-input:disabled` uses, so one form doesn't
 * speak two disabled languages. */
.md-switch-row--disabled {
  cursor: default;
}

.md-switch-row--disabled .md-switch-row__content {
  opacity: 0.38;
}

/* `.md-switch-row__label` composes `.pels-text-settings-label` in markup. */

/* Text-field row: M3 text field with a small caption underneath. */
.md-field-row {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  padding: var(--spacing-1) 0;
}

.md-field-row md-filled-text-field,
.md-field-row md-filled-select {
  width: 100%;
}

/* Stepped load row: compact M3 inputs side-by-side with a trailing remove.
 * Scoped to #device-detail-panel so these rules sort after the shared
 * .detail-stepped-* / .detail-mode-* baselines and satisfy stylelint's
 * no-descending-specificity rule. */
#device-detail-panel .detail-stepped-row > md-filled-text-field {
  width: 100%;
  min-width: 0;
}

#device-detail-panel .detail-stepped-row > .detail-stepped-remove {
  justify-self: end;
}

#device-detail-panel .detail-mode-row md-filled-text-field.detail-mode-temp {
  width: 120px;
  justify-self: end;
}

#device-detail-panel .detail-stepped-actions md-filled-button,
#device-detail-panel .detail-stepped-actions md-filled-tonal-button,
#device-detail-panel .detail-stepped-actions md-text-button {
  flex: 1 1 auto;
}

/* The "+ Add step" button stays intrinsic-width and leading-aligned rather than
 * stretching across the row like the save-bar pair. */
#device-detail-panel .detail-stepped-add md-text-button {
  flex: 0 0 auto;
}

/* Target-power-grid uses M3 fields directly inside the grid; ensure they
 * fill their cells. */
#device-detail-panel .target-power-grid md-filled-text-field {
  width: 100%;
  min-width: 0;
}

/* Control model row: full-width select with the hint below. */
#device-detail-control-model-row md-filled-select {
  width: 100%;
}

/* stylelint-disable no-descending-specificity */
/* Settings redesign detail overrides */
.card.panel:where(
  #devices-panel,
  #modes-panel,
  #limits-panel,
  #simulation-panel,
  #usage-panel,
  #advanced-panel,
  #weather-panel,
  #electricity-prices-panel,
  #price-aware-devices-panel,
  #recommendations-panel,
  #deadlines-panel
) {
  display: grid;
  gap: var(--spacing-3);
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  backdrop-filter: none;
}

.card.panel:where(
  #devices-panel,
  #modes-panel,
  #limits-panel,
  #simulation-panel,
  #usage-panel,
  #advanced-panel,
  #weather-panel,
  #electricity-prices-panel,
  #price-aware-devices-panel,
  #recommendations-panel,
  #deadlines-panel
).hidden {
  display: none;
}

:where(
  #electricity-prices-surface,
  #price-aware-devices-surface
) {
  display: grid;
  gap: var(--spacing-3);
  min-width: 0;
}

:where(
  #limits-panel,
  #simulation-panel,
  #weather-panel,
  #electricity-prices-panel,
  #price-aware-devices-panel
) .settings-form-card,
#modes-panel > .form-grid,
:where(#modes-panel) :where(#priority-form) {
  display: grid;
  gap: var(--spacing-3);
  margin: 0;
  padding: var(--spacing-4);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container-low);
  box-shadow: var(--shadow-sm);
}

:where(
  #advanced-panel,
  #device-detail-panel
) .settings-collapse,
#device-detail-panel .detail-diagnostics-card {
  margin: 0;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container-low);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

:where(
  #advanced-panel,
  #device-detail-panel
) .settings-collapse summary,
:where(
  #advanced-panel,
  #device-detail-panel
) .settings-collapse-header {
  min-height: 58px;
  padding: var(--spacing-3) var(--spacing-4);
  background: var(--pels-surface-container-low);
}

:where(
  #advanced-panel,
  #device-detail-panel
) .settings-collapse .collapse-content {
  display: grid;
  gap: var(--spacing-3);
  padding: var(--spacing-4);
  background: var(--pels-surface-container-low);
}

:where(
  #limits-panel,
  #simulation-panel,
  #advanced-panel,
  #electricity-prices-panel,
  #price-aware-devices-panel,
  #device-detail-panel
) :where(.field, .form-grid, .detail-setup-list, .detail-stepped-boost) {
  gap: var(--spacing-3);
  margin-bottom: 0;
}

:where(
  #limits-panel,
  #simulation-panel,
  #advanced-panel,
  #electricity-prices-panel,
  #price-aware-devices-panel,
  #device-detail-panel
) .field__label {
  /* Colour/weight now come from `.pels-text-settings-label` (markup); this
     panel context only tightens the line-height. */
  line-height: var(--font-line-height-tight);
}

:where(
  #limits-panel,
  #simulation-panel,
  #advanced-panel,
  #electricity-prices-panel,
  #price-aware-devices-panel,
  #device-detail-panel
) :where(.field__hint, .muted) {
  color: var(--pels-text-muted);
  font-size: var(--font-size-sm);
  line-height: var(--font-line-height-normal);
}

:where(
  #limits-panel,
  #simulation-panel,
  #advanced-panel,
  #price-aware-devices-panel,
  #device-detail-panel
) .checkbox-field {
  align-items: flex-start;
  gap: var(--spacing-3);
  padding: var(--spacing-3);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container);
}

:where(
  #limits-panel,
  #simulation-panel,
  #advanced-panel,
  #price-aware-devices-panel,
  #device-detail-panel
) .checkbox-field md-checkbox {
  flex: 0 0 auto;
  width: 20px !important;
  height: 20px !important;
  margin-top: 2px;
}

:where(
  #devices-panel,
  #modes-panel
) .device-list {
  display: grid;
  gap: var(--spacing-2);
  padding: 0;
  margin: 0;
  list-style: none;
}

#devices-panel .control-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) repeat(3, minmax(44px, 64px));
  gap: var(--spacing-2);
  align-items: center;
  min-width: 0;
  padding: var(--spacing-3);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container-low);
  box-shadow: var(--shadow-sm);
}

#devices-panel .control-row:last-child {
  border-bottom: 1px solid var(--panel-border);
}

#devices-panel .device-row__name {
  margin: 0;
  min-width: 0;
}

#devices-panel .device-row__title {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}

#devices-panel .device-row.clickable:hover {
  background: var(--pels-surface-container-low);
}

/* Refresh devices is a quiet, un-carded action — a text button with a refresh
 * icon sitting flush below the list, not another boxed card. */
#devices-panel .list__footer {
  align-items: flex-start;
  margin-top: var(--spacing-1);
}

/* Modes panel action row: "+ New mode" (tonal) leads, with Rename and Delete
 * as quiet text buttons. Delete carries the destructive tone. They wrap
 * naturally at narrow widths rather than stretching to fill a grid. */
#modes-panel .mode-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-2);
  align-items: center;
  margin-top: var(--spacing-1);
}

/* "+ New mode" sits on the `--panel` (surface-container-low) modes card. The
 * default tonal container (`--color-surface-3`) is only one tier up and washes
 * into the card. Lift it to the highest surface tier so the button's edge reads
 * clearly as a tappable control against the card surface. */
#modes-panel .mode-actions md-filled-tonal-button {
  --md-filled-tonal-button-container-color: var(--pels-surface-container-highest);
}

/* On-demand name editor, revealed by "+ New mode" / "Rename". The field spans
 * the width with its confirm/cancel pair trailing right. */
#modes-panel .mode-name-editor {
  display: grid;
  gap: var(--spacing-2);
  margin-top: var(--spacing-2);
}

#modes-panel .mode-name-editor md-filled-text-field {
  min-width: 0;
  width: 100%;
}

#modes-panel .mode-name-editor__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--spacing-2);
}

#modes-panel .mode-row {
  /* Temperature column: 116–132 px. Material's light fine-pointer number
   * field reserves room for native spinner chrome plus the °C suffix; 112 px
   * leaves only ~25 px for the input and clips the second digit. The extra
   * token-sized step keeps two-digit targets visible at 480 px while the name
   * column continues to wrap normally. */
  grid-template-columns: auto minmax(0, 1fr) minmax(116px, 132px) auto;
  gap: var(--spacing-3);
  padding: var(--spacing-3);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container-low);
  box-shadow: var(--shadow-sm);
}

/* Norwegian device names like "Termostat hovedsoverom" are long enough to
 * collide with the number-input + priority-badge cluster on the right of a
 * priority row at the 480 px supported width — without the wrap override
 * below, the name was rendering as "hovedsoveron", the final `m` lost under
 * the next column. The base `.mode-row .device-row__name` rule has
 * `white-space: nowrap` + `text-overflow: ellipsis`; the 430 px breakpoint
 * already relaxes it, but at 431–480 px the user still saw clipped text. Drop
 * the nowrap inside the modes priority list at all widths — the panel is
 * capped at 480 px so the name column is always short, and a 2-line wrap is
 * acceptable and complete vs. a silent character cut. `break-word` prefers
 * breaks at word boundaries and only falls back to mid-word when no
 * opportunity exists — matches the segmented control's wrap strategy and
 * avoids awkward mid-glyph splits when a space or hyphen is available. */
/* The cell also carries the meter-area home badge once areas are in use, so it
 * wraps onto a second line rather than squeezing the name and the badge into
 * one. Single-home installs render no badge and see no change. */
#modes-panel .mode-row .device-row__name {
  white-space: normal;
  overflow-wrap: break-word;
  text-overflow: clip;
  flex-wrap: wrap;
  row-gap: var(--spacing-1);
}

#modes-panel .mode-row:last-child {
  border-bottom: 1px solid var(--panel-border);
}

#modes-panel .drag-handle {
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: var(--radius-md);
}

#modes-panel .mode-target-input {
  width: 100%;
}

#modes-panel .priority-badge {
  margin-left: 0;
}

#device-detail-panel.slide-panel {
  max-width: 480px;
  background: var(--bg);
}

#device-detail-panel .slide-panel__header {
  /* Compact app-bar strip: the 48 px back control + hairline defines the row
     height; only a whisper of vertical padding so the chrome stays chrome. */
  padding: var(--spacing-1) var(--spacing-3);
  background: var(--pels-surface-container-low);
}

#device-detail-panel .slide-panel__content {
  display: grid;
  /* Reserve each card's full height. Firefox otherwise gives the Smart task
   * card a zero-height auto row, covering the activity log below it. */
  grid-auto-rows: max-content;
  /* `start` keeps the auto rows content-sized. Without it the default
   * `align-content: stretch` distributes the panel's leftover viewport
   * height (the slide panel is 100vh and this grid is its `flex: 1` body)
   * equally across every row, inflating the hero and every collapsed
   * section with hundreds of px of dead space whenever the content is
   * shorter than the screen. */
  align-content: start;
  gap: var(--spacing-3);
  padding: var(--spacing-3);
}

#device-detail-panel .detail-section {
  margin: 0;
}

#device-detail-panel .segmented {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
}

/* The `#device-detail-panel .segmented__option` override block was removed:
 * its `min-height`, `line-height`, `white-space`, `overflow-wrap`, and
 * `word-break` declarations duplicated the base `.segmented .segmented__option`
 * rule above. The base now carries the `break-word` + 96 px `min-width` floor
 * needed for the slide-over panel as well. */

#device-detail-panel :where(
  .detail-mode-row,
  .detail-stepped-row
) {
  padding: var(--spacing-3);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container);
}

#device-detail-panel .detail-mode-list,
#device-detail-panel .detail-stepped-list {
  display: grid;
  gap: var(--spacing-2);
}

#device-detail-panel .detail-stepped-actions,
#device-detail-panel .detail-actions,
#device-detail-panel .form__actions {
  gap: var(--spacing-2);
}

#price-aware-devices-panel .value-adjuster {
  background: var(--pels-surface-container);
}

@media (max-width: 430px) {
  /* Match the base `.card.panel:where(...)` reset specificity (0,2,0) so this
     narrow-viewport override actually wins. A bare `:where()` (0,0,0) loses to
     the earlier base rule regardless of source order; with equal specificity
     the later-in-source rule wins. */
  .card.panel:where(
    #devices-panel,
    #modes-panel,
    #limits-panel,
    #simulation-panel,
    #usage-panel,
    #advanced-panel,
    #weather-panel,
    #electricity-prices-panel,
    #price-aware-devices-panel,
    #recommendations-panel,
    #deadlines-panel
  ) {
    gap: var(--spacing-2);
  }

  #devices-panel .control-row {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  #devices-panel .pels-device-card__row {
    grid-template-columns: repeat(3, minmax(0, 1fr)) var(--pels-device-detail-col-width);
    gap: var(--spacing-1);
    padding-block: var(--spacing-2);
  }

  #devices-panel .control-row .device-row__name {
    grid-column: 1 / -1;
  }

  #devices-panel .pels-device-card__name {
    grid-column: 1 / -1;
  }

  #devices-panel .pels-device-card__cell {
    justify-content: center;
    min-height: 40px;
  }


  #modes-panel .mode-row {
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--spacing-2);
  }

  #modes-panel .mode-row .device-row__name {
    white-space: normal;
  }

  #modes-panel :where(
    .mode-target-input,
    .mode-target-placeholder
  ) {
    grid-column: 1 / -1;
    width: 100%;
    justify-self: stretch;
  }

  #device-detail-panel.slide-panel {
    max-width: 100%;
  }

  :where(
    #advanced-panel,
    #device-detail-panel
  ) .settings-collapse summary,
  :where(
    #advanced-panel,
    #device-detail-panel
  ) .settings-collapse-header {
    align-items: flex-start;
  }

  #device-detail-panel :where(
    .detail-deltas,
    .target-power-grid,
    .detail-deadline-form .form-grid
  ) {
    grid-template-columns: 1fr;
  }

  /* At ≤430 px the device-detail slide-over collapses to a single column;
   * the segmented control follows so the option labels stay readable instead
   * of being squeezed into a 96 px floor that wraps mid-word. Lives inside
   * the existing narrow-viewport media query rather than under a per-page
   * `--power-limiting` modifier so the primitive stays free of panel-scoped
   * tonal variants. */
  #device-detail-panel .segmented {
    grid-template-columns: 1fr;
  }
}
/* stylelint-enable no-descending-specificity */

/* ============================================================================
 * Desktop component overrides triggered by the 2026-05-17 live-walk audit.
 *
 * Placed at the end of the stylesheet so they win cascade ties against the
 * matching base rules defined earlier in the file. Each rule lives inside
 * the same `(hover: hover) and (pointer: fine)` gate as the desktop palette
 * so mobile stays byte-identical to today.
 * ============================================================================ */

@media (hover: hover) and (pointer: fine) {
  /* Hero — idle state. The default `--pels-surface-container-high` (= mono-10
     `#dddee2` on desktop) reads as a heavy gray slab against the ice-white
     page bg. Lift heroes onto a white surface with a hairline neutral border
     + Homey's standard card shadow — matches Homey's own panel pattern. */
  .plan-hero,
  .pels-hero {
    background: var(--pels-surface-container-lowest);
    border-color: var(--color-border-default);
    box-shadow: var(--shadow-card);
  }

  /* Hero — tone states (desktop light). The base rules already supply the
     M3 status treatment for every canvas: neutral surface + neutral hairline
     on three sides + a 4px role-coloured left state-rail + a role-toned
     headline (the semantic also rides the inline chips / meter overflow
     segments / status text). On the desktop light canvas the only deviation
     from the mobile-dark base is the surface tier: the base
     `--pels-surface-container-high` (= mono-10 `#dddee2`) reads as a heavy
     gray slab against the ice-white page, so lift the toned hero onto white
     like the idle hero above. The per-side `border-color` (and therefore the
     left rail) flows through from the base `[data-tone=…]` rules unchanged. */
  @media (min-width: 481px) {
    .plan-hero[data-tone="good"],
    .pels-hero[data-tone="good"],
    .plan-hero[data-tone="warn"],
    .pels-hero[data-tone="warn"],
    .plan-hero[data-tone="alert"],
    .pels-hero[data-tone="alert"],
    .plan-hero[data-tone="info"],
    .pels-hero[data-tone="info"] {
      background: var(--pels-surface-container-lowest);
    }
  }

  /* Power meter — fill on track. `--pels-status-good` is `#5eead4` (light
     teal) which only reaches ~1.2 : 1 on the desktop light-grey track. Use
     the darker `--color-base-good-soft` for graphical fills so the bar reads
     at AA on the light canvas (~3.7 : 1 on track, ~7.5 : 1 on white card).
     Same swap for warning / critical so over-threshold stripes stay readable. */
  .pels-meter-track {
    background: var(--pels-surface-container-high);
  }
  .pels-meter-track__fill,
  .pels-meter-segments__seg--managed {
    background: var(--color-state-positive-text);
  }
  .pels-meter-track__fill[data-tone="warning"],
  .pels-meter-segments__seg--background[data-over-safe-pace],
  .pels-meter-segments__seg--managed[data-over-safe-pace] {
    background: var(--color-state-warning-text);
  }
  .pels-meter-track__fill[data-tone="critical"] {
    background: var(--color-state-negative-text);
  }

  /* Material filled text fields / selects — Homey-style inset white. Mobile's
     dark canvas uses `--color-surface-4` (a darker neutral than the card)
     for filled fields; on a light canvas that same token resolves to
     `#dddee2` which reads as a heavy gray slab. Use white container with a
     visible hairline border, matching Homey's own input pattern. */
  md-filled-text-field,
  md-filled-select,
  .smart-task-edit__time-input {
    --md-filled-text-field-container-color: var(--color-surface-1);
    --md-filled-select-text-field-container-color: var(--color-surface-1);
    border: 1px solid var(--color-border-medium);
    border-radius: var(--radius-md);
  }

  /* Device cards — M3 status pattern (desktop light). The base
     `[data-state-kind=…]` rules already supply the full treatment for every
     canvas: neutral base surface + neutral hairline on three sides + a 4px
     role-coloured left state-rail + role-toned status text. On the desktop
     light canvas the only deviation is to reset the `unavailable` opacity so
     the role-coloured status text reads at full contrast on white; the rail
     and the per-side border flow through from the base rules unchanged. */
  .plan-card[data-state-kind="held"],
  .plan-card[data-state-kind="resuming"],
  .plan-card[data-state-kind="unavailable"] {
    opacity: 1;
  }

  /* Smart-tasks list — `.plan-history-card` walks the canonical
     `.pels-surface-card` shell (neutral white surface + hairline border)
     and needs no override here.

     `.deadline-list-card` carries a dark-canvas surface bump to
     `--pels-surface-container-high` so the live deadlines read as the
     primary tier above the history rows. On the desktop light palette that
     token resolves to `--color-surface-4` (`#dddee2`, mono-10) — a heavy
     gray slab against the white page, the same inversion the hero-idle
     override above neutralises. So on desktop we keep the active card on the
     white component surface and carry the active-vs-history distinction with
     the same `--shadow-card` lift the hero uses, instead of a coloured tier. */
  .deadline-list-card {
    background: var(--pels-surface-container-lowest);
    box-shadow: var(--shadow-card);
  }
}

/* Tonal hero gradient restoration that previously lived here
   (`#deadline-plan-panel .plan-hero[data-tone="…"]` + the matching
   `#deadlines-panel .deadlines-list-hero[data-tone="…"]` overrides) has been
   folded into the shared `.pels-hero` primitive: the desktop neutralisation
   above is now gated to `(min-width: 481px)`, so the default tonal gradients
   at the top of this file carry the per-tone story for every surface at the
   narrow webview width without a second per-surface declaration site. */

/* ── Weather insight (hidden, flag-gated) ─────────────────────────────────────
   Budget-page Tomorrow/state card, detail view, coverage band, and the
   Settings device pickers. Spec: notes/weather-insight-spec.md. Cards reuse
   the budget primitives (.pels-surface-card / .budget-redesign-card /
   .budget-settings-list); only weather-specific pieces live here. */

.weather-card__title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-2);
}

.weather-card__title-row .plan-chip {
  flex-shrink: 0;
}

.weather-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-2);
  justify-content: flex-end;
}

.weather-card__verdict {
  margin: 0;
  color: var(--text);
  font-size: var(--pels-text-answer-font-size);
}

.weather-card__verdict--warn,
.weather-card__reason {
  color: var(--color-state-warning-text);
}

.weather-card__row-note {
  margin: 0;
}

.weather-card__lowhigh {
  margin: 0;
}

.weather-card__attribution {
  margin: 0;
  font-size: var(--pels-text-caption-font-size);
  /* Secondary tone — matches the detail-view footer attribution so the one CC-BY
     string reads at a single grey across surfaces (not the dimmer .muted alias). */
  color: var(--pels-text-secondary);
}

.weather-card__chips {
  display: flex;
  gap: var(--spacing-2);
}

.weather-summary__headline {
  margin: 0;
}

.weather-numbers__details > .weather-numbers__summary {
  display: flex;
  align-items: center;
  gap: var(--spacing-1);
  min-height: 48px;
  cursor: pointer;
  list-style: none;
}

.weather-numbers__summary::-webkit-details-marker {
  display: none;
}

/* Chart viewport height is a physical-pixel value (cf. the deadline charts);
   must match WEATHER_CHART_HEIGHT in views/WeatherInsightChart.tsx (the
   cold-mount fallback size). */
.weather-insight-chart {
  width: 100%;
  min-width: 0;
  height: 220px;
  touch-action: pan-y;
}

/* Coverage band: token-styled HTML strip sharing the chart's temperature
   domain. Shade = usable-day count (solid ≥ 14, light 4–13, outline ≤ 3);
   the hollow dot repeats tomorrow's position (marker grammar: hollow =
   projected). */
.weather-coverage {
  display: grid;
  gap: var(--spacing-1);
}

.weather-coverage__track {
  --weather-coverage-dot-x: 0%;

  position: relative;
  display: flex;
  gap: var(--spacing-1);
  height: var(--spacing-3);
}

.weather-coverage__bin {
  flex: 1;
  border-radius: var(--radius-xs);
  background: var(--color-role-accent);
}

.weather-coverage__bin--solid {
  opacity: 0.9;
}

.weather-coverage__bin--light {
  opacity: 0.4;
}

.weather-coverage__bin--outline {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--pels-surface-outline);
}

.weather-coverage__dot {
  position: absolute;
  top: 50%;
  left: var(--weather-coverage-dot-x);
  width: var(--spacing-2);
  height: var(--spacing-2);
  transform: translate(-50%, -50%);
  border: 2px solid var(--color-role-accent);
  border-radius: var(--radius-full);
  background: var(--pels-surface-container);
}

.weather-coverage__labels {
  display: flex;
  justify-content: space-between;
  color: var(--pels-text-muted);
  font-size: var(--font-size-xs);
}

.weather-coverage__caption {
  margin: 0;
}

/* Scatter legend swatches (marker grammar: solid dot / line / hollow dot). */
.weather-legend__swatch--day {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--pels-text-supporting-color);
}

.weather-legend__swatch--estimate {
  height: 2px;
  background: var(--color-role-accent);
}

.weather-legend__swatch--tomorrow {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: transparent;
  border: 2px solid var(--color-role-accent);
}

.weather-device-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-2);
}

/* Native select/input primitive for device pickers and text fields
   (form-styling rule: native input/select + .field, never md-outlined-*).
   Padding is restated because Homey's host stylesheet opens with a universal
   `* { padding: 0 }` reset. Native dropdown arrow is kept (no
   `appearance: none`). `.pels-input` is the text-input twin (Multiple meters
   name field) so both share one primitive. */
.pels-select,
.pels-input {
  width: 100%;
  min-height: 48px;
  padding: var(--spacing-2) var(--spacing-3);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background: var(--pels-surface-container-high);
  color: var(--text);
  font: inherit;
}

.weather-settings-section {
  display: grid;
  gap: var(--spacing-3);
}

/* ── Multiple meters (homes management) ────────────────────────────────────
   Layout-only styling on top of the shared card/field/button/notice
   primitives: the section stack, the per-area list rows, and the inline
   delete-confirm strip. Warn moments ride `.pels-notice-warning`. Tokens
   only. */
.homes-settings {
  display: grid;
  gap: var(--spacing-3);
}

.homes-settings__list,
.homes-settings__editor {
  display: grid;
  gap: var(--spacing-3);
}

/* Row rhythm: actions sit inline with the name/meta block (vertically
   centered, pushed trailing) instead of on their own band below it; the
   delete-confirm strip breaks to a full-width row beneath. */
.homes-settings__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-2);
  padding-block: var(--spacing-2);
  border-bottom: 1px solid var(--panel-border);
}

.homes-settings__row:last-of-type {
  border-bottom: none;
  padding-bottom: 0;
}

.homes-settings__row-main {
  display: grid;
  flex: 1 1 12rem;
  min-width: 0;
  gap: var(--spacing-1);
}

.homes-settings__row-supporting {
  margin: 0;
}

.homes-settings__actions {
  justify-content: flex-end;
}

.homes-settings__row .homes-settings__actions {
  margin-top: 0;
  margin-left: auto;
}

.homes-settings__confirm {
  display: grid;
  flex: 1 1 100%;
  gap: var(--spacing-2);
}

.homes-settings__confirm-body {
  margin: 0;
}

.homes-settings__editor-title {
  margin: 0;
}

.homes-settings__main-note {
  margin: 0;
}

.homes-settings__add {
  display: grid;
  gap: var(--spacing-2);
  justify-items: start;
}

/* Main-home meter nudge: railed notice between the list and the Add
   affordance; normal-tone body (the rail carries the warning), trailing
   cross-link (weather cross-link idiom). */
.homes-settings__notice {
  display: grid;
  gap: var(--spacing-2);
}

.homes-settings__notice-body {
  margin: 0;
}

.homes-settings__notice-link {
  justify-self: start;
}

/* ── Global home scope bar (multi-home) ────────────────────────────────────
   Shell chrome below the global banners: it names which part of the home the
   page is about. A supporting `Showing` label plus a content-sized Material
   menu button — chrome, not a form field, so it does not wear the filled-field
   costume that out-ranked the tab strip beside it. One row exactly one touch
   target tall, no card padding, so the bar costs the shell nothing more than
   that.

   STICKY, because the scope must hold at every scroll position: the panel runs
   well past the fold, and a bar that scrolls away leaves the page silently
   unscoped exactly where a wrong edit costs most. The alternative — restating
   the home in the panel's own title — was measured and rejected: the app bar is
   not sticky either, so the title left the viewport ~51 px of scroll after the
   bar at 320 px and never scrolled at all at 480 px.

   The background is the page's own, so content passing underneath is covered
   without the bar reading as a card; the block padding plus its negating margin
   widen only the painted area, never the layout. `z-index` sits below the toast
   (20) and the modal layer (1000), above ordinary panel content.

   The mount ships `hidden`: `display: none` neutralises every rule here, so a
   home with no meter areas keeps a pixel-identical shell. */
#home-scope-bar {
  position: sticky;
  top: 0;
  z-index: 6;
  background: var(--bg);
  padding-block: var(--spacing-2);
  margin-block: calc(-1 * var(--spacing-2));
}

.scope-bar {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  min-height: var(--pels-touch-target-min);
}

/* Supporting role, not a field label: the home name is the loud element and
   `Showing` only introduces it, so it must not compete with the `Hard cap` /
   `Safety margin` labels a row below. Uses the repo's supporting type role
   rather than a raw font-size primitive. */
.scope-bar__label {
  flex: 0 0 auto;
  white-space: nowrap;
  color: var(--pels-text-supporting-color);
  font-size: var(--pels-text-supporting-font-size);
  line-height: var(--pels-text-supporting-font-line-height);
}

/* Positioning context for the menu, and the flex item that may shrink. */
.scope-bar__anchor {
  position: relative;
  display: flex;
  min-width: 0;
}

/* Content-sized: the button is as wide as the home it names, so a real
   Norwegian area name ("Leilighet i underetasjen") renders in full at 320 px
   instead of truncating, and 480 px gains no dead field. `max-width` is the
   only bound — a pathological name shrinks the label rather than pushing the
   row off-screen. A real container colour puts it one tier ABOVE the page and
   with the nav/cards, so the control that governs the whole page is not the
   quietest thing on screen: page -> chip -> nav/cards -> menu. Focus is left to
   Material's own `md-focus-ring`; a manual outline here would draw a second
   concentric ring (no `md-*` element in this stylesheet does that). */
.scope-bar__chip {
  min-width: 0;
  max-width: 100%;
  /* The Homey host stylesheet applies `* { padding: 0 }`, which outranks
     Material's `:host` leading/trailing padding — without this the label
     collapses against the state-layer edge and the caret spacing goes
     asymmetric on-device. Same restoration the `.homes-settings` buttons and
     `.banner__action` already carry. */
  padding-inline: var(--spacing-4);
  --md-filled-tonal-button-container-color: var(--pels-surface-container-low);
  --md-filled-tonal-button-label-text-color: var(--text);
  --md-filled-tonal-button-container-shape: var(--radius-md);
  --md-filled-tonal-button-container-height: var(--pels-touch-target-min);
}

/* The menu is Material's own surface, so it follows the app's dark scheme
   rather than the `light dark` the document advertises. It is bounded like the
   button: the menu is where a truncated name has to stay readable, so it must
   not be the one unbounded element in the row. The selected row needs a tier
   ABOVE its own container — Material's default resolves through
   `secondary-container`, which this app maps to an untinted grey, so the active
   scope would read as a dent instead of a highlight. */
.scope-bar__menu {
  color-scheme: dark;
  max-width: calc(100vw - 2 * var(--spacing-4));
  --md-menu-container-color: var(--pels-surface-container-high);
  --md-menu-item-selected-container-color: var(--pels-surface-container-highest);
}

/* Per-home Limits & safety surface (multi-home U3): the meter-area editor
   stacked above the static Main-home form (which home is selected comes from
   the scope bar above). Mirrors the .homes-settings grid-stack rhythm so the
   two multi-home surfaces read as one system.

   The area's name is user-authored, unbounded, and interpolated in several
   places here (status heading, simulation notice, inactive notice). A single
   long unbroken word would otherwise set the min-content width of the whole
   panel and push it past 320 px, so breaking inside a word is permitted
   throughout this block — it is inherited, which is exactly the point. */
.home-limits {
  display: grid;
  gap: var(--spacing-3);
  margin-bottom: var(--spacing-4);
  overflow-wrap: anywhere;
}

.home-limits__fields {
  display: grid;
  gap: var(--spacing-4);
}

.home-limits__simulation,
.home-limits__status {
  display: grid;
  gap: var(--spacing-2);
}

.home-limits__sim-notice {
  margin: 0;
}

/* Title + posture chip on one baseline-shared row. The title carries a scoped
   home name, and area names are user-authored with no maximum length, so the
   heading must be allowed to shrink and to break inside an unbroken word —
   otherwise its min-content width forces the card to overflow at 320 px. The
   chip keeps its intrinsic size; it is the heading that yields. */
.home-limits__status-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-2);
}

.home-limits__status-head h3 {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.home-limits__status-head .plan-chip {
  flex: 0 0 auto;
}

.home-limits__status-line {
  margin: 0;
}

/* Accounts for the app-global settings card hidden while an area is selected.
   Carded like its siblings; the paragraph inside carries no margin of its own. */
.home-limits__global-note p {
  margin: 0;
}

.weather-settings-section__hint {
  margin: 0;
}

/* Trailing cross-link to the Budget payoff: left-aligned like the back button,
   not the full-width stretch a bare grid child would otherwise take. */
.settings-cross-link {
  justify-self: start;
}

/* Live picker status line, positive/confirming tone (accent) — generalized
   from the weather pickers (`.weather-picker-status--ok` stays as the
   original alias); the Multiple meters zone preview reuses it. The warning
   tone reuses the canonical .field__hint--alert primitive, so it has no rule
   here. */
.pels-hint-ok,
.weather-picker-status--ok {
  color: var(--accent);
  font-weight: var(--font-weight-medium);
}

/* Backfill liveness spinner: small, accent-toned, sitting under the card body. */
.weather-backfill-spinner {
  --md-circular-progress-size: 32px;
  --md-circular-progress-active-indicator-color: var(--accent);

  margin-top: var(--spacing-2);
}

/* The section's answer — which car, and how full — so it reads as the headline
   rather than as a footnote under the list it drives. */
.detail-car-status {
  margin: 0;
  font-weight: var(--font-weight-medium);
  color: var(--color-semantic-text-primary);
}

.detail-car-status--secondary {
  font-weight: var(--font-weight-normal);
  color: var(--color-semantic-text-secondary);
}

.detail-car-list {
  display: flex;
  flex-direction: column;
}

.detail-car-row {
  gap: var(--spacing-3);
  cursor: pointer;
}

.detail-car-row input[type='checkbox'] {
  order: 2;
  flex-shrink: 0;
  margin-left: auto;
  width: var(--spacing-4);
  height: var(--spacing-4);
  accent-color: var(--color-role-accent);
}

.detail-car-confirm,
.detail-car-clear {
  align-self: flex-start;
  margin-bottom: var(--spacing-3);
  max-width: 100%;
}

/* Manual problem reports */
.support-report,
.support-report__form,
.support-report__result,
.support-report__copy {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
  min-width: 0;
}

#report-problem-panel .support-report md-filled-text-field,
#report-problem-panel .support-report md-filled-select {
  width: 100%;
  min-width: 0;
}

.support-report p,
.support-report h3 {
  margin: 0;
}

.support-report__text {
  box-sizing: border-box;
  width: 100%;
  padding: var(--spacing-3);
  overflow-wrap: anywhere;
  -webkit-user-select: text;
  user-select: text;
  font-size: var(--pels-text-supporting-font-size);
  color: inherit;
  background: var(--pels-surface-container-low);
  border: 0;
  border-radius: var(--radius-md);
}

.support-report__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-2);
}

#report-problem-panel .support-report md-outlined-button {
  padding-inline: var(--spacing-4);
}

.support-report__reference {
  overflow-wrap: anywhere;
}

.support-report__link {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.support-report__notice:empty {
  display: none;
}

/* Guided setup reuses the normal page controls and shared surface roles. */
.setup-journey,
.setup-journey__devices,
.setup-journey__device,
.setup-journey__summary {
  display: grid;
  gap: var(--spacing-3);
}

.setup-journey,
.setup-journey__device {
  padding: var(--spacing-4);
}

.setup-journey__summary {
  margin: 0;
}

.setup-journey__summary dd {
  margin: 0;
  color: var(--pels-text-secondary);
}

.setup-journey__summary dt {
  font-weight: var(--font-weight-semibold);
}

[data-setup-journey-panel] > .pels-appbar,
[data-setup-journey-panel] .device-control-legend,
[data-setup-journey-panel="power"] #settings-limits-form,
[data-setup-journey-panel="hardCap"] #settings-limits-global {
  display: none;
}
