/* ---------------------------------------------------------------- fonts --
 * Brand typography embedded as subsetted base64 woff2 so the SPA is fully
 * offline from flash — web_server serves exactly one CSS file and has no route
 * for separate .woff2 assets. Inter = body (Latin subset, carries all dynamic
 * user/entity text). Crimson Pro = display serif for fixed UI headings + brand
 * names only, so it is subset to ASCII + the few symbols Inter carries
 * (U+00A0/B0/D7 + typographic punctuation); any rarer glyph in a heading falls
 * back per-glyph to Georgia via --ww-font-display. Subsets built from the
 * upstream OFL variable fonts; see tools note in the repo. Keep weights minimal
 * to bound firmware size. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(data:font/woff2;base64,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) format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(data:font/woff2;base64,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) format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(data:font/woff2;base64,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) format("woff2");
}

@font-face {
  font-family: "Crimson Pro";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(data:font/woff2;base64,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) format("woff2");
}


:root {
  /* palette — warm green refresh (soothing, low-contrast) */
  --ww-bg: #f4f1e8;
  --ww-surface: #fcfaf3;
  --ww-surface-muted: #ebe6d8;
  --ww-text: #20211a;
  /* Darkened from #6b6f62 in the accessibility pass. The old value cleared AA on
     --ww-surface (4.93) and --ww-bg (4.56) but NOT on --ww-surface-muted (4.13),
     which is where the pills and the recessed control bodies put it — so the token
     was safe on two of the three surfaces it is actually used on, and nothing said
     which. This value clears all three (5.45 / 5.04 / 4.57) at a 6% lightness
     shift, so the contract is now simply "muted, and legible anywhere". */
  --ww-text-muted: #65685c;
  --ww-primary: #2a6b50;
  --ww-primary-hover: #1f5640;
  --ww-border: #e0dac9;
  /* Darkened from #b5654a, which was the last of the accessibility pass's findings
     and the only one left: all 22 were the same element, .ww-btn.ww-danger, at
     4.08:1. It was held back as a brand decision rather than an accessibility one,
     on the belief that the token served two roles a single value could not satisfy.
     Measuring it settled that: the filled button's text is --ww-surface, not #fff,
     so "surface on danger" and "danger as text on surface" compute the SAME ratio —
     one number governs both. The other role, .ww-btn.ww-ghost.ww-danger, is
     icon-only, judged by WCAG 1.4.11 non-text at 3.0:1, which any darkening keeps.
     4.70 clears AA at #a85c43; this goes one step further for margin (5.02). */
  --ww-danger: #a4573d;
  --ww-signal: #1b9e9a;
  --ww-amber: #cf8a3c;
  /* Tank gauges read as the brand "signal" teal — the site carries no blue. */
  --ww-water: var(--ww-signal);
  --ww-on: #2a6b50;
  --ww-off: #c2bba8;
  --ww-ink: #20211a;
  /* The ink that sits ON a saturated brand fill — a filled toggle tile, an "on"
     badge, the active nav pill, an error toast. It was the literal `#fff`, written
     out nine times across four files, and every one of those was an assumption that
     the fill underneath is dark. That assumption is exactly what a dark theme
     breaks: --ww-primary and --ww-danger become LIGHT there, and white-on-light is
     unreadable. One token, flipped once.
     Deliberately NOT applied to --ww-amber, which is light in both themes and so
     wants dark ink in both — see .ww-at-sf8 in the hub sheet. */
  --ww-on-accent: #fff;
  /* Dialog backdrop. A token because the dark theme needs a heavier one: the scrim
     works by darkening what is behind it, and there is much less headroom to darken
     when "behind it" is already near-black. */
  --ww-scrim: rgba(32, 33, 26, 0.45);
  /* The dock's scrim, and NOT --ww-scrim. The two backdrops answer different
     questions: a dialog's covers a page you must stop reading to answer it, while
     the dock's covers a page you are about to come straight back to. The dock's
     also has a 3px blur doing half the work, which a dialog does not. It was
     literally --ww-scrim until the dim read as too heavy for a navigation menu;
     the values are that scrim at ~2/3. Lower it here, not there, or every confirm
     in the app goes pale with it. */
  --ww-dock-scrim: rgba(32, 33, 26, 0.3);
  /* type */
  --ww-font-body: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --ww-font-display: "Crimson Pro", Georgia, "Times New Roman", serif;
  --ww-fs-caption: 0.75rem;
  --ww-fs-label: 0.85rem;
  --ww-fs-body: 0.95rem;
  --ww-fs-h3: 1.15rem;
  --ww-fs-h2: 1.5rem;
  --ww-fs-h1: 2.2rem;
  --ww-fs-hero: 3.2rem;
  /* spacing — a 2px grid, DERIVED from the code rather than imposed on it.
     Counting every padding/margin/gap px value across the partials gives a clear
     2px cadence with 6 and 8 dominant (40 and 36 uses), tapering through 20; the
     scale below is exactly that, named by its own value so a substitution is
     self-checking and a reader never has to look up what "sp-3" means.
     Deliberately NOT a 4pt or 8pt scale: this UI is not built on one, and
     rounding 267 live values onto an invented grid would move pixels on seven
     devices to satisfy a convention nobody here adopted.
     Odd values (1/3/5/7/9/11) and one-offs (26/44/48/58/70/72) stay literal —
     they are design decisions to make deliberately later, not mechanically now. */
  --ww-sp-2: 2px;
  --ww-sp-4: 4px;
  --ww-sp-6: 6px;
  --ww-sp-8: 8px;
  --ww-sp-10: 10px;
  --ww-sp-12: 12px;
  --ww-sp-14: 14px;
  --ww-sp-16: 16px;
  --ww-sp-18: 18px;
  --ww-sp-20: 20px;
  /* focus — its own token, deliberately NOT just --ww-primary at the point of use.
     `--ww-focus: var(--ww-primary)` is substituted here, on :root, so descendants
     inherit the resolved green. That matters because the inactive states below
     re-bind --ww-primary to a neutral on their own subtree; if focus rules read
     --ww-primary directly, the focus ring inside a disabled card would go grey
     along with the buttons. A keyboard user must always be able to see where they
     are, most of all in the region the mouse user was told to ignore. */
  --ww-focus: var(--ww-primary);
  --ww-focus-width: 2px;
  --ww-focus-offset: 2px;
  /* state — how a surface says "inactive" without saying it in opacity.
     `opacity` on a container fades its text along with its chrome, and this
     palette has no room for that: --ww-text-muted already measures 4.93:1 on
     --ww-surface, and the lightest grey that still clears WCAG AA there is
     #737373. Fading text by ANY amount lands it below the floor — which is
     exactly what was happening: 49 of 76 failing text runs traced to four
     `opacity: 0.5` container rules.
     So an inactive surface re-binds the colour tokens its contents already draw
     with, rather than compositing over the top of them. Body text drops to the
     muted tone (still 4.93:1), accent and danger go neutral so their buttons read
     as unavailable, and only genuine non-text decoration is allowed to fade.
     The state stays legible AND stays obvious: it is carried by three signals at
     once — de-emphasised text, neutralised accents, and the explicit
     "offline"/"disabled" pill the markup already renders. */
  --ww-inactive-ink: var(--ww-text-muted);
  --ww-inactive-chrome: 0.5;
  /* shape & elevation — softer */
  --ww-radius: 16px;
  --ww-radius-sm: 10px;
  --ww-radius-pill: 999px;
  --ww-shadow: 0 1px 2px rgba(32, 33, 26, 0.05), 0 10px 30px rgba(32, 33, 26, 0.05);
  --primary-color: var(--ww-primary);
}

/* ------------------------------------------------------------- dark theme --
   Re-binds the palette and nothing else. That is the whole design: every rule in
   every bundle already draws its colour from a token, so a theme is a list of
   values, not a second stylesheet. The one thing that was not tokenised — the ink
   on a brand fill — is --ww-on-accent above, added for exactly this reason.

   Derived tokens do NOT need restating here. `--ww-focus: var(--ww-primary)` and
   friends are declared on :root above and substituted at computed-value time, so
   they resolve against whatever --ww-primary computes to on :root AFTER this block
   has had its say. (Which is the opposite of the trap noted in the focus comment:
   the substitution is late enough to follow a re-bind at the same level, and only
   too early to follow a re-bind on a DESCENDANT.)

   The OS preference is the DEFAULT, not the only input: the topbar's theme button
   (and the hub's Settings > Appearance select) can pin a choice, which lands here as
   [data-theme] on <html>. Three states, expressed as two selectors:

     absent       follow the OS  -> the @media block below applies
     ="light"     pinned light   -> the @media block excludes itself
     ="dark"      pinned dark    -> the standalone block below applies

   The palette is therefore written out twice. That is the cost of the mechanism
   chosen over light-dark(), which would have kept one copy: +188 B gz against
   light-dark()'s +72 B, bought so the control cannot be a dead button on a browser
   older than Safari 17.5 / Chrome 123. A visibly broken switch is poor value for
   116 bytes. If that floor ever rises, this whole section collapses into one
   light-dark() per token and `color-scheme` alone drives it.

   `color-scheme` has to be pinned alongside, because it is what the UA reads to
   draw the things this stylesheet does not own — the <select> popup, the range and
   checkbox widgets, the scrollbars. Leaving it at `light dark` while the tokens are
   pinned dark would give a dark page a white dropdown hanging off it.

   Every value below was checked against the same WCAG maths as the light palette
   before it was written, not after: body and muted text, the accent-as-text runs,
   and the ink on each fill all clear AA with margin (5.7 at worst). The two soft
   ones — --ww-border and --ww-off against the surfaces — sit at 1.6/2.2, which is
   BETTER than the shipped light theme's 1.2/1.7 for the same pairs. They are soft
   on purpose in both; the toggle's state is carried by its knob (8.5:1 here), not
   by the track's edge. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* Warm near-black, not blue-grey: the light theme is a warm cream and the
       brand carries no blue, so the dark one is the same hue rotated down. */
    --ww-bg: #15160f;
    --ww-surface: #1e1f17;
    --ww-surface-muted: #2a2b21;
    --ww-text: #edeade;
    --ww-text-muted: #a5a596;
    --ww-primary: #6cbf92;
    --ww-primary-hover: #86d0a7;
    --ww-border: #3a3b2f;
    --ww-danger: #e79176;
    --ww-signal: #3ec9c4;
    --ww-amber: #d9a057;
    --ww-on: #6cbf92;
    --ww-off: #4d4e41;
    --ww-ink: #edeade;
    /* Ink and fill trade places, so .ww-btn (background: ink, color: surface)
       inverts into a light pill with dark text without touching the rule. */
    --ww-on-accent: #15160f;
    --ww-scrim: rgba(0, 0, 0, 0.66);
    --ww-dock-scrim: rgba(0, 0, 0, 0.44);
    /* A 5%-black shadow is invisible on a near-black page. Depth here comes from
       the surface being lighter than the page, helped by a shadow that is dark
       enough to still read as one. */
    --ww-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 10px 30px rgba(0, 0, 0, 0.35);
  }
}

/* Pinned dark. The same values as the block above — kept adjacent so they get
   edited together; nothing would catch them drifting apart except a reader. */
:root[data-theme="dark"] {
  /* Warm near-black, not blue-grey: the light theme is a warm cream and the
     brand carries no blue, so the dark one is the same hue rotated down. */
  --ww-bg: #15160f;
  --ww-surface: #1e1f17;
  --ww-surface-muted: #2a2b21;
  --ww-text: #edeade;
  --ww-text-muted: #a5a596;
  --ww-primary: #6cbf92;
  --ww-primary-hover: #86d0a7;
  --ww-border: #3a3b2f;
  --ww-danger: #e79176;
  --ww-signal: #3ec9c4;
  --ww-amber: #d9a057;
  --ww-on: #6cbf92;
  --ww-off: #4d4e41;
  --ww-ink: #edeade;
  /* Ink and fill trade places, so .ww-btn (background: ink, color: surface)
     inverts into a light pill with dark text without touching the rule. */
  --ww-on-accent: #15160f;
  --ww-scrim: rgba(0, 0, 0, 0.66);
  --ww-dock-scrim: rgba(0, 0, 0, 0.44);
  /* A 5%-black shadow is invisible on a near-black page. Depth here comes from
     the surface being lighter than the page, helped by a shadow that is dark
     enough to still read as one. */
  --ww-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 10px 30px rgba(0, 0, 0, 0.35);
}

/* The UA half of the switch (see the note above). Only needed when a choice is
   pinned — with no attribute, reset.css's `color-scheme: light dark` already
   means "follow the OS", which is exactly right. */
:root[data-theme="dark"] {
  color-scheme: dark;
}

:root[data-theme="light"] {
  color-scheme: light;
}


* {
  box-sizing: border-box;
}

/* The HTML `hidden` attribute must win over component layout rules (e.g.
   `.ww-row { display: flex }`), which would otherwise re-show a hidden row —
   the Setup "Hub" row stays gone until a hub is actually linked. */
[hidden] {
  display: none !important;
}

/* `color-scheme` on the root, and both values: it is what tells the UA to render
   the things the stylesheet does not own — the <select> popup, the checkbox and
   range widgets, the scrollbars, the canvas behind an overscroll — in the matching
   theme. It was `light` on <body>, which pinned all of that to light no matter what
   the tokens did, and would have left a dark page with a white dropdown hanging off
   it. Declared here rather than on body because scrollbars follow the root. */
html {
  min-height: 100%;
  background: var(--ww-bg);
  color: var(--ww-text);
  color-scheme: light dark;
}

body {
  min-height: 100vh;
  margin: 0;
  background: var(--ww-bg);
  color: var(--ww-text);
  font-family: var(--ww-font-body);
  -webkit-font-smoothing: antialiased;
}

esp-app {
  display: block;
  min-height: 100vh;
}


/* ----------------------------------------------------------------- shell -- */
/* Safe-area insets live on the base rule so a phone in landscape gets the
   side insets. Zero on other devices. */
/* Clears the fixed dock. Derived from the shut dock height:
   --ww-dock-btn (55px) + --ww-sp-16 (16px) + 12px clearance. Re-derive
   when --ww-dock-btn changes. */
.ww-shell {
  max-width: 880px;
  margin: 0 auto;
  padding: 0 calc(var(--ww-sp-16) + env(safe-area-inset-right))
    calc(83px + env(safe-area-inset-bottom))
    calc(var(--ww-sp-16) + env(safe-area-inset-left));
}

/* The logo at one end, the theme toggle at the other, and nothing between them.
   space-between rather than the three-column grid this used to be: with exactly
   two children it states the intent directly, and it degrades the right way — a
   product that renders the bar without a toggle keeps the logo against the left
   edge instead of holding an empty track open beside it. The grid needed a
   phantom 1fr column on the left purely to counterweight the button and push the
   brand to the middle, and there is no middle any more. */
.ww-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ww-sp-12);
  padding: var(--ww-sp-18) var(--ww-sp-4) var(--ww-sp-6);
}

/* The mark and the connection dot travel together as the leading group, so the
   dot stays with the thing whose connection it describes. */
.ww-brand {
  display: flex;
  align-items: center;
  gap: var(--ww-sp-10);
}

/* Theme switch: sized as a tap target, no border or fill so it reads as
   chrome. 40px sets the bar height. The phone layout reserves it in
   .ww-shell's padding-top. Change the size and re-measure both. */
.ww-theme-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  border-radius: var(--ww-radius-pill);
  background: transparent;
  color: var(--ww-text-muted);
  cursor: pointer;
}

.ww-theme-btn:hover {
  background: var(--ww-surface-muted);
  color: var(--ww-ink);
}

.ww-theme-btn .ww-ico {
  width: 1.25rem;
  height: 1.25rem;
}

.ww-wordmark {
  display: block;
  height: 20px;
  width: auto;
  color: var(--ww-ink);
}

.ww-conn {
  font-size: 0.78rem;
  color: var(--ww-text-muted);
  display: inline-flex;
  align-items: center;
  align-self: center;
  gap: var(--ww-sp-6);
}

.ww-conn::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--ww-off);
}

.ww-conn.is-online::before {
  background: var(--ww-on);
}

/* Reconnecting: a pulse distinguishes it from flat offline grey. */
.ww-conn.is-reconnecting::before {
  animation: ww-conn-pulse 1.2s ease-in-out infinite;
}

@keyframes ww-conn-pulse {
  0%,
  100% {
    opacity: 0.35;
  }
  50% {
    opacity: 1;
  }
}

/* ------------------------------------------------------------------ icons -- */
/* Inline MDI glyphs use currentColor and scale with the font size. */
.ww-ico {
  width: 1.1em;
  height: 1.1em;
  flex: 0 0 auto;
  vertical-align: -0.16em;
}

.ww-section-title .ww-ico {
  width: 0.95em;
  height: 0.95em;
  margin-right: 5px;
  color: var(--ww-primary);
}

.ww-btn .ww-ico {
  margin-right: var(--ww-sp-6);
}

/* Icon-only button: square padding and no icon gap so the glyph sits
   centred. */
.ww-btn.ww-icon-btn,
.ww-out-head .ww-btn.ww-icon-btn {
  padding: var(--ww-sp-6);
}

.ww-btn.ww-icon-btn .ww-ico {
  margin-right: 0;
}

/* ------------------------------------------------------------------- dock -- */
/* Navigation dock: a fixed disc (the current page) that opens into a
   quarter-circle arc of page discs. All discs are the same size. The
   current page is filled. The rest are outlined. Bottom-right at every
   width. JS writes the per-button arc direction (--fx/--fy). The radius
   and disc sizes live here. */

/* The <ww-dock> element itself draws nothing and must not box: it exists to
   own behaviour, and its two children — the scrim and .ww-dock — are both
   viewport-fixed and are positioned as siblings. `display: contents` is what
   keeps it out of the way of that. They MUST stay siblings: the drag puts a
   transform on .ww-dock, and a transformed element becomes the containing
   block for fixed descendants, so a scrim nested inside it would stop
   covering the viewport the moment somebody moved the dock. */
ww-dock {
  display: contents;
}

.ww-dock {
  position: fixed;
  right: var(--ww-sp-16);
  bottom: calc(var(--ww-sp-16) + env(safe-area-inset-bottom));
  /* Above the phone topbar (20), under the toast (50) and dialog (100). */
  z-index: 30;
  /* Flex so the button is a flex item, not inline (avoids descender gap).
     --ww-dock-r: arc radius from button centre to page centre. All three
     values move together: the radius holds pages off the button and off
     each other. */
  display: flex;
  --ww-dock-r: 105px;
  --ww-dock-btn: 55px;
  --ww-dock-tab: 55px;
}

/* One more disc on the arc, so the arc gets bigger.

   The console's Back control takes a place of its own (ww-dock.js), and the
   quarter sweep is spaced for whatever it holds: n discs sit 90/(n-1) degrees
   apart, so their centres are 2r*sin(45/(n-1)) apart. A hub shows three pages, so
   three discs sit 80px apart at r=105 and four sit 54.3px apart — closer than the
   55px they are wide, which is discs touching. At 125px the four sit 64.7px apart
   and the sweep still fits: its far disc reaches 125px + half a button from the
   corner, about 196px from the right edge of a 320px screen.

   Only when the control is there. A plain browser never sees it, and a dock that
   grew for a control it is not drawing would be paying for the app everywhere. */
.ww-dock.has-back {
  --ww-dock-r: 125px;
}

/* Left-handed mode: mirrors anchors and arc. */
.ww-dock--left {
  right: auto;
  left: var(--ww-sp-16);
}

/* Dragging: no transition fights the pointer. is-snapping adds one for
   the release leg. */
.ww-dock.is-snapping {
  transition: transform 0.18s cubic-bezier(0.2, 0.9, 0.3, 1.25);
}

@media (prefers-reduced-motion: reduce) {
  .ww-dock.is-snapping {
    transition: none;
  }
}

/* Scrim behind an open dock. Fixed, full-viewport, above the topbar.
   Hidden with visibility (not display) so it can transition and stay out
   of the hit-testing path when shut. */
.ww-dock-scrim {
  position: fixed;
  inset: 0;
  z-index: 25;
  background: var(--ww-dock-scrim);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.18s ease, visibility 0.18s;
}

.ww-dock-scrim.is-open {
  opacity: 1;
  visibility: visible;
}

/* No backdrop-filter: the dim carries the whole message without blur. */
@supports not (
  (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))
) {
  .ww-dock-scrim {
    background: rgba(0, 0, 0, 0.72);
  }
}

/* Page tabs: hidden with visibility (not display) so they can transition
   and stay out of the accessibility tree. A zero-size box at the button
   centre — the arc origin — so each page is placed by a plain translate. */
.ww-dock-tabs {
  position: absolute;
  right: calc(var(--ww-dock-btn) / 2);
  bottom: calc(var(--ww-dock-btn) / 2);
  width: 0;
  height: 0;
  visibility: hidden;
  transition: visibility 0.2s;
}

/* Origin mirrored to the left edge. */
.ww-dock--left .ww-dock-tabs {
  right: auto;
  left: calc(var(--ww-dock-btn) / 2);
}

.ww-dock.is-open .ww-dock-tabs {
  visibility: visible;
}

/* Shared disc shape: circle, border, shadow, centred content. */
.ww-dock-menu,
.ww-dock-tabs button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--ww-border);
  border-radius: 50%;
  background: var(--ww-surface);
  box-shadow: var(--ww-shadow);
  color: var(--ww-text);
  font: inherit;
  font-size: var(--ww-fs-label);
  font-weight: 600;
  padding: 0;
  cursor: pointer;
  white-space: nowrap;
}

/* The current page. Filled so it stands out as the permanent control.
   touch-action none so a swipe starting on it does not scroll the page. */
.ww-dock-menu {
  position: relative;
  z-index: 1;
  width: var(--ww-dock-btn);
  height: var(--ww-dock-btn);
  touch-action: none;
  background: var(--ww-primary);
  border-color: transparent;
  color: var(--ww-on-accent);
}

/* Icon sized to match the page icons (same disc diameter). */
.ww-dock-menu .ww-ico {
  width: 1.75em;
  height: 1.75em;
  vertical-align: 0;
}

/* One page on the arc. --fx/--fy give the unit-circle direction (from JS).
   The negative margins put the page centre on the arc origin. */
.ww-dock-tabs button {
  position: absolute;
  right: 0;
  bottom: 0;
  width: var(--ww-dock-tab);
  height: var(--ww-dock-tab);
  margin: 0 calc(var(--ww-dock-tab) / -2) calc(var(--ww-dock-tab) / -2) 0;
  color: var(--ww-text-muted);
  /* Shut: collapsed into the dock button, small and gone. */
  opacity: 0;
  transform: translate(0, 0) scale(0.55);
  transition: transform 0.18s cubic-bezier(0.2, 0.9, 0.3, 1.25),
    opacity 0.14s ease;
  /* Staggered along the arc: 30ms per page. */
  transition-delay: calc(var(--i, 0) * 30ms);
}

/* Left dock: margin follows the anchor edge. */
.ww-dock--left .ww-dock-tabs button {
  right: auto;
  left: 0;
  margin: 0 0 calc(var(--ww-dock-tab) / -2) calc(var(--ww-dock-tab) / -2);
}

.ww-dock.is-open .ww-dock-tabs button {
  opacity: 1;
  transform: translate(
      calc(var(--ww-dock-r) * var(--fx, 0)),
      calc(var(--ww-dock-r) * var(--fy, 0))
    )
    scale(1);
}

.ww-dock-tabs button:hover {
  color: var(--ww-text);
}

@media (prefers-reduced-motion: reduce) {
  .ww-dock-tabs,
  .ww-dock-tabs button {
    transition: none;
    transition-delay: 0s;
  }
}

/* Page icon: same ratio as the menu icon (same disc size). */
.ww-dock-tabs .ww-ico {
  width: 1.75em;
  height: 1.75em;
  vertical-align: 0;
}

/* Badge: hangs off the top-right corner of a disc. Nothing renders one
   yet. */
.ww-dock-badge {
  position: absolute;
  top: -7.5px;
  right: -7.5px;
  min-width: 22.5px;
  padding: 0 6.25px;
  border-radius: var(--ww-radius-pill);
  background: var(--ww-primary);
  color: var(--ww-on-accent);
  font-size: 13.75px;
  line-height: 22.5px;
  text-align: center;
}

/* ----------------------------------------------------- collapsible cards -- */
/* Shared by diagnostics Logs/Diagnostics cards and hub Program cards.
   A header (.ww-out-head) with a caret that toggles .ww-collapsed,
   hiding the body. */
.ww-out-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ww-sp-8);
}

/* Right-hand cluster in a card header: keeps controls on one baseline. */
.ww-head-actions {
  display: flex;
  align-items: center;
  gap: var(--ww-sp-8);
}

.ww-timer-head {
  cursor: pointer;
}

.ww-timer-caret {
  display: inline-flex;
  color: var(--ww-text-muted);
  transition: transform 0.15s ease;
}

.ww-card:not(.ww-collapsed) .ww-timer-caret {
  transform: rotate(180deg);
}

.ww-collapsed .ww-timer-body {
  display: none;
}

/* Collapsed Logs card: hide the Clear action. */
.ww-collapsed [data-log-clear] {
  display: none;
}

/* Button in a card header: compact so it does not tower over the heading. */
.ww-out-head .ww-btn {
  flex: 0 0 auto;
  font-size: 0.85rem;
  padding: var(--ww-sp-6) var(--ww-sp-14);
}

/* ----------------------------------------------------------------- dialog -- */
/* Confirmation dialog (shared Dialog.confirm). Backdrop covers the page
   above the toast/nav. The card carries surface styling and .ww-btn
   actions. */
.ww-dialog-back {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--ww-sp-20);
  background: var(--ww-scrim);
  opacity: 0;
  transition: opacity 0.15s ease;
}

.ww-dialog-back.is-shown {
  opacity: 1;
}

.ww-dialog {
  width: 100%;
  max-width: 380px;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  background: var(--ww-surface);
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: var(--ww-sp-20);
  transform: translateY(8px);
  transition: transform 0.15s ease;
}

.ww-dialog-back.is-shown .ww-dialog {
  transform: translateY(0);
}

.ww-dialog-title {
  margin: 0;
  font-family: var(--ww-font-display);
  font-weight: 600;
  font-size: var(--ww-fs-h3);
  color: var(--ww-ink);
}

/* pre-line keeps caller line breaks without overflow. */
.ww-dialog-body {
  margin: var(--ww-sp-10) 0 0;
  color: var(--ww-text-muted);
  font-size: var(--ww-fs-body);
  line-height: 1.45;
  white-space: pre-line;
}

.ww-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--ww-sp-10);
  margin-top: var(--ww-sp-20);
}

/* An answer INSIDE a dialog: "enter a network name", "the hub would not take
   those", "looking for networks…". Here rather than as a toast because a toast
   lands outside the dialog it is about, one of the two products that share these
   dialogs has no toast at all, and a message about a field belongs beside the
   field. Neutral by default (a status), .is-error for a refusal. */
.ww-dialog-note {
  margin: var(--ww-sp-10) 0 0;
  color: var(--ww-text-muted);
  font-size: var(--ww-fs-caption);
  line-height: 1.4;
}

.ww-dialog-note.is-error {
  color: var(--ww-danger);
}

/* A labelled FIELD inside a dialog — stacked, label over control.
   Deliberately not `.ww-row`, which a Settings card uses to put a key and its
   readout on one line: a readout takes the width it needs, and a control has to
   be operated. Side by side on a phone the label wins the line and the control
   is squeezed to nothing — the Wi-Fi picker rendered about four characters wide
   next to "Choose a network", which is a list you cannot read. */
.ww-dialog-field {
  padding: var(--ww-sp-10) 0;
  border-top: 1px solid var(--ww-border);
}

.ww-dialog-field:first-of-type {
  border-top: 0;
}

.ww-dialog-field > label {
  display: block;
  margin-bottom: 6px;
  font-weight: 600;
  font-size: var(--ww-fs-body);
}

.ww-dialog-field > input {
  width: 100%;
}

/* The Wi-Fi dialog's picker cell: the network list, and — on a product that can
   sweep again on demand — the button that asks. Two things on the field's one
   line, so the list takes what the button does not. */
.ww-dialog-pick {
  display: flex;
  align-items: center;
  gap: var(--ww-sp-10);
  min-width: 0;
}

.ww-dialog-pick > select,
.ww-dialog-pick > input {
  flex: 1;
  min-width: 0;
}

.ww-dialog-pick > .ww-btn {
  flex: none;
}

/* The Wi-Fi / Restart / Reset row at the bottom of each Settings card.
   Owns its display rather than borrowing .ww-inline from components.css
   (concatenated after this file) to avoid a specificity trap. */
.ww-control {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--ww-sp-8);
  margin-top: var(--ww-sp-14);
}

/* Phone: lay each button group as a grid. Variable count (3 on nodes,
   5 on hub) so grids go on the wrappers, not .ww-control itself. */
@media (max-width: 620px) {
  .ww-control {
    display: block;
  }
  .ww-control-extra,
  .ww-control-main {
    display: grid;
    gap: var(--ww-sp-6);
  }
  .ww-control-extra {
    grid-template-columns: 1fr 1fr;
    margin-bottom: var(--ww-sp-6);
  }
  .ww-control-main {
    grid-template-columns: 1fr 1fr 1fr;
  }
  /* Four-button main group goes 2x2 instead of 3+1 orphan. */
  .ww-control-quad {
    grid-template-columns: 1fr 1fr;
  }
  /* One-button group (the Hub appliance's Settings card, which offers Wi-Fi and
     nothing else — it has no Restart or Reset to give). Without this the single
     button inherits the three-column default and sits in the first cell at a
     third of the width, hard against the left edge of a centred row. */
  .ww-control-one {
    grid-template-columns: 1fr;
  }
  /* Two-button group (the room node's Radar card: Calibrate / Reset) — without
     this it inherits the three-column default and leaves an empty third cell,
     stretching both buttons to two thirds of the card. Same columns as the quad
     rule above, but named for its own count: a pair is not a short quad, and the
     next row with its own count should add its own rule rather than borrow one. */
  .ww-control-pair {
    grid-template-columns: 1fr 1fr;
  }
  .ww-control .ww-btn {
    width: 100%;
    font-size: 0.82rem;
    padding: var(--ww-sp-8) var(--ww-sp-12);
  }
  .ww-control .ww-btn .ww-ico {
    margin-right: var(--ww-sp-4);
  }
}

/* ----------------------------------------------------------- update card -- */
/* Shared diagnostics Update card: appears when the gateway reports a
   newer build. Only this action row is shared here. */
.ww-update-action {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ww-sp-10);
}

.ww-update-avail {
  font-size: var(--ww-fs-body);
  font-weight: 600;
  color: var(--ww-primary);
}

.ww-update-action .ww-btn.ww-icon-btn {
  background: transparent;
  border-color: var(--ww-primary);
  color: var(--ww-primary);
}

.ww-update-action .ww-btn.ww-icon-btn:hover:not(:disabled) {
  background: var(--ww-primary);
  border-color: var(--ww-primary);
  color: var(--ww-on-accent);
}

.ww-update-action .ww-btn:disabled {
  opacity: 0.6;
  cursor: default;
}

/* ------------------------------------------------------------------ focus -- */
/* One focus ring for every interactive element. :where() contributes zero
   specificity so per-control rings still win. */
:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: var(--ww-focus-width) solid var(--ww-focus);
  outline-offset: var(--ww-focus-offset);
}

/* --------------------------------------------------------- reduced motion -- */
/* Honour prefers-reduced-motion. Two animations carry state and stay as
   static signals: the connection dot pulse (reconnecting vs offline) and
   the OTA bar (indeterminate vs determinate). Transitions collapse.
   Those two states get static colour below and in the hub partial. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }

  /* Reconnecting becomes amber — the in-progress colour. */
  .ww-conn.is-reconnecting::before {
    animation: none !important;
    background: var(--ww-amber);
    opacity: 1;
  }
}

/* The shared control vocabulary: the widgets every Wala Works product renders
   identically, in the cascade slot between the app shell (chrome.css) and the
   product-specific layers.

   These rules used to exist TWICE — byte-for-byte in nodebase.css (which the six
   Wi-Fi / field-node bundles carry) and again in devices/hub-mini.css (which
   the hub carries instead, since it has no nodebase.css). Two copies of one
   design is two things to keep in step, and they had already begun to drift: the
   comments above several of these rules disagreed about what the rule was for.
   Promoting them here makes each control one definition that ships in all seven
   bundles, so a change to a toggle or a form field lands on every product at once
   rather than on whichever copy the author happened to be looking at.

   What did NOT move: rules whose two copies genuinely differ (.ww-badge, .ww-log,
   .ww-device-id) — those are real per-product divergences and stayed where they
   were, to be reconciled deliberately or not at all.

   Cascade note: everything here is overridable by nodebase.css and by any
   device partial, both of which are concatenated after this file. */

/* ---------------------------------------------------------------- layout -- */
/* A note on `minmax(0, 1fr)` in the grids below, because the obvious `1fr` is a
   trap: `1fr` is shorthand for `minmax(auto, 1fr)`, so a track will refuse to
   shrink below its content's min-content width. One card with a wide indivisible
   child then steals space from its neighbours and the "equal columns" the author
   wrote are silently unequal — exactly what happened to the hub's LAN row, where
   a 2x2 relay grid forced 227px against its neighbour's 135px. `minmax(0, 1fr)`
   means what `1fr` looks like it means. Every measured grid is even today, so
   this changes nothing now; it stops the next long device name changing it. */
.ww-view {
  display: grid;
  gap: var(--ww-sp-16);
}

.ww-grid-3 {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--ww-sp-18);
}

.ww-grid-2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ww-sp-18);
}

#ww-prog {
  display: grid;
  gap: var(--ww-sp-14);
}

.ww-card {
  background: var(--ww-surface);
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius);
  box-shadow: var(--ww-shadow);
  padding: var(--ww-sp-20);
}

/* Unprovisioned slots are hidden outright (not dimmed) on the dashboard and
   program views — Setup is where you add them. */
.ww-hidden {
  display: none !important;
}

.ww-empty {
  text-align: center;
  color: var(--ww-text-muted);
}

.ww-empty h2 {
  margin: 0 0 var(--ww-sp-6);
}

/* ------------------------------------------------------------- headings --
   Four voices, and only four. They were eight before this pass, and the extra
   four were not variety — they were the same job styled twice in two files, or
   an override that forgot to reset an inherited property. Worse, the ladder ran
   BACKWARDS on the Program page: a rule row's label ("Pump") rendered at 24px
   display serif, larger and louder than the "OUTPUT RULES" section it sits
   inside. The deepest level in the tree was the biggest thing on the screen.

     1. Section eyebrow   .ww-section-title[data-sec]   fs-h3 700 CAPS  ink
        Introduces a group of cards from OUTSIDE them: "Sensors", "Control",
        "Scheduled Overrides". The top of the page's structure and the only heading allowed
        to be loud.

     2. Group label       .ww-section-title (base)      fs-label 700 CAPS muted
                          .ww-subhead                          "
                          .ww-prog-group                 (hub, sentence case)
        A sub-grouping within a section or a card: the rule builder's "Turn on
        when", Setup's "Available", the Program page's per-device grouping.
        Small caps, muted — it separates, it does not announce. The one variant
        that is NOT uppercased is .ww-prog-group, because its text is a device
        nickname the operator typed; see the note there.

     3. Card title        .ww-card h2                   fs-h2 display serif
        The name of a panel: "Settings", "WiFi", "Recovery Code". The serif is
        the brand's voice and this is the only place it speaks in the layout.

     4. Control label     .ww-state-tile h2, .ww-row label, and the hub's rule
                          and schedule row heads         body sans 600
        The label ON a control — a toggle tile, a form row, a collapsed rule.
        It is a name for a thing you operate, not a title, so it takes the body
        face at the size its control needs (fs-h3 on a touch tile, fs-body in a
        dense row) and never the display serif. */

.ww-card h2 {
  margin: 0 0 var(--ww-sp-6);
  font-family: var(--ww-font-display);
  font-size: var(--ww-fs-h2);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
}

/* Level 2 — the group label. Shared with .ww-subhead below; the two were 0.74rem
   at 0.1em and 0.78rem at 0.04em, near-identical by accident rather than by
   agreement, so they are one voice now. */
.ww-section-title {
  margin: var(--ww-sp-6) var(--ww-sp-4) -2px;
  font-family: var(--ww-font-body);
  font-size: var(--ww-fs-label);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ww-text-muted);
}

/* Level 1 — the section eyebrow. Larger and darker than the group labels the
   same class drives inside the rule builder.
   `text-transform` is restated rather than inherited from the base rule: it was
   inherited, which meant this heading's case was a side effect of a rule about
   a different level, and any change there would silently re-case every section
   title on the dashboard. Both levels now declare their own case. */
.ww-section-title[data-sec] {
  margin: var(--ww-sp-12) var(--ww-sp-4) var(--ww-sp-2);
  font-size: var(--ww-fs-h3);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ww-ink);
}

.ww-hint {
  font-size: 0.8rem;
  color: var(--ww-text-muted);
  line-height: 1.45;
  margin: var(--ww-sp-6) 0 0;
}

.ww-badge.is-on {
  background: var(--ww-on);
  color: var(--ww-on-accent);
  border-color: transparent;
}

/* --------------------------------------------------- relay control tile -- */
/* The shared relay control (see relayTileHTML): a full-width card that is itself
   a button. State reads as the tile's colour — a plain surface tile with the
   accent as text when off, a filled accent tile with --ww-on-accent text when on
   (.is-on, set live in each app's applyState). The fill is held at 85% opacity so the on state reads as
   a tint rather than a hard block. Used by relay4x (one per relay) and the
   LongRange pump (its single flow relay). Read-only status tiles (relay4x wall
   switches) reuse .ww-state-tile without the .ww-relay-tile button reset. */
.ww-state-tile {
  color: var(--ww-primary);
  transition:
    background 0.15s ease,
    color 0.15s ease,
    border-color 0.15s ease;
}

.ww-state-tile.is-on {
  background: color-mix(in srgb, var(--ww-primary) 85%, transparent);
  border-color: transparent;
  color: var(--ww-on-accent);
}

/* Level 4 — the control label (see the headings note above). A tile is a switch,
   and its heading names the thing you are switching: "Relay 1", "Switch 3",
   "Pump". That is a control label, not a panel title, so it drops the display
   serif for the body face. Sized at fs-h3 rather than the fs-body used by dense
   rows because a tile is a touch target and its label is most of what you aim
   at. The hub's compact LAN tiles override the size back down (and only the
   size) — see .ww-lan-tiles .ww-relay-tile h2, which had already made exactly
   this decision for one screen before it was a rule anywhere.
   Beats .ww-card h2 on source order, not specificity: both are (0,1,1). */
.ww-state-tile h2 {
  font-family: var(--ww-font-body);
  font-size: var(--ww-fs-h3);
  font-weight: 600;
  letter-spacing: normal;
}

/* Inside the tile everything follows its single text colour (green off / white
   on) — drop the muted caption and bold-peer treatment used elsewhere. */
.ww-state-tile h2,
.ww-state-tile .ww-relay-meta,
.ww-state-tile .ww-relay-meta b,
.ww-state-tile .ww-text-muted {
  color: inherit;
}

/* Relay tiles are also buttons (tap to toggle); reset the user-agent button look
   back to a plain card. Read-only status tiles keep the plain div. */
.ww-relay-tile {
  display: block;
  width: 100%;
  text-align: left;
  font: inherit;
  cursor: pointer;
}

.ww-relay-tile:focus-visible {
  outline: 2px solid var(--ww-primary);
  outline-offset: 2px;
}

/* Muted caption under a tile heading: the relay4x mapping, the pump's On/Off
   state word, the hub's LAN relay "Auto" badge. */
.ww-relay-meta {
  margin: var(--ww-sp-10) 0 0;
  font-size: 0.8rem;
  color: var(--ww-text-muted);
}

.ww-relay-meta b {
  color: var(--ww-text);
  font-weight: 600;
}

/* ------------------------------------------------------------- controls -- */
.ww-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ww-sp-12);
  padding: 9px 0;
  border-top: 1px solid var(--ww-border);
}

.ww-row:first-of-type {
  border-top: 0;
}

/* Level 4 — the control label again, in its dense-row size. Was 0.9rem, which is
   the scale's 0.95rem step missed by 0.05rem for no reason anyone recorded. */
.ww-row label {
  font-weight: 600;
  font-size: var(--ww-fs-body);
}

/* The row KEYS on a Settings card ("Identifier", "Nickname", "WiFi Network",
   "Language", "BLE Proxy" …) take the eyebrow voice of the group labels elsewhere —
   uppercase, letter-spaced, muted, same as .ww-subhead / .ww-section-title. Shared
   here so every device's Settings card reads as one labelled column: the hub, the
   field nodes, relay4x and room all tag their Settings card .ww-settings-card.
   Scoped to that card (not the global .ww-row label) so Program / Messages / Nodes /
   Mapping labels keep their normal case; :not(.ww-toggle) skips the toggle-wrapping
   <label> that carries only a switch. */
.ww-settings-card .ww-row > label:not(.ww-toggle) {
  font-size: var(--ww-fs-label);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ww-text-muted);
}

/* Settings card: rows group into sections (.ww-settings-sec), so the per-row hairline
   (.ww-row's global border-top) is suppressed and a single divider is drawn between
   adjacent sections instead. Was hub-only; shared here now that the field nodes,
   relay4x and room group their Overview card the same way — identity/network
   rows, then any toggles, then the Wi-Fi / Restart / Reset row under its own rule.
   That rule is what sets the control row apart on every product (it replaced the
   <hr class="ww-sep"> those devices used to carry, which sat 14 px lower). Same
   border/spacing voice as the Diagnostics card's .ww-diag-sec below. */
.ww-settings-card .ww-row {
  border-top: 0;
}

.ww-settings-sec + .ww-settings-sec {
  border-top: 1px solid var(--ww-border);
  margin-top: var(--ww-sp-8);
  padding-top: var(--ww-sp-8);
}

.ww-field {
  display: grid;
  gap: 5px;
  margin: var(--ww-sp-10) 0;
}

.ww-field > label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ww-text-muted);
}

input[type="text"],
input[type="password"],
input[type="number"],
input[type="time"],
select,
textarea {
  font: inherit;
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--ww-border);
  border-radius: 10px;
  background: var(--ww-surface);
  color: var(--ww-text);
}

input:focus,
select:focus,
textarea:focus {
  outline: 2px solid var(--ww-primary);
  outline-offset: 1px;
}

input:disabled,
select:disabled,
textarea:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.ww-inline {
  display: flex;
  gap: var(--ww-sp-8);
  align-items: center;
  flex-wrap: wrap;
}

/* In-card section divider. Splits grouped rows from a trailing action block at a
   card's bottom — the node recovery card's deprovision action is the remaining
   user. The Settings cards used to end with one above their Restart / Reset row;
   they group into .ww-settings-sec blocks now, so that divider comes from the
   section rule instead and reads identically on every product. */
.ww-sep {
  border: 0;
  border-top: 1px solid var(--ww-border);
  margin: var(--ww-sp-16) 0;
}

.ww-btn {
  font: inherit;
  font-weight: 600;
  padding: 9px var(--ww-sp-16);
  border-radius: 999px;
  border: 1px solid var(--ww-ink);
  background: var(--ww-ink);
  color: var(--ww-surface);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}

.ww-btn:hover {
  background: var(--ww-primary);
  border-color: var(--ww-primary);
}

.ww-btn.ww-ghost {
  background: transparent;
  color: var(--ww-primary);
}

.ww-btn.ww-ghost:hover {
  background: var(--ww-surface-muted);
}

.ww-btn.ww-danger {
  border-color: var(--ww-danger);
  background: var(--ww-danger);
}

.ww-btn.ww-danger:hover {
  filter: brightness(0.92);
}

/* Debug toggle: a .ww-btn that reads its on/off state by swapping its fill and
   text colours instead of a separate switch. Off = outlined (surface fill, ink
   text); on = filled ink. The real state lives on the hidden checkbox inside,
   so the shared [data-switch] reflection still drives it. */
.ww-toggle-btn {
  position: relative;
  background: var(--ww-surface);
  color: var(--ww-ink);
  border-color: var(--ww-ink);
}

.ww-toggle-btn input {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
}

.ww-toggle-btn:hover {
  background: var(--ww-surface-muted);
  border-color: var(--ww-ink);
}

.ww-toggle-btn:has(input:checked) {
  background: var(--ww-ink);
  color: var(--ww-surface);
  border-color: var(--ww-ink);
}

/* Active stays filled ink even under the cursor — the toggle never flashes the
   brand green, so "on" reads as a solid black fill until the next tap. */
.ww-toggle-btn:has(input:checked):hover {
  background: var(--ww-ink);
  border-color: var(--ww-ink);
}

.ww-toggle-btn:has(input:focus-visible) {
  outline: 2px solid var(--ww-primary);
  outline-offset: 2px;
}

/* toggle switch */
.ww-toggle {
  position: relative;
  display: inline-block;
  width: 46px;
  height: 26px;
  flex: 0 0 auto;
}

.ww-toggle input {
  opacity: 0;
  width: 0;
  height: 0;
}

.ww-toggle .ww-track {
  position: absolute;
  inset: 0;
  background: var(--ww-off);
  border-radius: 999px;
  transition: background 0.2s;
}

.ww-toggle .ww-track::before {
  content: "";
  position: absolute;
  width: 20px;
  height: 20px;
  left: 3px;
  top: 3px;
  background: #fff;
  border-radius: 50%;
  transition: transform 0.2s;
}

.ww-toggle input:checked + .ww-track {
  background: var(--ww-primary);
}

.ww-toggle input:checked + .ww-track::before {
  transform: translateX(20px);
}

.ww-toggle input:focus-visible + .ww-track {
  outline: 2px solid var(--ww-primary);
  outline-offset: 2px;
}

/* ------------------------------------------------------- notify topics -- */
/* Which notifications one recipient takes, folded under their row in the
   Recipients card. Every box is disabled: a filter is edited by the phone that
   owns it, over a route that needs that phone's own signature (docs/NOTIFY.md
   §3), and no shipped app can make one yet. Shown anyway because removal is
   loud — the row disappears — and muting is quiet. */
.ww-notify-topics {
  margin: 0 0 var(--ww-sp-10) var(--ww-sp-12);
}

.ww-notify-topics summary {
  cursor: pointer;
  color: var(--ww-text-muted);
  font-size: 0.875rem;
  list-style: none;
}

.ww-notify-topics summary::-webkit-details-marker {
  display: none;
}

.ww-notify-topics summary::after {
  content: "▸";
  margin-left: var(--ww-sp-4);
}

.ww-notify-topics[open] summary::after {
  content: "▾";
}

.ww-notify-topic {
  display: flex;
  align-items: center;
  gap: var(--ww-sp-8);
  padding: var(--ww-sp-2) 0 var(--ww-sp-2) var(--ww-sp-8);
}

/* --------------------------------------------------------- diagnostics -- */
.ww-diag {
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius);
  background: var(--ww-surface);
  overflow: hidden;
}

.ww-diag summary {
  cursor: pointer;
  padding: var(--ww-sp-12) var(--ww-sp-16);
  font-weight: 600;
  list-style: none;
}

.ww-diag summary::-webkit-details-marker {
  display: none;
}

.ww-diag summary::after {
  content: "▸";
  float: right;
  color: var(--ww-text-muted);
}

.ww-diag[open] summary::after {
  content: "▾";
}

.ww-diag .ww-diag-body {
  padding: var(--ww-sp-4) var(--ww-sp-16) var(--ww-sp-14);
}

.ww-kv {
  display: flex;
  justify-content: space-between;
  gap: var(--ww-sp-12);
  padding: var(--ww-sp-6) 0;
  border-top: 1px solid var(--ww-border);
  font-size: 0.88rem;
}

/* Sub-heading inside a card — the "Debug build" group in Readouts, the
   Linked / Available groups in the hub's discovery list. */
/* Level 2 — the group label, same voice as .ww-section-title's base; only the
   margins differ, because this one separates rows inside a card rather than
   sitting above one. */
.ww-subhead {
  font-size: var(--ww-fs-label);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ww-text-muted);
  margin: var(--ww-sp-14) 0 var(--ww-sp-2);
}

.ww-kv span:last-child {
  font-variant-numeric: tabular-nums;
  color: var(--ww-text-muted);
}

/* The Diagnostics card's readout keys ("Firmware", "MCU Temperature", "WiFi
   Strength" …) take the same eyebrow voice as the Settings card's row labels
   — uppercase, letter-spaced, muted — so the two cards read as one labelled
   column instead of two typographic voices on the same page. Scoped to
   .ww-diag-body so it reaches only the Diagnostics card (appended by
   diagnostics.js), not any other .ww-kv consumer. */
.ww-diag-body .ww-kv > span:first-child {
  font-size: var(--ww-fs-label);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ww-text-muted);
}

/* The Diagnostics card's rows read as a continuation of the Settings card above
   them, so match .ww-row's geometry rather than the base .ww-kv's tighter one:
   vertically centred, the same 9 px row padding, and body-size value text
   (the base .ww-kv shrinks to 0.88rem). The base .ww-kv border-top is suppressed
   too — mirroring .ww-settings-card .ww-row — so the only dividers are the
   .ww-diag-sec section breaks below, not a hairline on every row. Scoped to
   .ww-diag-body so it only touches the Diagnostics card. */
.ww-diag-body .ww-kv {
  align-items: center;
  padding: 9px 0;
  border-top: 0;
  font-size: var(--ww-fs-body);
}

/* The Diagnostics card groups its rows into sections (.ww-diag-sec) that mirror
   the Settings card's .ww-settings-sec pattern: system readouts (Firmware, MCU
   Temperature, WiFi Strength) above, the gated heap/loop/reset debug group below.
   A single divider between adjacent sections — same border/spacing voice as the
   hub's .ww-settings-sec + .ww-settings-sec rule, shared here so every device's
   Diagnostics card reads the same. */
.ww-diag-body .ww-diag-sec + .ww-diag-sec {
  border-top: 1px solid var(--ww-border);
  margin-top: var(--ww-sp-8);
  padding-top: var(--ww-sp-8);
}

.ww-mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.ww-device-id:empty {
  display: none;
}

.ww-text-muted {
  color: var(--ww-text-muted);
  font-size: 0.85rem;
}

/* Clear of the dock, at every width, from the BASE rule.
 *
 * The toast is bottom-CENTRED and the dock is bottom-right, so on a narrow phone
 * the two overlap outright. Both phone stylesheets used to lift it to 80px and
 * both spent a paragraph on the same bug — the lift was written, then lost to
 * cascade order when the base rule moved into this file and won, so every toast
 * rendered behind the bar. One unconditional value cannot be lost that way, and
 * the dock is fixed on desktop too, where the old lift did not apply at all. */
.ww-toast {
  position: fixed;
  left: 50%;
  bottom: calc(76px + env(safe-area-inset-bottom));
  transform: translateX(-50%) translateY(20px);
  background: var(--ww-text);
  color: var(--ww-bg);
  padding: var(--ww-sp-10) var(--ww-sp-18);
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s, transform 0.2s;
  z-index: 50;
}

.ww-toast.is-shown {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}

/* ---- inline form-save feedback (flashField) ---- */
.ww-flash-host {
  position: relative;
}

.ww-flash {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--ww-text-muted);
  background: var(--ww-bg);
  padding: 2px 4px;
  border-radius: 999px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
  z-index: 5;
}

.ww-flash.is-shown {
  opacity: 1;
}

.ww-flash.is-error {
  color: var(--ww-danger);
}

.ww-flash svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* A dialog variant that centers its title (e.g. the QR-code verification dialog). */
.ww-dialog.ww-center-title .ww-dialog-title {
  text-align: center;
}

/* App-specific layout. NOT part of the shared design language.
 *
 * The files beside this one (fonts/tokens/reset/chrome/components) are vendored
 * byte-identical from web/src and guarded by tools/check-design-sync.mjs. This
 * file is Hub's own, so it is NOT in SOURCE.json and never syncs anywhere: it
 * holds layout that only makes sense for an app with a hundred entities and a
 * scrolling list of discovered devices, which no device page has.
 *
 * Editing the vendored files is a change to BOTH products — the appliance and
 * every device page — so "the app needed a tighter card" is not a reason to move
 * a lamp on a pump controller. Take such a change to web/src, re-vendor, and
 * re-record the hashes.
 *
 * Loaded LAST in the build order, so these rules win on specificity ties.
 */

/* ------------------------------------------------------------ utilities -- */

/* Read aloud, never drawn. For a word that a glyph has replaced on screen but
 * that a screen reader still needs — the sheet's Name and Room labels.
 *
 * NOT components.css's `.ww-hidden`, which is `display: none` and therefore hides
 * from assistive technology too. The clip-rect dance is the standard one: the
 * element must stay in the layout and keep a non-zero box, or several screen
 * readers skip it. */
.ww-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ------------------------------------------------------------ dark mode -- */

/* tokens.css owns the palette, in both themes. This file owns three derived
 * values it needs and no device page does.
 *
 * # What used to be here, and why it is gone
 *
 * This file carried a WHOLE SECOND dark palette — every colour token restated,
 * twice, under `html:not([data-ww-theme="light"])` and `html[data-ww-theme]`. The
 * reason given was that tokens.css was vendored from another repo and a palette
 * in it would be a cross-repo commit against every device page. That reason was
 * already false: tokens.css has had a full dark theme since the accessibility
 * pass. The two palettes were near-identical (#14150f against #15160f, and so on
 * down the list) and diverging one value at a time, with nothing able to see it —
 * the sync check pointed at a path that no longer existed.
 *
 * Two palettes for one product is a fork. tokens.css is the one, and this file
 * now restates NOTHING it declares.
 *
 * # The attribute is `data-theme`, not `data-ww-theme`
 *
 * That was the other half of the fork: the shared switch keys off `[data-theme]`
 * and this app wrote `[data-ww-theme]`, so the two products could not have shared
 * a stylesheet even if the values had matched. `ui/theme.js` writes the shared
 * name now. The mechanism is unchanged — absent means "follow the OS", which is
 * what lets the media query in tokens.css keep deciding.
 *
 * # The three that stay, and why they are not in tokens.css
 *
 * `--ww-fill`, `--ww-grain-blend` and `--ww-grain-opacity` are the app's own —
 * same standing as `--ww-spring` below. No device page has a level fill or a
 * grain overlay. They live here so the rules that change with the theme are
 * written ONCE against a variable rather than duplicated under both selectors.
 *
 * They are declared under the SAME three selectors tokens.css uses, in the same
 * order, so they flip with the palette rather than a frame behind it. The ink on
 * a brand fill is `--ww-on-accent`, which tokens.css has carried all along; this
 * file used to call it `--ww-on-accent` and define it itself. Same idea, two names,
 * one of them shared — so the shared one won. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ww-grain-blend: screen;
    --ww-grain-opacity: 0.28;
    /* 45%, not the light palette's 65%. The fill and the surface around it have
       swapped which is darker, so a level now has to read as a DEEP green
       against near-black rather than a mid green against cream — and the number
       written across it, which straddles the fill line and so cannot change
       colour partway, has to stay light either side. Measured: light ink on this
       is 4.97:1, against 5.34:1 for the light palette's fill. Leaving it at 65%
       would be 3.22:1 and a reading nobody can take at a glance. */
    --ww-fill: color-mix(in srgb, var(--ww-primary) 45%, var(--ww-surface));
  }
}

:root[data-theme="dark"] {
  --ww-grain-blend: screen;
  --ww-grain-opacity: 0.28;
  --ww-fill: color-mix(in srgb, var(--ww-primary) 45%, var(--ww-surface));
}

/* The light values for the app's own three. `color-scheme` is reset.css's now —
 * it declares `light dark` on :root, and tokens.css pins the single-value form
 * under [data-theme]. Both of those used to be duplicated here. */
:root {
  --ww-grain-blend: multiply;
  --ww-grain-opacity: 0.5;
  --ww-fill: color-mix(in srgb, var(--ww-primary) 65%, var(--ww-surface));
}

/* Ink on a bright fill.
 *
 * Each of these paints `--ww-primary`, `--ww-on` or `--ww-danger` across an
 * element and writes white on top — correct on cream, where those colours are
 * dark, and unreadable after dark, where they are light.
 *
 * These two are components.css's and are overridden here; the app's own filled
 * controls — the dock button, the big toggle, the pills, the armed action — take
 * `--ww-on-accent` directly at the rule that colours them, further down this file.
 * Anything new that fills with a brand colour belongs on that variable, and
 * forgetting is loud rather than subtle: white on light green is a control
 * nobody can read at all.
 *
 * Ties on specificity with components.css's own `.ww-state-tile.is-on` and wins on
 * load order, which is what this file is for. */
:is(.ww-state-tile, .ww-badge).is-on {
  color: var(--ww-on-accent);
}

/* --------------------------------------------------------- motion (WS0) -- */

/* The "feel" layer of the iOS-Home grammar: a tile dips under the finger and
 * springs back. Foundation only — the .is-pressing class is toggled by the
 * tiles in WS1; motion.js owns the JS-driven transforms (the sheet spring).
 *
 * `--ww-spring` is app-owned and NOT in tokens.css: that file is vendored
 * byte-identical from the firmware repo and guarded by `npm run check:design`,
 * and no device page has a press-scale. If this ever earns a place in the shared
 * language it is a deliberate two-repo commit, not a silent edit here. The curve
 * matches the one components.css already uses for the sheet's spring-back, so the
 * two motions read as one hand. */
:root {
  --ww-spring: cubic-bezier(0.2, 0, 0, 1);
}

/* A press-scale needs a transition on `transform`, but a CSS `transition` is one
 * declaration that REPLACES rather than merges — setting it here would clobber
 * components.css's background/color/border-color transition on the same element
 * and the on/off tint would snap instead of ease. So the vendored longhands are
 * restated alongside `transform`. Scoped to `.ww-view` (the app's own surface)
 * and one specificity step above components.css's bare `.ww-state-tile`, so this wins
 * the tie without `!important`. */
.ww-view .ww-state-tile {
  transition:
    background 0.15s ease,
    color 0.15s ease,
    border-color 0.15s ease,
    transform 0.12s var(--ww-spring);
}

/* The climate tile sets its own `background` transition (the warm/cool wash), so
 * it needs `transform` folded into that one too or it would not dip. */
.ww-view .ww-climate {
  transition:
    background 0.2s ease,
    transform 0.12s var(--ww-spring);
}

/* Held down: dip ~3%. Small enough to read as a press and not as a jump, and it
 * scales from the centre so a two-column grid stays visually still around it. */
.ww-view .ww-state-tile.is-pressing {
  transform: scale(0.97);
}

/* Reduce-motion: no dip. The transition is left in place but with nothing to
 * animate to, it never fires. Covers every tile family, dimmer and climate
 * included, because .is-pressing is the single class all of them toggle. */
@media (prefers-reduced-motion: reduce) {
  .ww-view .ww-state-tile.is-pressing {
    transform: none;
  }
}

/* ------------------------------------------------------- made, not milled -- */

/* Solarpunk's visual ancestry is Art Nouveau and Arts & Crafts: organic line,
 * visible making, texture over gloss. The palette was already most of the way
 * there — cream paper, botanical green, terracotta, a display serif — so what
 * follows is about FORM, which was the machined part.
 *
 * One corner out of four. A uniform 16px radius on every card is the most
 * milled thing in the UI; breaking one corner reads as handmade at a distance
 * without ever announcing itself. It is deliberately the bottom-left — the
 * corner the eye leaves last on a left-to-right scan. */
.ww-view .ww-card {
  border-radius: var(--ww-radius) var(--ww-radius) var(--ww-radius) 6px;
}

/* Paper, not a hex value.
 *
 * A fractal-noise grain over the whole page, at an opacity where you cannot see
 * it and would notice it gone. Inline as a data URI: the CSP on an artifact and
 * the offline requirement on the appliance both rule out fetching a texture,
 * and it is a few hundred bytes this way.
 *
 * The blend mode is a variable because it has to reverse after dark. `multiply`
 * darkens, which is what makes noise read as fibre on cream; on near-black it
 * darkens toward nothing and the grain disappears, taking with it the one thing
 * that keeps large flat areas from looking like a screen rather than a surface.
 * `screen` lightens instead, at a lower opacity because light specks on a dark
 * ground are far more visible than dark specks on a light one. */
.ww-shell::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: var(--ww-grain-opacity);
  mix-blend-mode: var(--ww-grain-blend);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)' opacity='0.055'/%3E%3C/svg%3E");
}

/* Golden hour, over everything.
 *
 * `--ww-warmth` is set by the shell from the real solar elevation (sun.js), so
 * this peaks when the sun is ON the horizon and falls away in both directions —
 * which is what golden hour physically is.
 *
 * Held at 4% at its strongest, and only across the top third. The first attempt
 * ran to 9% over the whole page and at -5° elevation — a perfectly ordinary
 * dusk — the entire app went peach. The effect has to be one you notice on the
 * way past, not one you can name.
 *
 * Above the grain, below everything else, and inert. */
.ww-shell::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg, rgb(207 138 60 / 0.055), rgb(181 101 74 / 0.015) 35%, transparent 65%);
  opacity: var(--ww-warmth, 0);
  transition: opacity 2s linear;
}

/* ----------------------------------------------------------- topbar -- */

/* There are no topbar rules here any more, and that is the point.
 *
 * This file used to override the shared bar four ways: a 36px/1fr/36px grid to
 * hold the wordmark centred against the theme button, a tighter padding to suit
 * it, the button sized to 36px, and its own focus ring. All of that existed to
 * solve one problem — keeping a centred brand centred — and the shared bar has
 * no centre to keep any more. It ranges the mark left and the toggle right, so
 * the overrides had nothing left to do and the app takes chrome.css whole.
 *
 * Three things changed for this app when they went, all accepted deliberately:
 * the toggle is 40px rather than 36px, it gains chrome.css's hover background,
 * and its focus ring is now the shared --ww-focus tokens rather than a 2px ink
 * outline.
 *
 * The fourth is the one to know about. chrome.css puts the left/right safe-area
 * insets on .ww-shell and leaves the TOP inset to whatever sits there, and what
 * sat there was the `@media (max-width: 620px)` rule this file used to carry. No
 * one takes it now, so on a notched phone the bar sits that much higher. If that
 * reads badly on hardware the fix belongs in chrome.css, taken once for all
 * eight products, rather than restored here — putting it back would rebuild the
 * divergence this commit removed. */

/* Both washes sit BEHIND the content, via a stacking context on the shell.
 *
 * There used to be a standing warning here against giving .ww-topbar `position`
 * or `z-index` from this file: an early attempt at `position: relative; z-index:
 * 1` silently overrode the vendored `position: fixed` (app.css loads last and
 * the specificity tied), and the topbar scrolled away with the page. The
 * vendored rule is gone — the shared chrome keeps the bar in flow at every width
 * — so there is no longer a tie to lose. The warning is kept as history rather
 * than as a rule, because the failure it describes is a good one to recognise.
 *
 * `isolation: isolate` keeps the z-index: -1 pseudo-elements inside the shell
 * rather than letting them slip behind the page background, and unlike
 * transform or filter it does not turn the shell into a containing block, so
 * the fixed dock stays fixed to the viewport. */
.ww-shell {
  isolation: isolate;
}

/* A rule after the words.
 *
 * Section headings have nothing after them otherwise, so a room's worth of tiles
 * runs into the next room's heading with only whitespace between.
 *
 * Kept plain on purpose. This repeats after every heading on every page — many
 * times per screen — and a decorated separator at that frequency stops reading as
 * a signature and starts reading as texture nobody can switch off. The "made, not
 * milled" marks above are each seen once per card or once per page, which is what
 * lets them carry a motif; this one cannot.
 *
 * `currentColor` so it takes the heading's own colour in whatever context the
 * heading lands in, rather than a second hard-coded palette that could drift from
 * tokens.css unnoticed. */
.ww-section-title {
  display: flex;
  align-items: center;
  gap: 10px;
}

.ww-section-title::after {
  content: "";
  flex: 1;
  min-width: 24px;
  height: 1px;
  background: currentColor;
  opacity: 0.25;
}

/* Bigger than chrome.css sizes it. That 0.95em is measured against a STROKED
   glyph, where the drawing is a thin outline on a 24px field; a filled one puts
   solid ink in the same box and collapses into a blob at small sizes — a
   thermostat and a speaker were indistinguishable smudges over their headings.
   Ties on specificity and wins on load order, which is what app.css is for. */
.ww-section-title .ww-ico {
  width: 1.2em;
  height: 1.2em;
}

/* An empty state is one short muted line, which with no space around it reads
   less like "nothing here" and more like a render that failed. The padding gives
   the sentence enough presence to be read as an answer rather than an absence. */
.ww-view .ww-empty {
  padding: 18px 0;
}

/* --------------------------------------------------------- list rows -- */

/* The generic list row: a name, a supporting line, and one action.
 *
 * Introduced for discovered devices and now also the automations list, which is
 * why it is named for its shape rather than its first caller. The .ww-card
 * treatment is right for a handful of dashboard tiles and wrong for a list: at
 * phone width a card ran ~470px of a 681px viewport, so a customer with 25
 * discovered devices scrolled ~11,000px before reaching their own house. Name
 * and action share a line instead.
 *
 * Anything list-shaped should reuse this. `.ww-disc-note` below is the one piece
 * that really is discovery's alone; its fold shares the areas rules. */
.ww-list-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--ww-surface);
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius-sm);
}

.ww-list-row + .ww-list-row {
  margin-top: 8px;
}

.ww-list-main {
  min-width: 0; /* lets the name ellipsize instead of forcing the row wider */
  flex: 1;
}

.ww-list-name {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--ww-font-display);
  font-size: var(--ww-fs-body);
  overflow: hidden;
}

/* The truncating part is the LABEL, not the row.
 *
 * `text-overflow: ellipsis` has no effect on a flex container's own text: the
 * text becomes an anonymous flex item and the property does not reach it. The
 * result is a name chopped mid-word with no ellipsis at all — "clear prompt
 * once connec" — which reads as a rendering fault rather than as truncation.
 * So the icon and the label are explicit flex items, and the label truncates.
 *
 * One line on purpose: a wrapped name pushes the action button around and makes
 * a list of twenty read as a wall. */
.ww-list-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ww-list-name .ww-ico {
  flex: none;
}

.ww-list-meta {
  color: var(--ww-text-muted);
  font-size: var(--ww-fs-caption);
  margin-top: 2px;
}

/* Ours get the brand colour — the one visual cue that separates a Wala Works
   device from the neighbour's printer, at a glance and without reading. */
.ww-list-row.is-ours .ww-list-name {
  color: var(--ww-primary);
}

.ww-list-action {
  flex: none;
}

/* A tap target, not a link. 44px is the floor Apple and Google both publish,
   and this is a phone-first app. */
.ww-list-action .ww-btn {
  min-height: 44px;
}

/* Discovery's own: the "cannot act on this here" note is specific to a list of
   found devices, not to list rows in general. */
.ww-disc-note {
  color: var(--ww-text-muted);
  font-size: var(--ww-fs-caption);
  white-space: nowrap;
}

/* ------------------------------------------------------------- layout -- */

/* `.ww-view` is a grid, and a grid item defaults to `min-width: auto` — it
   refuses to shrink below its content's min-content width. With a `nowrap`
   device name inside, that meant the item never let the name ellipsize and
   instead pushed the whole view 29px past the viewport, taking the fixed
   topbar and bottom nav with it (fixed bars span the scroll width once a page
   overflows). `min-width: 0` is what lets the ellipsis do its job.

   This is the single most common way a mobile layout overflows, and it is
   invisible until someone renders at a real device width. */
.ww-view > * {
  min-width: 0;
}

/* Custom elements are inline by default, and an inline wrapper around block
   rows lays them out against the wrong containing block. Any element that wraps
   view content needs this. */
.ww-view > ww-discovery,
.ww-view > ww-automations,
.ww-view > ww-attention,
.ww-view > ww-rooms {
  display: block;
}

/* The room page holds a head and a run of foldable sections, and `.ww-view`'s own
   gap cannot reach them — it is one grid item from the outside. Repeating the
   view's grid inside it gives those children the same 16px rhythm as every other
   section boundary in the app, rather than a second set of margins that would
   have to be kept in step with it by hand. */
.ww-view > ww-room {
  display: grid;
  gap: 16px;
  align-content: start;
}

/* Inside a section, tighter than between them: a heading belongs to the tiles
   under it, and at the same 16px it belongs to nothing in particular. The
   difference is also what makes a run of shut sections read as a list rather
   than as a page with its contents missing. */
.ww-room-section {
  display: grid;
  gap: 10px;
  align-content: start;
}

/* ------------------------------------------------------------- dock -- */

/* The dock is `web/src/ui/ww-dock.js` and `chrome.css` styles it — the same
 * element, and the same rules, the device pages use. What was here was 245 lines
 * drawing a second one: a horizontal row of labelled pills, plus a bridge block
 * that undid the arc's positioning so the two stylesheets would not fight while
 * this app still drew the row. Both are gone.
 *
 * The badge rules went with them. `chrome.css` has carried `.ww-dock-badge`
 * since the arc was written, marked "nothing renders one yet" — this app is its
 * first consumer, and it is the shared rule that draws it now.
 *
 * What is left is the two clearances, because they are about THIS shell rather
 * than about the dock. `chrome.css` reserves 83px at the foot of `.ww-shell`,
 * derived from `--ww-dock-btn` (55px) + 16px + 12px, which is now correct for
 * this app too — so the 72px override that was here (sized for a 44px pill) is
 * deleted rather than re-tuned, and the shared value is simply inherited.
 *
 * The toast still needs its own: it is bottom-CENTRED and the dock is bottom-
 * right, so at 360px the two overlap outright. 83px + a little, to clear the
 * same disc. */
.ww-toast {
  bottom: calc(87px + env(safe-area-inset-bottom));
}

/* ------------------------------------------------------ automations -- */

/* Room around the headings on the Automation page.
 *
 * Every other page's sections are direct children of `.ww-view`, so its 16px
 * grid gap does the spacing. This whole page is ONE grid item — three sections
 * inside a single component — and the gap cannot reach in, so without these
 * rules each heading has only the vendored `6px / -2px` margins: a finder card
 * almost touching the "Automations" below it, and a heading sitting on its own
 * first row.
 *
 * 22 and 14 are not new numbers. They are what a heading gets everywhere else
 * once `.ww-view`'s 16px gap is added to those same vendored margins, so this
 * page keeps the app's rhythm rather than inventing a second one. */
ww-automations .ww-section-title {
  margin: 0 4px 14px;
}

/* Only a heading with something BEFORE it gets the space above it. The first one
   must not, or its margin — a grid item's, so it collapses with nothing — would
   push this page's content down past every other page's. Lit's own markers are
   comments, which `+` skips, so the first heading correctly has no element
   sibling in front of it. */
ww-automations * + .ww-section-title {
  margin-top: 22px;
}

/* A paused automation is still listed — knowing it exists is the point — but it
   should not compete with the ones that can actually fire. */
.ww-list-row.is-paused .ww-list-name {
  color: var(--ww-text-muted);
}

.ww-toggle-btn {
  min-width: 62px;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius-pill);
  background: var(--ww-surface-muted);
  color: var(--ww-text-muted);
  font-family: var(--ww-font-body);
  font-size: var(--ww-fs-label);
  cursor: pointer;
}

.ww-toggle-btn.is-on {
  background: var(--ww-primary);
  border-color: transparent;
  color: var(--ww-on-accent);
}

/* ----------------------------------------------------------- dimmer -- */

/* The fill IS the control.
 *
 * components.css already says filled green = on. A dimmer is that same fill at a
 * height, so 40% brightness is a tile filled 40% of the way up — no track, no
 * thumb, no slider chrome that would read as borrowed from another product.
 *
 * The tile keeps .ww-state-tile but NOT .is-on's flat background: the fill
 * element supplies the colour instead, at the same 85% mix so a partly-lit tile
 * and a fully-lit one are recognisably the same object. */
.ww-dimmer {
  position: relative;
  overflow: hidden;
  /* The gesture is a vertical drag, and without this the browser claims it for
     scrolling before the first pointermove arrives — the drag simply never
     starts on a touch device. */
  touch-action: none;
  cursor: ns-resize;
}

/* Two tints, not a block.
 *
 * The first attempt filled with the same 85% green .is-on uses. It read as a
 * fault rather than a level: the fill line lands wherever the brightness
 * happens to be, so at 71% it cut straight through "Entrance Color + White
 * Lights", leaving half the name on cream and half on green. No single text
 * colour is legible across that boundary, and there is nowhere safe to put the
 * name because the boundary MOVES.
 *
 * So the lit part and the unlit part are both tints of the brand green, dark
 * ink stays readable on either, and the level reads as the step between them.
 * A lit dimmer is still unmistakably green against an unlit one's cream.
 *
 * 65/25 rather than 45/18, which was legible but so pale that a lit dimmer
 * looked less "on" than the solid cover tile beside it. Ink on the fill measures
 * 5.34:1 — comfortably past WCAG AA's 4.5:1, with room for the colour card to
 * darken the fill further before contrast becomes the binding constraint.
 *
 * The strong tint is `--ww-fill` rather than a literal `color-mix` because the
 * ratio has to change after dark: see the dark-mode block at the top of this
 * file, where the same argument comes out at 45%. The soft one stays 25% in
 * both — it is a wash, and a wash carries no text of its own. */
.ww-dimmer.is-on {
  background: color-mix(in srgb, var(--ww-primary) 25%, var(--ww-surface));
}

.ww-dimmer-fill {
  position: absolute;
  inset: auto 0 0 0;
  background: var(--ww-fill);
  /* Not while dragging — a transition on the value being dragged makes the fill
     trail the finger. 0.15s matches .ww-state-tile's own state transition, so a
     level arriving from HA eases exactly like a state change does. */
  transition: height 0.15s ease;
  pointer-events: none;
}

.ww-dimmer-body {
  position: relative; /* above the fill */
}

/* Text has to stay legible against BOTH the cream surface above the fill line
   and the green below it, and it may straddle the line. Green-on-cream and
   white-on-green cannot both be one colour, so the type stays dark ink and the
   fill carries the state — this is the one place the on-state does not flip the
   text to white. */
.ww-dimmer .ww-state-tile h2,
.ww-dimmer h2,
.ww-dimmer .ww-relay-meta {
  color: var(--ww-ink);
  position: relative;
}

.ww-dimmer.is-dead {
  opacity: 0.55;
  cursor: default;
}

/* ---------------------------------------------------------- thermostat -- */

/* Warm and cool washes.
 *
 * These are the only two colours in the app that are not the brand green, and
 * they have to be: green for "cooling" would mean nothing at all, and a
 * thermostat's whole job is the difference between heating and cooling.
 *
 * **Deliberately NOT in tokens.css.** That file is vendored byte-identical from
 * the firmware repo and guarded by `npm run check:design` — adding tokens there
 * is a cross-repo change, and no device page has a thermostat. If these ever
 * earn a place in the shared language that is a two-repo commit, made on
 * purpose.
 *
 * 26% over the surface. Ink measures 11.4:1 on the warm wash and 10.9:1 on the
 * cool one, so the wash can carry meaning without the text having to fight it. */
.ww-climate {
  /* The drag is vertical; without this the browser claims the gesture for
     scrolling before the first pointermove arrives. */
  touch-action: none;
  cursor: ns-resize;
  transition: background 0.2s ease;
}

.ww-climate.is-heating {
  background: color-mix(in srgb, #c8632a 26%, var(--ww-surface));
}

.ww-climate.is-cooling {
  background: color-mix(in srgb, #2f6f9e 26%, var(--ww-surface));
}

/* The current temperature is what someone came to read, so it is the only thing
   on the tile at display scale. The target below it is context. */
.ww-climate-now {
  font-family: var(--ww-font-display);
  font-size: var(--ww-fs-h2);
  line-height: 1.1;
  color: var(--ww-ink);
}

.ww-climate h2,
.ww-climate .ww-relay-meta {
  color: var(--ww-ink);
}

.ww-climate.is-dead {
  opacity: 0.55;
  cursor: default;
}

/* -------------------------------------------------------------- sheet -- */

.ww-sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--ww-ink) 45%, transparent);
}

.ww-sheet {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 41;
  max-height: 85vh;
  overflow-y: auto;
  background: var(--ww-bg);
  border-top: 1px solid var(--ww-border);
  border-radius: var(--ww-radius) var(--ww-radius) 0 0;
  /* Clears the home indicator on a notched phone; the bottom nav is behind the
     scrim so its own reservation does not apply here. */
  padding: 10px 16px calc(18px + env(safe-area-inset-bottom));
}

.ww-sheet:focus {
  outline: none; /* focused programmatically on open, not by the user */
}

/* Spring back from a drag that did not go far enough to dismiss. Suppressed
   while the finger is down (.is-dragging) or every pointermove would be chasing
   this ease and the sheet would visibly lag the thumb. */
.ww-sheet {
  transition: transform 0.22s cubic-bezier(0.2, 0, 0, 1);
}

.ww-sheet.is-dragging {
  transition: none;
}

.ww-sheet-scrim {
  transition: opacity 0.22s ease;
}

.ww-sheet-grab {
  width: 38px;
  height: 4px;
  border-radius: var(--ww-radius-pill);
  background: var(--ww-border);
  /* Padded out to a real touch target while still painting as a 4px bar: the
     handle is a hint about a gesture, not the whole hit area for it. The
     negative top margin keeps the visible bar where it was. */
  box-sizing: content-box;
  padding: 8px 0;
  margin: -8px auto 6px;
  background-clip: content-box;
}

/* The two drag surfaces. Without this the browser claims the vertical gesture
   for scrolling `.ww-sheet` (overflow-y: auto) and the dismiss never sees the
   pointermove events — the sheet refuses to move on a real phone while working
   perfectly with a mouse, which is the worst kind of bug to find late. */
.ww-sheet-grab,
.ww-sheet-head {
  touch-action: none;
}

/* The name and the reading on the left, the edit toggle ranged right. The
   headings are wrapped in their own box rather than laid out beside the button
   individually, so the subtitle — which is the longer line — wraps against the
   button's edge instead of running underneath it.

   `.ww-sheet-headings` is REQUIRED, not decorative, and every sheet head has to
   have one. The two dialogs put their title and subtitle straight into the head
   when this rule arrived, so the flex row ranged the subtitle beside the title
   and "Add a room" lost half its width to "It will appear on Home and here". A
   head is a row of at most two things: the headings, and an optional control. */
.ww-sheet-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.ww-sheet-headings {
  min-width: 0;
  /* Take the row when there is no control beside it, so a long room name has
     the whole panel to wrap into rather than only its own content width. */
  flex: 1;
}

.ww-sheet-title {
  display: flex;
  align-items: center;
  gap: 9px;
  font-family: var(--ww-font-display);
  font-size: var(--ww-fs-h2);
  color: var(--ww-primary);
}

/* The sheet's two faces, as one button.
 *
 * Filled when the settings are showing, which is the same grammar `.ww-pill`
 * and every lit tile already use for "this is on" — rather than swapping the
 * pencil for a cross, which would move a landmark somebody had just learned.
 *
 * `flex: none` so a long entity name cannot squeeze it below the touch floor. */
.ww-sheet-edit {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  /* Pulled up and out so the glyph optically aligns with the title rather than
     sitting a touch-target's worth of padding below it. */
  margin: -8px -6px 0 0;
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius-pill);
  background: var(--ww-surface);
  color: var(--ww-text-muted);
  cursor: pointer;
}

.ww-sheet-edit.is-active {
  background: var(--ww-primary);
  border-color: transparent;
  color: var(--ww-on-accent);
}

.ww-sheet-sub {
  margin-top: 4px;
  color: var(--ww-text-muted);
  font-size: var(--ww-fs-caption);
}

.ww-sheet-section {
  margin-top: 20px;
}

.ww-sheet-label {
  font-size: var(--ww-fs-label);
  color: var(--ww-text-muted);
  margin-bottom: 8px;
}

/* A hue or temperature ramp. 44px tall so it is a comfortable target on a
   phone, and touch-action: none because it is dragged horizontally — without it
   the browser claims the gesture for the sheet's own vertical scroll. */
.ww-strip {
  height: 44px;
  border-radius: var(--ww-radius-sm);
  /* Firmer than --ww-border on purpose. A bulb's warm-to-cool ramp ends at
     near-white around 6500K — which is physically right and not something to
     fake bluer — and against the cream sheet that end had no edge at all, so
     the control appeared to stop halfway. */
  border: 1px solid color-mix(in srgb, var(--ww-ink) 22%, transparent);
  touch-action: none;
  cursor: pointer;
}

.ww-strip + .ww-strip {
  margin-top: 8px;
}

/* Where the value sits. The colour strips do not need one — the light itself
   shows you what you picked — but two temperature ramps side by side are
   identical gradients, and without a marker neither says which end of the band
   it is. */
.ww-strip {
  position: relative;
}

.ww-strip-thumb {
  position: absolute;
  top: 50%;
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  border-radius: var(--ww-radius-pill);
  background: var(--ww-surface);
  /* A ring rather than a fill: the strip's own colour must stay visible through
     the marker, since that colour is the value. */
  border: 2px solid var(--ww-ink);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
  pointer-events: none;
}

.ww-strip:focus-visible {
  outline: 2px solid var(--ww-ink);
  outline-offset: 2px;
}

/* A label and its current value on one line, above the control that changes it.
   The value is what someone checks after a drag, so it sits where their eye
   already is rather than only inside the strip. */
.ww-sheet-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}

.ww-sheet-rowlabel {
  font-size: var(--ww-fs-label);
  color: var(--ww-text-muted);
}

.ww-sheet-rowvalue {
  font-family: var(--ww-font-display);
  font-size: var(--ww-fs-body);
}

.ww-sheet-row + .ww-strip + .ww-sheet-row {
  margin-top: 16px;
}

/* Choices that wrap: a thermostat can offer six modes and five fan speeds, and
   the segmented control's equal columns would squeeze those to unreadable. */
.ww-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.ww-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius-pill);
  background: var(--ww-surface);
  color: var(--ww-text);
  font: inherit;
  font-size: var(--ww-fs-label);
  cursor: pointer;
}

.ww-pill.is-active {
  background: var(--ww-primary);
  border-color: transparent;
  color: var(--ww-on-accent);
}

.ww-pill .ww-ico {
  width: 1.05em;
  height: 1.05em;
}

/* Discrete actions in a drawer — open/stop/close, lock/unlock, start/dock.
 *
 * Deliberately not the tile's `.ww-inline` icon row. A tile has ~150px and has
 * to say "Stop" as a square glyph the customer decodes under time pressure; a
 * drawer has the full width and can afford the word. Equal columns because
 * these come in sets of two or three and a ragged row of verbs reads as a
 * ranking they do not have.
 *
 * `min-height: 48px` rather than the 44px floor the pills use: these are the
 * consequential ones. Unlocking a door and docking a vacuum should not be the
 * same size as picking a fan speed. */
.ww-sheet-actions {
  display: flex;
  gap: 8px;
}

.ww-sheet-actions .ww-btn {
  flex: 1;
  min-width: 0;
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

/* An action that has been armed and is waiting for a second press — today only
   the lock's Unlock. It has to look unmistakably different from the button that
   was there a moment ago, because the whole mechanism rests on someone noticing
   that the first press did something other than what they expected. Filled in
   the danger colour, which nothing else in a drawer uses. */
.ww-sheet-actions .ww-btn.is-armed {
  background: var(--ww-danger);
  border-color: transparent;
  color: var(--ww-on-accent);
}

.ww-sheet-done {
  width: 100%;
  margin-top: 22px;
  min-height: 48px;
}

/* A field as one row: glyph, then control.
 *
 * components.css's `.ww-field` is a grid that stacks a label above its control,
 * which is right for a settings form and wrong inside a sheet that already
 * scrolls — four lines to name two things that name themselves. Undone here
 * rather than there: that file is vendored, and every other `.ww-field` in the
 * app (Settings, the finder) still wants the stack.
 *
 * Both sheets share the grammar rather than each having their own, so a name
 * box is the same shape whichever one you opened. */
.ww-sheet-details .ww-field,
.ww-room-new .ww-field {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 10px;
}

/* Just wide enough for the glyph. `flex: none` so the control takes every pixel
   left over — this is a label, and it should cost the field as little width as
   a word would have cost it height. */
.ww-sheet-details .ww-field > label,
.ww-room-new .ww-field > label {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  color: var(--ww-text-muted);
}

.ww-sheet-details .ww-field > label .ww-ico,
.ww-room-new .ww-field > label .ww-ico {
  width: 20px;
  height: 20px;
}

/* `min-width: 0` is the load-bearing half. A flex item's default minimum is its
   CONTENT width, so a long entity name or "Bathroom · First floor" would push
   the row wider than the sheet instead of ellipsing inside it. The touch heights
   are the app's own: components.css sizes these for a pointer. */
.ww-sheet-details input[type="text"],
.ww-sheet-details input[type="search"],
.ww-sheet-details select,
.ww-room-new input[type="text"],
.ww-room-new input[type="search"] {
  flex: 1;
  min-width: 0;
  min-height: 44px;
}

.ww-sheet-error {
  margin: 10px 0 0;
  color: var(--ww-danger);
  font-size: var(--ww-fs-caption);
  line-height: 1.4;
}

/* --------------------------------------------------------------- first run -- */

/* The sign-in and create-account screen (ROADMAP 6.6).
 *
 * The one view with no dock, no topbar content and nothing else on it, so the
 * card is centred rather than filling the column the way every other view does.
 * Capped in `ch` rather than `px`: what makes this readable is the line length
 * of the paragraph explaining that the password cannot be recovered, and that
 * is a measure of text, not of screen. */
.ww-login {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
}

.ww-login > .ww-card {
  width: 100%;
  max-width: 52ch;
}

/* `.ww-field` is components.css's stacked label-above-control, which is what a form
   wants, so nothing is undone here — unlike the sheets above. */
.ww-login .ww-field {
  margin: 0 0 14px;
}

/* Same treatment as a sheet's error, and deliberately not a banner: it sits
   with the field it is about. */
.ww-login-error {
  margin: 0 0 12px;
  color: var(--ww-danger);
  font-size: var(--ww-fs-caption);
  line-height: 1.4;
}

/* ------------------------------------------------------------------- camera -- */

/* A camera's feed, in the drawer and on its tile.
 *
 * `ui/cards/camera.js` owns the markup and decides nothing about how it looks —
 * the same bargain every component here makes. What is decided below is that a
 * feed is a 16:9 box on a dark mat, because the space around a frame that has
 * not arrived, or that does not fill the box, should read as the inside of a
 * camera rather than as a hole in the sheet.
 *
 * # The mat does NOT follow the theme, and that is the exception
 *
 * Every other surface in this app flips: `--ww-ink` is near-black on cream and
 * near-white after dark, which is right for text and wrong for this. A
 * photograph's surround is dark in every viewer anybody has used, and a light
 * mat around a night-vision hallway is a bright rectangle held up in a dark
 * room at the moment somebody is checking whether their front door is shut.
 *
 * So these two are the app's own, scoped to the camera markup rather than added
 * to the palette: nothing else should be reaching for a colour that refuses the
 * theme, and putting them in `:root` would invite it.
 *
 * A custom element is inline by default; without `display: block` the drawer's
 * feed would be laid out in a line box and pick up a descender's worth of gap. */
ww-camera-drawer,
ww-camera-tile {
  display: block;
}

.ww-camera,
.ww-camera-full,
.ww-view .ww-camera-tile {
  --ww-camera-mat: #14150f;
  --ww-camera-on-mat: #e9e6d9;
}

.ww-camera {
  position: relative;
  margin: 0;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--ww-radius);
  border: 1px solid var(--ww-border);
  background: var(--ww-camera-mat);
}

/* `contain`, not `cover`: a doorbell camera is portrait and a driveway camera is
   wide, and cropping either to fill the box hides the part of the scene the
   customer put the camera there to see. Letterboxing costs some dark pixels and
   shows the whole frame. */
.ww-camera-feed {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* Holds the box open while the feed is away being full screen, so the drawer
   does not collapse and re-expand around the buttons under it. */
.ww-camera-placeholder {
  width: 100%;
  height: 100%;
}

/* Why there is a LIVE badge at all: MJPEG and a still are the same element with
   the same URL shape, and a stationary scene looks identical either way. Without
   this the only way to know whether the picture is current is to wait for
   something to move in it. */
.ww-camera-live {
  position: absolute;
  top: 8px;
  left: 8px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--ww-radius-pill);
  background: color-mix(in srgb, var(--ww-camera-mat) 65%, transparent);
  color: var(--ww-camera-on-mat);
  font-size: var(--ww-fs-label);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.ww-camera-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ww-danger);
}

@media (prefers-reduced-motion: no-preference) {
  .ww-camera-dot {
    animation: ww-camera-pulse 2s ease-in-out infinite;
  }
}

@keyframes ww-camera-pulse {
  50% {
    opacity: 0.35;
  }
}

/* What stands in for the picture when there is not one: switched off, no token,
   or a load that failed. Centred in the same box the feed would have filled, so
   the drawer is the same shape either way. */
.ww-camera-note {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px;
  text-align: center;
  color: var(--ww-camera-on-mat);
}

.ww-camera-note .ww-ico {
  width: 28px;
  height: 28px;
  opacity: 0.7;
}

.ww-camera-note p {
  margin: 0;
  font-size: var(--ww-fs-caption);
  line-height: 1.4;
  opacity: 0.85;
  max-width: 30ch;
}

/* The feed, filling the viewport. Above the sheet (41) and its scrim (40),
   because it is on top of both. See camera.js for why this is an overlay rather
   than the Fullscreen API. */
.ww-camera-full {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  /* A turned frame is laid out at the viewport's HEIGHT and only then rotated
     into place, so it overflows this box horizontally before the transform is
     applied. Clipped rather than allowed to give the page a scrollbar. */
  overflow: hidden;
  background: var(--ww-camera-mat);
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom)
    env(safe-area-inset-left);
}

.ww-camera-full:focus {
  outline: none; /* focused programmatically on open, not by the user */
}

/* The feed FILLS the overlay rather than sitting at whatever size the camera
   happens to encode at. `max-width` and an auto width look like the careful
   answer and are the wrong one: a 320x180 stream — which is what plenty of
   cameras publish, and what `fake-ha` serves — would then paint 320px wide on a
   360px phone, smaller than the same feed in the sheet it was expanded from.
   `object-fit: contain` on the base rule is what keeps that honest, so nothing
   is cropped and nothing is stretched out of shape. */
.ww-camera-full .ww-camera-feed {
  width: 100%;
  height: 100%;
}

/* A wide frame on an upright phone, turned to fill the long axis.
 *
 * Both halves of the condition are load-bearing and they are answered in
 * different places: `is-turned` is the CARD saying the frames are wider than
 * they are tall (`_wideFrame`, measured from the still), and the media query is
 * the BROWSER saying the phone is upright. Turning a portrait doorbell camera,
 * or turning anything at all once the phone has been rotated, would be worse
 * than not turning at all.
 *
 * The box swaps its axes and the picture is then turned into it: laid out
 * 100vh wide and 100vw tall, `object-fit` fits the frame inside that, and the
 * rotation brings the result upright against the long edge of the screen. Only
 * the picture turns — the LIVE badge and Close stay put, because they are the
 * app talking rather than the camera. */
@media (orientation: portrait) {
  .ww-camera-full.is-turned .ww-camera-feed {
    width: 100vh;
    height: 100vw;
    transform: rotate(90deg);
  }
}

/* Bottom-centre rather than a corner cross: this is the one control on the
   screen, the screen is a phone, and the bottom is where the thumb already is. */
.ww-camera-close {
  position: absolute;
  bottom: calc(16px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
}

/* The tile.
 *
 * The thumbnail sits ABOVE the name row rather than behind it: a name and a
 * state over a photograph is a contrast problem that changes every time the
 * light in the room does, and there is no text colour that is legible against
 * both a night-vision hallway and a sunlit garden.
 *
 * `cover` here where the drawer's feed is `contain`, and the two are not in
 * disagreement: a grid of tiles is a rhythm, and one portrait doorbell camera
 * letterboxed into a 16:9 slot puts two grey bars in the middle of a row. The
 * thumbnail is a pointer at the feed, so it may crop; the feed itself is the
 * thing being looked at, so it may not.
 *
 * The grid is `align-items: start`, so this tile being taller than the toggles
 * beside it costs them nothing. */
.ww-view .ww-camera-tile .ww-camera-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: calc(var(--ww-radius) - 4px);
  background: var(--ww-camera-mat);
  margin-bottom: 8px;
}

/* -------------------------------------------------------------------- graph -- */

/* A measurement's last day. `ww-graph` owns the markup and decides nothing about
   how it looks; everything here is the sizing and the palette, which is the
   bargain every component in this app makes (see ui/base.js).

   A custom element is inline by default, so without this the plot's height would
   be at the mercy of the line box around it. */
ww-graph {
  display: block;
}

.ww-graph {
  margin: 0;
}

/* The end marker hangs half its width past the last reading, which sits exactly
   on the right edge — so the plot is inset by that half on both sides. Without
   the inset the dot marking "now" is sliced down the middle by the sheet. */
.ww-graph-plot {
  position: relative;
  padding: 0 7px;
}

.ww-graph-plot svg {
  display: block;
  width: 100%;
  /* Fixed, because the viewBox is stretched to it (preserveAspectRatio="none")
     rather than setting the shape. Tall enough that a few degrees of overnight
     drift is a visible slope, short enough that the chart does not push the
     sheet's Done button off a phone. */
  height: 104px;
  overflow: visible;
}

/* A wash, never a block. The line is the data; the fill is only there to say
   which side of it is "less". */
.ww-graph-area {
  fill: color-mix(in srgb, var(--ww-primary) 12%, transparent);
}

/* The high and the low, as hairlines. Solid and one step off the surface —
   dashes would read as a threshold or a projection, which is a claim about the
   data that nobody made.

   `non-scaling-stroke` on every stroked mark here is load-bearing rather than
   tidy: the viewBox is stretched to the sheet's width, so a 1-unit line would be
   a hairline on a 360px phone and 2.5px on a tablet. */
.ww-graph-grid {
  stroke: var(--ww-border);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.ww-graph-line {
  fill: none;
  stroke: var(--ww-primary);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* The current reading, as a dot on the right edge. `top` is written inline by
   the component as a percentage of the plot's height; everything else about
   where it sits is here.

   The ring is the sheet's own background rather than a border: it keeps the
   marker legible where it lands on top of the line, and a stroke around a mark
   would be ink that is not data. */
.ww-graph-dot {
  position: absolute;
  right: 7px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--ww-primary);
  box-shadow: 0 0 0 2px var(--ww-bg);
  /* Centred on the last reading in both axes: `right` and `top` place its
     corner, and the translate moves it half its own size back. */
  transform: translate(50%, -50%);
}

/* The two numbers the hairlines stand for, and the two ends of the window. Text
   never wears the data's colour: the line carries the identity, and a caption in
   mid-green on cream is a label nobody can read. */
.ww-graph-legend,
.ww-graph-axis {
  display: flex;
  gap: 12px;
  color: var(--ww-text-muted);
  font-size: var(--ww-fs-caption);
}

/* Both together at the start of the line, NOT ranged to the two ends. Pushed
   apart they sit above "24 hours ago" and "now", and a high in the left corner
   over the words "24 hours ago" reads as a claim about WHEN it was high. */
.ww-graph-legend {
  margin-top: 10px;
  gap: 16px;
}

.ww-graph-legend b {
  color: var(--ww-text);
  font-weight: 600;
}

/* The window's two ends, each under the edge it names. */
.ww-graph-axis {
  justify-content: space-between;
  margin-top: 2px;
}

/* Loading, nothing recorded, or refused. A line of muted text where the chart
   would be, rather than an empty frame — a plot with no line in it reads as a
   fault, and two of these three are not faults at all. */
.ww-graph-note {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  color: var(--ww-text-muted);
  font-size: var(--ww-fs-body);
}

/* ------------------------------------------------------------ making a room -- */

/* Full width, under the last room. A button the width of the cards above it
   reads as one more of them rather than as a control floating on the page. */
.ww-add-room {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-top: 10px;
}

/* ---------------------------------------------------------- picking a glyph -- */

/* Shared by the make-a-room dialog and the entity sheet, which is why these are
   bare class names rather than nested under either. `ww-icon-picker` owns the
   markup; this owns how big it is allowed to get.

   A custom element is inline by default, which would leave the picker's height
   at the mercy of the line box around it. */
ww-icon-picker {
  display: block;
}

/* The grid scrolls inside itself rather than growing the sheet. Sixty glyphs at
   44px would be most of a phone screen, and a dialog whose Add button is below
   the fold is a dialog people cannot finish. `auto-fill` rather than a fixed
   count so the same rule works on a 360px phone and a tablet. */
.ww-icon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: 6px;
  max-height: 168px;
  overflow-y: auto;
  margin-top: 10px;
  padding: 2px;
}

/* Square, and big enough to hit: 44px is the app's touch floor, and a glyph
   picker is the one place in the product where somebody taps a target chosen
   only by its picture. */
.ww-icon-pick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  min-height: 44px;
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius-sm);
  background: var(--ww-surface);
  color: var(--ww-text);
  cursor: pointer;
}

.ww-icon-pick:hover {
  border-color: var(--ww-primary);
}

/* The chosen one is outlined rather than filled. A filled glyph square would
   read as a control that is ON, which is the grammar every coloured thing on
   the Home grid already has. */
.ww-icon-pick.is-picked {
  border-color: var(--ww-primary);
  box-shadow: inset 0 0 0 1px var(--ww-primary);
}

.ww-icon-pick .ww-ico {
  width: 22px;
  height: 22px;
}

/* Cancel and Add, side by side, Add on the right where a thumb finishes. Equal
   widths because neither is the dangerous one — nothing here is destructive,
   and weighting them would imply otherwise.

   Shared with the add-a-device dialog rather than copied. Two modal footers that
   drift apart by four pixels is a thing nobody sees and everybody feels. */
.ww-room-new-actions {
  display: flex;
  gap: 10px;
  margin-top: 14px;
}

.ww-room-new-actions .ww-btn {
  flex: 1;
}

/* ------------------------------------------------------- adding a device -- */

/* Which room a found device is in.
 *
 * The rooms are a scrolling list rather than a `<select>` because this is the
 * whole question on the whole screen — see ww-device-add.js. `max-height` in
 * `dvh` so a sixteen-room house scrolls the LIST and not the dialog: the title
 * says which device this is about and the buttons are how you leave, and both
 * have to stay put while somebody hunts for the bathroom. */
.ww-device-add-rooms {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 46dvh;
  overflow-y: auto;
  margin-top: 4px;
  /* Room for the focus ring on the first and last rows, which an overflow
     container would otherwise clip to nothing. */
  padding: 2px;
}

/* A room as a row: its own glyph, its name, and the floor it is on. The glyph is
   `roomIcon()`'s answer, the same one the room cards and Home draw, so a
   customer picks the kitchen by recognising it rather than by reading. */
.ww-room-pick {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 48px;
  padding: 0 12px;
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius);
  background: var(--ww-surface-muted);
  color: var(--ww-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ww-room-pick .ww-ico {
  flex: none;
  width: 22px;
  height: 22px;
  color: var(--ww-text-muted);
}

/* `min-width: 0` for the same reason the sheet's fields have it: a long room
   name should ellipse inside the row rather than widen it. */
.ww-room-pick-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ww-room-pick-floor {
  flex: none;
  color: var(--ww-text-muted);
  font-size: 13px;
}

/* The chosen room. A ring rather than a fill, matching `.ww-icon-pick`'s chosen
   state, so the two pickers in the app agree about what "this one" looks like. */
.ww-room-pick.is-chosen {
  border-color: var(--ww-primary);
  box-shadow: inset 0 0 0 1px var(--ww-primary);
}

.ww-room-pick.is-chosen .ww-ico {
  color: var(--ww-primary);
}

.ww-room-pick[disabled] {
  opacity: 0.55;
  cursor: default;
}

.ww-device-add-empty {
  margin: 10px 0 0;
}

/* ----------------------------------------------------- sheet controls (WS3) -- */

/* The level bar: drag right for more. The dimmer tile's fill-is-the-control laid
 * along the width of the sheet, with the same 65% brand fill and 0.15s ease as
 * the tile, so a level arriving from HA settles the same way in both places.
 *
 * Full width, because that is the width the drag maps onto: `DragValue` measures
 * this element at pointerdown, so the travel and the geometry cannot drift apart
 * and there is no constant here for anyone to keep in sync.
 *
 * 64px tall — enough to hold the glyph and "100%" on one line and to clear the
 * 44px tap-target floor, short enough that three controls and the details fields
 * still fit a phone without the sheet becoming a page. */
.ww-slider {
  position: relative;
  height: 64px;
  border-radius: var(--ww-radius);
  border: 1px solid var(--ww-border);
  background: var(--ww-surface-muted);
  overflow: hidden;
  /* The sheet scrolls up and down and this drags side to side, so the axes are
     split rather than fought over: `pan-y` hands the browser the vertical
     component and keeps the horizontal one here. `none` would take both and a
     thumb landing on the slider could no longer scroll the sheet at all. */
  touch-action: pan-y;
  cursor: ew-resize;
}

.ww-slider-fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--ww-fill);
  transition: width 0.15s ease;
  pointer-events: none;
}

/* Centred across the whole bar rather than sitting on the fill, so the reading
 * does not walk off the end at low values. Ink rather than a fill-dependent
 * colour because it has both grounds behind it at once — bare surface-muted to
 * the right of the fill, `--ww-fill` to its left — and the boundary between them
 * moves under the finger, so there is nowhere safe to put a second colour. */
.ww-slider-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: var(--ww-ink);
  font-family: var(--ww-font-display);
  font-size: var(--ww-fs-body);
  pointer-events: none;
}

.ww-slider-label .ww-ico {
  width: 22px;
  height: 22px;
}

.ww-slider:focus-visible {
  outline: 2px solid var(--ww-ink);
  outline-offset: 2px;
}

/* The switch sheet's primary control: a big on/off, filled green when on to
 * match the tile's own on-state so the two never disagree at a glance. */
.ww-bigtoggle {
  width: 100%;
  min-height: 92px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius);
  background: var(--ww-surface-muted);
  color: var(--ww-primary);
  font-family: var(--ww-font-display);
  font-size: var(--ww-fs-h3);
  cursor: pointer;
  transition:
    background 0.15s ease,
    color 0.15s ease,
    transform 0.12s var(--ww-spring);
}

.ww-bigtoggle .ww-ico {
  width: 30px;
  height: 30px;
}

.ww-bigtoggle.is-on {
  background: color-mix(in srgb, var(--ww-primary) 85%, transparent);
  border-color: transparent;
  color: var(--ww-on-accent);
}

.ww-bigtoggle:active {
  transform: scale(0.98);
}

.ww-bigtoggle:focus-visible {
  outline: 2px solid var(--ww-ink);
  outline-offset: 2px;
}

/* The sheet springs up from the bottom on open; the scrim fades with it. A
 * one-shot animation, not a transition, so the inline transform the
 * drag-to-dismiss writes still governs once this finishes — an animation only
 * owns the property while it runs. Reduced-motion skips both. */
@media (prefers-reduced-motion: no-preference) {
  .ww-sheet {
    animation: ww-sheet-in 0.28s var(--ww-spring);
  }
  .ww-sheet-scrim {
    animation: ww-scrim-in 0.28s ease;
  }
}

@keyframes ww-sheet-in {
  from {
    transform: translateY(100%);
  }
}

@keyframes ww-scrim-in {
  from {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ww-bigtoggle:active {
    transform: none;
  }
}

/* --------------------------------------------------- two-zone tile (WS1) -- */

/* The iOS-Home tile: a circular icon chip (the toggle) beside a body (the
 * sheet-opener). Structural, so it applies at every width; the size tuning for
 * the phone grid stays in the @media block below. */
.ww-view .ww-tile-top {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 10px;
}

.ww-view .ww-tile-body {
  flex: 1;
  min-width: 0; /* lets the name ellipsize instead of forcing the tile wider */
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* The chip. A 40px disc, brand icon inside. It carries the "power is here"
 * affordance the tile body cannot: a muted disc on an unlit tile, brightening
 * to the surface colour on a lit one so it stays legible against the green
 * fill. A thermostat's and a dead entity's chip are labels (.is-static), not
 * buttons. */
.ww-view .ww-tile-icon {
  flex: none;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: var(--ww-radius-pill);
  border: 1px solid var(--ww-border);
  background: var(--ww-surface-muted);
  color: var(--ww-primary);
  cursor: pointer;
  transition:
    background 0.15s ease,
    color 0.15s ease,
    transform 0.12s var(--ww-spring);
}

.ww-view .ww-tile-icon .ww-ico {
  width: 20px;
  height: 20px;
}

.ww-view .ww-tile-icon.is-static {
  cursor: default;
}

/* Lit: the disc brightens to the surface colour and keeps the brand icon, so it
 * reads as the active control against the tile's filled-green on-state. */
.ww-view .ww-state-tile.is-on .ww-tile-icon {
  background: var(--ww-surface);
  border-color: transparent;
  color: var(--ww-primary);
}

/* The chip dips on its own press, separately from the tile-body press-scale. */
.ww-view .ww-tile-icon:active {
  transform: scale(0.9);
}

.ww-view .ww-tile-icon:focus-visible {
  outline: 2px solid var(--ww-ink);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .ww-view .ww-tile-icon:active {
    transform: none;
  }
}

/* ------------------------------------------- optimistic toggle (WS4) -- */

/* A toggle shows its intended state at once — the card and chip flip green
 * before Home Assistant has confirmed — and .is-pending marks that the state is
 * not yet confirmed: the chip breathes gently until the echo lands or the
 * settle window expires. A steady breath, not a spinner; the tile stays usable,
 * it is only being honest that the answer is not back yet. */
.ww-view .ww-state-tile.is-pending .ww-tile-icon {
  animation: ww-pending-pulse 1s ease-in-out infinite;
}

@keyframes ww-pending-pulse {
  50% {
    opacity: 0.6;
  }
}

/* Reduced-motion keeps the signal without the movement: a held half-opacity
 * instead of a breath. */
@media (prefers-reduced-motion: reduce) {
  .ww-view .ww-state-tile.is-pending .ww-tile-icon {
    animation: none;
    opacity: 0.6;
  }
}

/* No echo inside the settle window: pending-state.js has dropped the optimism
 * back to the truth, and the tile says so where its readout was. Amber, not
 * red — the command was not refused, the device simply did not answer, and it
 * may yet. Only when the tile is NOT lit: on a filled-green tile the white
 * readout is already legible, and amber-on-green would not be. */
.ww-view .ww-state-tile.is-noresponse:not(.is-on) .ww-relay-meta {
  color: var(--ww-amber);
}

/* The sheet's big toggle carries the same two states, so the sheet and the tile
 * behind it never disagree about whether a command is still in flight. */
.ww-bigtoggle.is-pending {
  animation: ww-pending-pulse 1s ease-in-out infinite;
}

.ww-bigtoggle.is-noresponse:not(.is-on) {
  color: var(--ww-amber);
}

@media (prefers-reduced-motion: reduce) {
  .ww-bigtoggle.is-pending {
    animation: none;
    opacity: 0.75;
  }
}


/* ------------------------------------------------------------ tiles -- */

/* Two per row on a phone, and shorter.
 *
 * components.css collapses .ww-grid-2 to one column below 620px, which is right
 * for a device page showing four things and wrong for a house showing ninety.
 * Measured before this: one tile ran ~330px tall on a 360px screen — two per
 * viewport, so a customer scrolled past twenty screens of their own home.
 *
 * Two columns of ~150px tiles put eight on screen. The filled-green on-state
 * keeps enough surface to still read as the brand, which is why this is a
 * smaller tile and not a list row. */
@media (max-width: 620px) {
  .ww-view .ww-grid-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    /* Tiles size to their own content instead of to the tallest in the row.
       A grid item defaults to `stretch`, so one cover tile — name, state and a
       row of three 44px buttons — set the height of every toggle beside it, and
       a toggle has only a name and a word to put in it. That produced ~150px of
       dead air per switch tile and cost more than half the screen. */
    align-items: start;
  }

  .ww-view .ww-state-tile {
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    /* A floor, not a height. A single-line tile measures 63px on its own, so
       this adds ~9px rather than the 21px that left a visible gap under the
       readout — enough to keep a rhythm across the grid and to stay a
       comfortable tap target, without reintroducing dead air. */
    min-height: 72px;
  }

  .ww-view .ww-state-tile h2 {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin: 0;
    font-size: var(--ww-fs-body);
    line-height: 1.25;
    /* Two lines, then ellipsis. "Entrance Color + White Lights" is a real name
       and must not push the state off the tile. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .ww-view .ww-state-tile h2 .ww-ico {
    flex: none;
    width: 1.1em;
    height: 1.1em;
  }

  /* Sits under the name rather than pushed to the floor of the tile. With
     `margin-top: auto` the readout was bottom-pinned, which only looked
     deliberate while every tile was forced to the same height. */
  .ww-view .ww-state-tile .ww-relay-meta {
    margin-top: 0;
    font-size: var(--ww-fs-caption);
  }

  /* Action rows (cover, lock) sit under the readout and must stay tappable:
     44px is the published floor on both platforms. */
  .ww-view .ww-state-tile .ww-inline {
    gap: 6px;
    margin-top: 6px;
  }

  .ww-view .ww-state-tile .ww-inline .ww-btn {
    flex: 1;
    min-width: 0;
    min-height: 40px;
    padding: 0;
  }
}

/* -------------------------------------------------- needs attention -- */

/* The fault list on Settings: a section heading that folds its own tiles away.
 *
 * A heading rather than the card-shaped fold the Devices page uses, because what
 * is inside is a grid of tiles and a tile is already a card — nesting them would
 * put a box round a box for no gain. The 16px gap is `.ww-view`'s own, repeated
 * here for the same reason `ww-room` repeats it: the view's gap cannot reach
 * inside one of its grid items. */
.ww-attention {
  display: grid;
  gap: 16px;
}

/* The heading is a real <button> — it toggles, it carries aria-expanded, and a
   <div> with a click handler is none of that. Only the button chrome is undone;
   the font, colour and small-caps come from .ww-section-title, and restating any
   of them here would win the specificity tie and silently make this heading
   different from every other one on the page. */
/* The room page's section headings fold the same way and are listed alongside
   rather than given rules of their own, so the two folds in the app cannot drift
   into looking like two different mechanisms. */
.ww-attention-head,
.ww-room-section-head {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  /* 44px is the tap-target floor on both platforms, and a section heading is
     ~17px of text. */
  min-height: 44px;
}

/* .ww-section-title's rule line is an ::after, which is the LAST flex item, so
   without this the caret lands between the count and the rule instead of at the
   right edge where every other caret in the app sits. */
.ww-attention-caret,
.ww-room-section-caret {
  order: 1;
  display: inline-flex;
}

.ww-collapsed .ww-attention-body,
.ww-collapsed .ww-room-section-body {
  display: none;
}

/* ------------------------------------------------------------- about -- */

/* The About card is three facts, not a table.
 *
 * components.css rules every `.ww-kv` off with a top border, which earns its keep
 * on the appliance's Readouts page — dozens of dense rows where the line is what
 * keeps an eye on one of them. Three rows in a card already have the card's own
 * edge doing that, so the rules only add a stripe under its top edge and two
 * more that separate nothing.
 *
 * Overridden here rather than edited there: components.css is vendored and
 * hash-guarded by `check:design`, and app.css loads last, so this wins the tie
 * without touching the design language. */
.ww-view .ww-kv {
  border-top: 0;
}

/* ------------------------------------------------------------- areas -- */

/* The Devices page: cards that collapse to just their header — one per room, and
 * the discovered-devices list under them.
 *
 * The collapse itself is chrome.css's grammar (.ww-out-head as the header,
 * .ww-collapsed on the card) so this page opens and closes like the device
 * Diagnostics page rather than inventing a third way to fold something. What is
 * here is only what a page of rooms needs and no device page does: the header as
 * a real <button>, and the device -> entity nesting inside.
 *
 * A card per area rather than one long list with sticky headings, because the
 * card edge is what makes a collapsed room read as closed rather than as a gap.
 *
 * Discovery shares these rules rather than copying them (`.ww-disc-fold` and
 * friends, below, in every selector). It is the last card on the same page and a
 * card that folds differently from the ones above it reads as a bug — but it is
 * not an area, and naming its markup `.ww-area` would say it was. */
.ww-area,
.ww-disc-fold {
  margin-bottom: 10px;
  padding: 0;
  overflow: hidden;
}

/* The header is a <button> for keyboard and screen-reader reasons — it toggles,
   it carries aria-expanded, and a <div> with a click handler is none of that. So
   the button chrome has to be undone: a button inherits a background, a border
   and centred text, and all three fight .ww-out-head's layout. */
.ww-area-head,
.ww-disc-head {
  width: 100%;
  padding: 14px 16px;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  /* 44px is the tap-target floor on both platforms; the padding above clears it
     for one line of text, and this holds it if a room name ever renders shorter. */
  min-height: 44px;
}

.ww-area-name,
.ww-disc-name {
  font-weight: 600;
  /* A long room name truncates rather than pushing the count and caret off the
     right edge — "Ground floor guest bathroom" is not unusual. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The count is the ONLY thing a collapsed room says about itself, so it must not
   be the thing that gets squeezed out when the name is long. */
.ww-area-count,
.ww-disc-count {
  flex: none;
  margin-left: auto;
  font-size: var(--ww-fs-caption);
}

.ww-area-head .ww-ico,
.ww-disc-head .ww-ico {
  flex: none;
  color: var(--ww-text-muted);
}

/* The room's own glyph, ahead of its name. Brand colour rather than the muted
   treatment its sibling chevron takes: the chevron is chrome that says which way
   the fold goes, and this is the one thing on a collapsed row that identifies
   the room faster than reading it. Listed after the rule above so it wins
   without a specificity fight.
 *
 * Sized to match the device glyph one level down rather than made a feature of
 * it. Everything on this row competes for the same line, and the width this
 * takes comes straight off the end of the room name — "Living room · Ground
 * floor" is already truncating at phone width. */
.ww-area-head .ww-area-ico .ww-ico {
  width: 1.05em;
  height: 1.05em;
  color: var(--ww-primary);
}

.ww-area-ico {
  flex: none;
  display: inline-flex;
  align-items: center;
}

.ww-area-body,
.ww-disc-body {
  padding: 0 16px 12px;
}

.ww-collapsed .ww-area-body,
.ww-collapsed .ww-disc-body {
  display: none;
}

/* A device inside a room: its name and model on one line, then its entities
   indented under it. The left border is what carries the nesting — indentation
   alone gets lost once a room holds several devices with several entities each. */
.ww-device + .ww-device {
  margin-top: 4px;
  border-top: 1px solid var(--ww-border);
  padding-top: 10px;
}

.ww-device-head {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
}

.ww-device-head .ww-ico {
  flex: none;
  width: 1.05em;
  height: 1.05em;
  color: var(--ww-text-muted);
}

.ww-device-name {
  font-weight: 600;
  font-size: var(--ww-fs-label);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Manufacturer and model. Allowed to disappear before the name does: it is the
   detail that identifies a device you are already looking at, not the label you
   scan for. */
.ww-device-model {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--ww-fs-caption);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ww-device-entities {
  padding-left: 10px;
  border-left: 2px solid var(--ww-border);
  margin-left: 6px;
}

.ww-device-empty {
  padding: 2px 0 6px 16px;
  font-size: var(--ww-fs-caption);
}

/* An entity row on this page is a <button> (it opens the sheet), so it needs the
   same chrome-stripping as the area header — and it must fill the width, or the
   tap target is only as wide as the text. */
.ww-entity-row {
  width: 100%;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ww-entity-row:hover .ww-list-label {
  color: var(--ww-text);
}

/* The domain, as the row's second line. Muted and small: it is a hint about what
   kind of thing this is, not a fact anyone came looking for — and it is the one
   place on this page where HA's data model is deliberately visible, because
   "which of these four is the temperature one" is genuinely easier to answer with
   `sensor` on screen. */
.ww-entity-row .ww-list-meta {
  font-size: var(--ww-fs-caption);
}

/* ---------------------------------------------------- home overview -- */

/* "and 8 more — see Devices". A line of text rather than a button, deliberately:
   see HOME_TILE_CAP in ww-app.js. A Home that can expand into the whole house is
   the duplication this split exists to remove, one tap later. */
.ww-home-more {
  margin: 6px 0 18px;
  font-size: var(--ww-fs-caption);
}

/* ------------------------------------------------------------- rooms -- */

/* The room chooser on Home.
 *
 * Its own grid rather than .ww-grid-2, because that one collapses to a single
 * column below 620px (components.css) — right for a device page showing four
 * things, and wrong here: a one-column list of eight rooms is a scroll, and a
 * room button carries a glyph, a name and three words. `auto-fill` with a 150px
 * floor gives two columns on the narrowest phone we test (360px) and more as the
 * viewport grows, with no breakpoint of our own to keep in sync. */
.ww-rooms {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
}

/* A room is not a control, so this is deliberately quieter than a tile: no
   filled-green on-state, no press-scale, no chip. It is a door. The only thing
   it shares with a tile is the one broken corner, because that mark belongs to
   every surface in the app rather than to tiles specifically.

   One column, always, and what is in it centred on both axes. Nothing may take
   width from this block: a card that gives its readings a column centres the name
   against what is left rather than against the card — 34.7% of the way across,
   where a room with no sensor in it centres at 50% — and a grid alternating
   between the two zigzags. Hence the corners, which are out of flow.

   Centred vertically because `.ww-rooms` stretches every card in a row to the
   tallest, so a one-line name beside a two-line one is a card holding 20px less
   than it is drawn. Top-aligned, all of that slack falls under the badges and
   the two glyphs sit at different heights.

   The bottom band is padding rather than emptiness — `--ww-room-band` is the air
   line's own row, held off the badges, which are otherwise the last thing in the
   card and end one inset above its edge. Every card reserves it, including the
   fourteen of seventeen in the house this was measured against that have no air
   sensor at all: a card that grew only when it had something to say would make
   the rows uneven again, which is the thing this rule exists to stop. */
.ww-room-btn {
  --ww-room-inset: 14px;
  /* A 10px reading line measures 12px; the other 6px keeps it off the badges. */
  --ww-room-band: 18px;
  position: relative;
  display: grid;
  align-items: center;
  /* 104 was the floor before the band existed. The band is chrome, so the floor
     rises with it and a bare card keeps the proportions it had. */
  min-height: 122px;
  padding: var(--ww-room-inset) var(--ww-room-inset)
    calc(var(--ww-room-inset) + var(--ww-room-band));
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius) var(--ww-radius) var(--ww-radius) 6px;
  background: var(--ww-surface);
  color: var(--ww-text);
  font: inherit;
  cursor: pointer;
  transition:
    background 0.15s ease,
    border-color 0.15s ease,
    transform 0.12s var(--ww-spring);
}

.ww-room-btn:active {
  transform: scale(0.97);
}

@media (prefers-reduced-motion: reduce) {
  .ww-room-btn:active {
    transform: none;
  }
}

.ww-room-btn:focus-visible {
  outline: 2px solid var(--ww-ink);
  outline-offset: 2px;
}

/* What you can DO with the room, centred in the whole card: the glyph over the
   name over the badges, each one middled against the others, so the stack reads
   as one object rather than three left-aligned things of different lengths.

   `min-width: 0` is what lets a long room name ellipsize instead of pushing the
   card wider — a grid item's default `min-width: auto` refuses to shrink below
   its content, and a column is a share of the space, not a cap on it. */
.ww-room-main {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 4px;
  min-width: 0;
}

/* The glyph, in the same 40px disc the tile chip uses, so a room and a device
   read as members of one family at a glance. Static: nothing here toggles. */
.ww-room-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: none;
  margin-bottom: 4px;
  border-radius: var(--ww-radius-pill);
  border: 1px solid var(--ww-border);
  background: var(--ww-surface-muted);
  color: var(--ww-primary);
}

.ww-room-ico .ww-ico {
  width: 20px;
  height: 20px;
}

.ww-room-name {
  font-weight: 600;
  line-height: 1.25;
  /* Two lines, then ellipsis — "Ground floor guest bathroom" is a real room name
     and must not push the count off the button. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Redundant with the parent's, and load-bearing anyway: a -webkit-box does not
     inherit the flex item's shrink, so without this the clamp has nothing to
     clamp against and the name sets its own width. */
  min-width: 0;
}

/* Only ever the word "Empty" now — a room with something on prints badges
   instead. Muted, because it is an observation about the room rather than
   anything to act on. */
.ww-room-count {
  font-size: var(--ww-fs-caption);
  color: var(--ww-text-muted);
}

/* What is happening in the room, one glyph per kind of thing.
 *
 * `nowrap` is load-bearing: a third badge arriving as a wrapped second line
 * would jog the whole grid. If the cap is ever wrong the row clips rather than
 * reflows, which is the failure that stays still. */
.ww-room-badges {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
  /* Measured on a real house at 360px: the busiest card's badges come to 71.9px
     against the 131px the row now gets, the readings having moved out of its
     way. Comfortable, and it was not always — a nowrap row overflows its parent
     rather than being clipped by it, so two-digit counts once pushed the row
     THROUGH the readings beside it. This is what kept that inside the card. */
  overflow: hidden;
}

/* What the room is LIKE, as opposed to what you can do in it: temperature,
 * humidity and air, one to a corner.
 *
 * The frame is the card's visual margin rather than its padding box, which is
 * why it is `inset: --ww-room-inset` and not `inset: 0`. The button's bottom
 * padding carries the reserved band as well, so `inset: 0` would hold the air
 * line 18px off the floor and leave it hanging in the middle of nothing.
 *
 * Out of flow on purpose. In flow, these were a column that took width from the
 * name and made a card with sensors a different shape from a card without; the
 * corners cost the layout nothing, so every card is the same card whether it has
 * three readings or none.
 *
 * Muted, unglyphed and uncoloured, because a reading is not a control and the
 * one thing this screen's colour means is "you can change this". `tabular-nums`
 * so 22 and 9 occupy the same width and the corners do not shimmer as the house
 * warms up. */
.ww-room-readings {
  position: absolute;
  inset: var(--ww-room-inset);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--ww-text-muted);
  white-space: nowrap;
}

/* Fixed corners, not filled in order. Temperature is top-left on all seventeen
   cards or it is nowhere, and a room with only an air reading leaves the other
   three corners empty rather than promoting it into a spot that means
   temperature everywhere else. An empty corner says "no sensor"; a corner that
   means something different per card says nothing at all.

   Bottom-left is the only one left out, because the broken corner lives there
   and it is the one piece of this card that is not a right angle. */
.ww-room-reading {
  position: absolute;
}

.ww-room-reading[data-reading="temperature"] {
  top: 0;
  left: 0;
}

.ww-room-reading[data-reading="humidity"] {
  top: 0;
  right: 0;
}

.ww-room-reading[data-reading="aqi"] {
  right: 0;
  bottom: 0;
}

/* The backstop for a unit nobody has published yet. The top two corners share
   their line with the 40px glyph, so each may have half the card less the glyph
   and the gap either side of it — 41.5px on a 360px phone, against a widest
   measured value of 27.2px. Nothing real clips here; this is what stops a freak
   unit from printing across the glyph instead of losing its own tail. */
.ww-room-reading[data-reading="temperature"],
.ww-room-reading[data-reading="humidity"] {
  max-width: calc(50% - 24px);
  overflow: hidden;
}

/* Brand-coloured rather than the button taking a fill: eight filled-green rooms
   would read as eight things switched on, which is the opposite of what the
   badges say. */
.ww-room-badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: var(--ww-fs-caption);
  font-weight: 600;
  color: var(--ww-primary);
}

.ww-room-badge .ww-ico {
  width: 14px;
  height: 14px;
}

/* The badges that did not fit. Muted and unglyphed, so it reads as a count of
   things withheld rather than as a fourth kind of thing. */
.ww-room-more {
  font-size: var(--ww-fs-caption);
  font-weight: 600;
  color: var(--ww-text-muted);
}

/* Which floor the rooms below are on. A label, not a section title: it carries no
   icon and no rule, so it sits inside the Rooms section rather than competing
   with the heading above it.
 *
 * Margins only, never a negative one. `ww-rooms` is a block, so its children have
 * nothing but their own margins between them — and a -6px pull against a 12px
 * caption is half a line, which the opaque room buttons below then paint over.
 * The heading rendered with its bottom third missing. */
.ww-floor-title {
  margin: 0 0 8px;
  font-size: var(--ww-fs-caption);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ww-text-muted);
}

/* Between one floor's rooms and the next floor's heading. */
.ww-rooms + .ww-floor-title {
  margin-top: 18px;
}

/* ---------------------------------------------------------- one room -- */

/* The room's own header: back, glyph, name, and what is on in it. Inside the
   view rather than in .ww-topbar — that bar belongs to chrome.css, carries the
   product and one control on every screen, and is not where a control that
   belongs to ONE screen goes. */
.ww-room-head {
  display: flex;
  align-items: center;
  gap: 10px;
}

.ww-room-back {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px is the published tap-target floor on both platforms, and this is the
     one control on the page that a customer reaches for without looking. */
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--ww-border);
  border-radius: var(--ww-radius-pill);
  background: var(--ww-surface);
  color: var(--ww-text);
  cursor: pointer;
}

.ww-room-back:focus-visible {
  outline: 2px solid var(--ww-ink);
  outline-offset: 2px;
}

.ww-room-title-ico {
  flex: none;
  display: inline-flex;
  color: var(--ww-primary);
}

.ww-room-title {
  min-width: 0;
}

.ww-room-title h2 {
  margin: 0;
  font-size: var(--ww-fs-h3);
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ww-room-title .ww-text-muted {
  font-size: var(--ww-fs-caption);
}

/* The collapse header and the open-room button, side by side on the Devices
   page. The header keeps `width: 100%` from its own rule, so it needs a flex
   basis that can shrink or a long room name pushes the door glyph off the card. */
.ww-area-bar {
  display: flex;
  align-items: stretch;
}

.ww-area-bar .ww-area-head {
  flex: 1;
  min-width: 0;
}

.ww-area-open {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  padding: 0;
  border: 0;
  border-left: 1px solid var(--ww-border);
  background: transparent;
  color: var(--ww-text-muted);
  cursor: pointer;
}

.ww-area-open:hover,
.ww-area-open:focus-visible {
  color: var(--ww-primary);
  background: var(--ww-surface-muted);
}
