/* Enewe / The Curtailer — application stylesheet.
   Built on the design system's tokens (tokens.css) - this file only adds
   component classes, no new colors/spacing/type values of its own. */

/* ---------- Layout ---------- */

.container {
  max-width: var(--content-max-width);
  margin: 0 auto;
  padding: 0 var(--space-6);
}

.container-narrow {
  max-width: var(--content-max-width-narrow);
  margin: 0 auto;
  padding: 0 var(--space-6);
}

.page-main {
  padding: var(--space-7) 0 var(--space-9);
}

@media (max-width: 700px) {
  .container,
  .container-narrow {
    padding: 0 var(--space-4);
  }

  .page-main {
    padding: var(--space-6) 0 var(--space-7);
  }
}

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

.mt-2 {
  margin-top: var(--space-2);
}

.mt-3 {
  margin-top: var(--space-3);
}

.mt-4 {
  margin-top: var(--space-4);
}

.mt-5 {
  margin-top: var(--space-5);
}

.mt-6 {
  margin-top: var(--space-6);
}

.ml-auto {
  margin-left: auto;
}

.text-center {
  text-align: center;
}

.table-scroll {
  overflow-x: auto;
}

.form-narrow {
  padding: 0;
  max-width: 480px;
}

.mb-2 {
  margin-bottom: var(--space-2);
}

.mb-3 {
  margin-bottom: var(--space-3);
}

.mb-4 {
  margin-bottom: var(--space-4);
}

.mb-5 {
  margin-bottom: var(--space-5);
}

.mb-6 {
  margin-bottom: var(--space-6);
}

.flex-between {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.page-title-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

.page-title-row .page-title {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.is-hidden {
  display: none;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.btn-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--brand-primary);
  cursor: pointer;
  font: inherit;
  font-weight: 600;
}

.btn-link:hover {
  color: var(--brand-primary-hover);
  text-decoration: underline;
}

.step {
  display: flex;
  flex-direction: column;
}

.step-title {
  font: var(--text-heading-sm);
  color: var(--text-primary);
  margin-top: var(--space-2);
}

.step-body {
  margin-top: var(--space-2);
}

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

.eyebrow {
  font: var(--text-label);
  letter-spacing: var(--letter-spacing-wide);
  text-transform: uppercase;
  color: var(--text-tertiary);
}

.page-title {
  font: var(--text-heading-lg);
  color: var(--text-primary);
  margin: 0 0 var(--space-2);
}

.page-lead {
  font: var(--text-body-md);
  color: var(--text-secondary);
  max-width: 640px;
  margin: 0 0 var(--space-6);
}

.section-title {
  font: var(--text-heading-md);
  color: var(--text-primary);
  margin: var(--space-7) 0 var(--space-3);
}

/* Modifiers of .section-title, so they must come after it: the base rule
   sets `margin` as a shorthand (touching margin-top too), and with equal
   specificity the later rule wins on every property it sets - a modifier
   declared before the base it modifies gets its margin-top silently
   overwritten right back. (Regression: both sat above .section-title
   until this fix, so neither one ever actually took effect - see the
   "History button" misalignment bug this caused wherever a
   .page-title-row paired a flush title with a same-row button.) */
.section-title-flush {
  margin-top: 0;
}

.section-title-tight {
  margin-top: var(--space-5);
}

.text-muted {
  color: var(--text-tertiary);
}

.text-small {
  font: var(--text-body-sm);
}

/* The launch-offer strike-through treatment: a struck, muted list price
   shown before the live launch price - e.g. "<del>€795</del> €295".
   Deliberately no animation/flash per the launch pricing copy brief's
   design notes - a countdown lives in .launch-offer-banner, not here. */
.text-strike {
  text-decoration: line-through;
  color: var(--text-tertiary);
  font-size: 0.6em;
  font-weight: 400;
}

/* ---------- Nav ---------- */

/* Secondary nav — a thin dark rail above the primary nav, holding the
   company/utility items (about, contact, phone, partnerships) so the
   primary nav can stay purely about the product. */
.site-topbar {
  background: var(--neutral-950);
  color: var(--neutral-400);
  font: var(--text-caption);
}

.site-topbar__inner {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  align-items: center;
  /* One child since the tagline gave up its slot to the country
     switcher - the utility rail sits right, where a country/language
     control is conventionally looked for. */
  justify-content: flex-end;
  gap: var(--space-5);
  padding: var(--space-2) var(--space-6);
  min-height: 38px;
}

.site-topbar__links {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.site-topbar a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--neutral-300);
  transition: color var(--duration-fast) var(--ease-standard);
}

.site-topbar a:hover {
  color: var(--neutral-0);
  text-decoration: none;
}

.site-topbar__phone svg {
  color: var(--neutral-500);
}

/* The country switcher's wide-screen trigger. Labelled with the country
   you're on rather than a generic "Country", so it doubles as the "you
   are here" cue - on a multi-domain site the address bar is otherwise
   the only one. Styled as a quiet chip on the dark rail, a shade
   brighter than the plain links beside it so it reads as a control. */
.site-topbar__country {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 3px 10px;
  border: 1px solid oklch(100% 0 0 / 0.16);
  border-radius: var(--radius-pill);
  background: none;
  color: var(--neutral-200);
  font: inherit;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard);
}

.site-topbar__country:hover {
  background: oklch(100% 0 0 / 0.07);
  border-color: oklch(100% 0 0 / 0.4);
}

/* The narrow-screen counterpart, beside the burger: one tap to the same
   dialog without opening the menu first. Flag only - the accessible name
   carries the country, and there is no room for it beside a 44px burger
   and the brand. */
.site-nav__country {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  margin-left: auto;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  cursor: pointer;
}

/* "Become a partner" is deliberately quiet — a hairline pill and a single
   accent dot, enough to read as a separate offer without competing with
   the primary CTAs one row below. */
.site-topbar__partner {
  padding: 3px 10px;
  border: 1px solid oklch(100% 0 0 / 0.16);
  border-radius: var(--radius-pill);
  transition: background var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard);
}

.site-topbar__partner:hover {
  background: oklch(100% 0 0 / 0.07);
  border-color: oklch(100% 0 0 / 0.4);
}

.site-topbar__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--brand-accent);
  flex-shrink: 0;
}

/* The language switcher's wide-screen trigger - the same quiet chip
   treatment as .site-topbar__country beside it, labelled with the active
   language's short code rather than a flag (a language isn't one
   country's). Opens site-language-modal, listing every language by its
   own native name - see partials/_language_switcher.html. */
.site-topbar__language {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border: 1px solid oklch(100% 0 0 / 0.16);
  border-radius: var(--radius-pill);
  background: none;
  color: var(--neutral-200);
  font: inherit;
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard);
}

.site-topbar__language:hover {
  background: oklch(100% 0 0 / 0.07);
  border-color: oklch(100% 0 0 / 0.4);
}

/* The mobile menu's row - a plain link-styled row alongside About/Contact
   in site-nav__mobile-secondary, rather than the topbar's chip. */
.site-nav__mobile-language {
  display: block;
  width: 100%;
  padding: var(--space-2) 0;
  border: none;
  background: none;
  color: var(--text-secondary);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.site-nav__mobile-language:hover {
  color: var(--text-primary);
}

/* ---------- Language list (site-language-modal - see
   partials/_language_switcher.html) ---------- */

.language-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.language-list__item {
  display: block;
  width: 100%;
  padding: var(--space-3);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: none;
  color: var(--text-primary);
  font: var(--text-body-md);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-standard),
    background var(--duration-fast) var(--ease-standard);
}

.language-list__item:hover {
  border-color: var(--brand-primary);
}

.language-list__item.is-current {
  border-color: var(--brand-primary);
  background: var(--primary-100);
}

.site-nav {
  border-bottom: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  position: sticky;
  top: 0;
  z-index: 20;
  transition: box-shadow var(--duration-base) var(--ease-standard);
}

/* Toggled from site.js once the page scrolls, so the nav lifts off the
   content instead of sitting on a flat hairline the whole way down. */
.site-nav.is-scrolled {
  box-shadow: var(--shadow-md);
}

.site-nav__inner {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-3) var(--space-6);
}

.site-nav__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-primary);
  flex-shrink: 0;
}

.site-nav__brand-icon {
  height: 30px;
  width: auto;
  display: block;
}

.site-nav__brand-text {
  font: 400 19px/1.2 var(--font-body);
  letter-spacing: -0.01em;
}

.site-nav__links {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-inline: auto;
  font: var(--text-body-sm);
  color: var(--text-secondary);
}

.site-nav__links a {
  position: relative;
  padding: 6px 0;
  color: var(--text-secondary);
  transition: color var(--duration-fast) var(--ease-standard);
}

.site-nav__links a:hover {
  color: var(--text-primary);
  text-decoration: none;
}

/* Underline grows from the left on hover and stays put on the active
   page, so the two states share one visual language. */
.site-nav__links a::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 2px;
  border-radius: var(--radius-pill);
  background: var(--brand-primary);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--duration-base) var(--ease-standard);
}

.site-nav__links a:hover::after,
.site-nav__links a.is-active::after {
  transform: scaleX(1);
}

.site-nav__links a.is-active {
  color: var(--brand-primary);
  font-weight: 600;
}

.site-nav__actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.site-nav__burger {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  cursor: pointer;
}

.site-nav__mobile {
  display: none;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4) var(--space-6) var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  font: var(--text-body-md);
  color: var(--text-primary);
}

.site-nav__mobile.is-open {
  display: flex;
  /* The menu lives inside the sticky nav (see partials/_nav.html), so an
     open menu taller than the viewport would otherwise push its lower
     links below the fold with no way to reach them - the nav itself
     never scrolls. Capped below the nav bar's own height, scrolling
     within. */
  max-height: calc(100vh - 72px);
  overflow-y: auto;
}

.site-nav__mobile a {
  padding: var(--space-2) 0;
  color: var(--text-primary);
}

/* The secondary-nav items live down here on mobile, set apart from the
   primary links above them rather than run together in one long list. */
.site-nav__mobile-secondary {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
  font: var(--text-body-sm);
}

.site-nav__mobile-secondary a {
  color: var(--text-secondary);
}

.site-nav__mobile-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

@media (max-width: 900px) {
  .site-nav__links,
  .site-nav__actions,
  /* The chip beside the burger takes over at this width. */
  .site-topbar__country {
    display: none;
  }

  .site-nav__country {
    display: flex;
  }

  .site-nav__burger {
    display: flex;
    /* The country chip beside it now owns the auto margin that pushes
       this cluster right; .site-nav__inner's own gap spaces the two. */
  }

  /* About/Contact/language are in the burger menu at this width — the
     rail keeps only the two things worth one tap: the phone and the
     partner link. */
  .site-topbar__links a:not(.site-topbar__phone):not(.site-topbar__partner),
  .site-topbar__language {
    display: none;
  }

  .site-topbar__inner {
    justify-content: flex-end;
    padding: var(--space-2) var(--space-4);
  }

  .site-nav__inner {
    padding: var(--space-3) var(--space-4);
  }
}

@media (max-width: 420px) {
  .site-topbar__links {
    gap: var(--space-3);
  }

  .site-topbar__partner {
    padding: 3px 8px;
  }
}

/* ---------- App shell (sidebar + top bar) ---------- */

/* The logged-in product area (app_base.html) - a persistent sidebar
   instead of the public site's horizontal nav, since the two serve
   different jobs: the public nav is a handful of marketing destinations
   a visitor scans once, the app is a small set of product sections
   revisited constantly, which reads better as a fixed rail than a bar
   that scrolls out of view. The sidebar carries product sections only;
   the visitor's own actions (profile, language, sign out) sit in the
   top bar's account menu - see .app-user-menu below. */
.app-shell {
  display: flex;
  min-height: 100vh;
  background: var(--bg-surface-sunken);
}

.app-sidebar {
  display: flex;
  flex-direction: column;
  width: 256px;
  flex-shrink: 0;
  padding: var(--space-5) var(--space-4);
  background: var(--bg-surface);
  border-right: 1px solid var(--border-subtle);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.app-sidebar__brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-3) var(--space-5);
  color: var(--text-primary);
}

.app-sidebar__brand:hover {
  text-decoration: none;
}

.app-sidebar__brand-icon {
  height: 56px;
  width: auto;
  display: block;
}

.app-sidebar__brand-text {
  font: 600 15px/1.2 var(--font-body);
  letter-spacing: var(--letter-spacing-tight);
}

.app-sidebar__nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.app-sidebar__link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  font: var(--text-body-sm);
  font-weight: 600;
  transition: background var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.app-sidebar__link svg,
.app-sidebar__link img {
  flex-shrink: 0;
}

.app-sidebar__link svg {
  color: var(--text-tertiary);
  transition: color var(--duration-fast) var(--ease-standard);
}

.app-sidebar__link:hover {
  background: var(--bg-surface-sunken);
  color: var(--text-primary);
  text-decoration: none;
}

.app-sidebar__link.is-active {
  background: var(--primary-50);
  color: var(--brand-primary);
}

.app-sidebar__link.is-active svg {
  color: var(--brand-primary);
}

/* A native <details>/<summary> disclosure for the once-and-rarely
   config pages (production sites, WiFi networks, subscriptions, billing
   info) - same "prefer a native element over hand-rolled JS" call as
   the <dialog class="modal"> pattern in site.js. No border/label of its
   own: the toggle row matches .app-sidebar__link exactly (same font
   size/weight/padding/hover as Home and Devices above it), so
   Configuration reads as a peer of those, not a separate section - only
   the browser's own disclosure triangle is suppressed in favor of the
   chevron below, which the [open] state rotates. Server-rendered `open`
   (see _nav_app.html) keeps a Configuration sub-page's own is-active
   link visible on load, without needing JS to restore expanded state. */
.app-sidebar__section > summary {
  list-style: none;
  cursor: pointer;
}

.app-sidebar__section > summary::-webkit-details-marker {
  display: none;
}

.app-sidebar__chevron {
  margin-left: auto;
  color: var(--text-tertiary);
  transition: transform var(--duration-fast) var(--ease-standard);
}

.app-sidebar__section[open] > summary .app-sidebar__chevron {
  transform: rotate(90deg);
}

.app-sidebar__section-links {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 2px;
}

/* Slightly smaller than the top-level links (--text-caption vs.
   --text-body-sm) and indented past their icon column, so they read as
   nested under Configuration rather than as peers of it. */
.app-sidebar__link--sub {
  padding-left: calc(var(--space-3) + 20px + var(--space-3));
  font: var(--text-caption);
}

.app-sidebar__link--sub svg {
  width: 16px;
  height: 16px;
}

/* Pushed to the bottom of the sidebar's flex column - the only thing
   left below the nav now that the visitor-level links moved to the top
   bar's account menu. */
.app-sidebar__powered-by {
  margin-top: auto;
  padding: var(--space-2) var(--space-3);
  text-align: center;
  font: var(--text-caption);
  color: var(--text-tertiary);
}

.app-sidebar__powered-by:hover {
  color: var(--text-secondary);
}

.app-main {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}

/* Rendered at every width (it used to exist only as the mobile burger
   bar) - on a wide screen it is a slim strip whose only content is the
   account menu, right-aligned; under 900px the burger and brand join it
   on the left. Sticky so the account menu stays reachable however far
   a long device page has scrolled. */
.app-topbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 56px;
  padding: var(--space-2) var(--space-5);
  background: var(--bg-surface);
  border-bottom: 1px solid var(--border-subtle);
  position: sticky;
  top: 0;
  z-index: 20;
}

.app-topbar__burger,
.app-topbar__brand {
  display: none;
}

.app-topbar__spacer {
  flex: 1;
}

/* ---------- Account menu (top bar dropdown) ---------- */

.app-user-menu {
  position: relative;
}

.app-user-menu__trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2) var(--space-1) var(--space-1);
  border-radius: var(--radius-pill);
  list-style: none;
  cursor: pointer;
  color: var(--text-primary);
  font: var(--text-body-sm);
  font-weight: 600;
  transition: background var(--duration-fast) var(--ease-standard);
}

.app-user-menu__trigger::-webkit-details-marker {
  display: none;
}

.app-user-menu__trigger:hover,
.app-user-menu[open] > .app-user-menu__trigger {
  background: var(--bg-surface-sunken);
}

.app-user-menu__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--primary-100);
  color: var(--primary-700);
  font: 600 14px/1 var(--font-body);
  flex-shrink: 0;
}

.app-user-menu__name {
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.app-user-menu__chevron {
  color: var(--text-tertiary);
  transition: transform var(--duration-fast) var(--ease-standard);
}

.app-user-menu[open] .app-user-menu__chevron {
  transform: rotate(180deg);
}

.app-user-menu__panel {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  z-index: 30;
  min-width: 240px;
  padding: var(--space-2);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}

.app-user-menu__identity {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2) var(--space-3) var(--space-3);
}

.app-user-menu__identity-name {
  font: var(--text-body-sm);
  font-weight: 600;
  color: var(--text-primary);
}

.app-user-menu__identity-email {
  font: var(--text-caption);
  color: var(--text-tertiary);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Same row shape as .app-sidebar__link - these are the rows that used
   to be sidebar rows. The language row is a <button>, so the reset a
   bare <button> needs (no border/background, full width, left text) is
   folded in here rather than in a separate button-only rule. */
.app-user-menu__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: var(--radius-md);
  background: none;
  text-align: left;
  color: var(--text-secondary);
  font: var(--text-body-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.app-user-menu__item svg {
  flex-shrink: 0;
  color: var(--text-tertiary);
}

.app-user-menu__item:hover {
  background: var(--bg-surface-sunken);
  color: var(--text-primary);
  text-decoration: none;
}

.app-user-menu__item-value {
  margin-left: auto;
  font: var(--text-caption);
  font-weight: 400;
  color: var(--text-tertiary);
}

.app-user-menu__divider {
  height: 1px;
  margin: var(--space-2) var(--space-2);
  background: var(--border-subtle);
}

.app-content {
  width: 100%;
  max-width: var(--content-max-width);
  margin: 0 auto;
  padding: var(--space-6);
}

.app-shell__backdrop {
  display: none;
}

@media (max-width: 900px) {
  .app-sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 40;
    width: 288px;
    transform: translateX(-100%);
    box-shadow: var(--shadow-lg);
    transition: transform var(--duration-base) var(--ease-standard);
  }

  .app-sidebar.is-open {
    transform: translateX(0);
  }

  .app-topbar {
    padding: var(--space-2) var(--space-4);
  }

  .app-topbar__burger {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: 1px solid var(--border-default);
    border-radius: var(--radius-md);
    background: var(--bg-surface);
    cursor: pointer;
  }

  .app-topbar__brand {
    display: block;
    font: 600 16px/1.2 var(--font-body);
    color: var(--text-primary);
  }

  /* Avatar only - the name is one tap away in the panel's identity
     row, and the brand + burger already use the bar's width. */
  .app-user-menu__name,
  .app-user-menu__chevron {
    display: none;
  }

  .app-user-menu__trigger {
    padding: var(--space-1);
  }

  .app-content {
    padding: var(--space-4);
  }

  .app-shell__backdrop.is-open {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 30;
    background: oklch(0% 0 0 / 0.4);
  }
}

.nav-minimal {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--border-subtle);
}

.nav-minimal__back {
  font: var(--text-body-sm);
  color: var(--text-tertiary);
}

/* ---------- Footer ---------- */

.site-footer {
  padding: var(--space-8) var(--space-6) var(--space-5);
  border-top: 1px solid var(--border-subtle);
}

.site-footer__grid {
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: var(--space-7);
}

.site-footer__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-primary);
}

.site-footer__brand-icon {
  height: 72px;
  width: auto;
  display: block;
}

.site-footer__brand-text {
  font: 400 18px/1.2 var(--font-body);
  letter-spacing: -0.01em;
}

.site-footer__blurb {
  font: var(--text-body-sm);
  color: var(--text-secondary);
  margin-top: var(--space-3);
  max-width: 320px;
}

.site-footer__contact {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
  font: var(--text-body-sm);
}

.site-footer__contact a:first-child {
  color: var(--text-primary);
  font-weight: 600;
}

/* Separator between the phone number and the contact link. */
.site-footer__contact a + a::before {
  content: "·";
  color: var(--text-tertiary);
  margin-right: var(--space-3);
}

.site-footer h3 {
  font: var(--text-label);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-wide);
  margin: 0 0 var(--space-3);
}

.site-footer__links {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font: var(--text-body-sm);
  color: var(--text-secondary);
}

/* The country-domain switcher. Sits between the link columns and the
   copyright line, laid out as a wrapping row so adding a market never
   needs a layout change. */
.site-footer__countries {
  max-width: var(--content-max-width);
  margin: var(--space-6) auto 0;
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  font: var(--text-body-sm);
}

.site-footer__countries-label {
  font: var(--text-label);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-wide);
}

.site-footer__country {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-secondary);
}

.site-footer__country.is-current {
  color: var(--text-primary);
  font-weight: 600;
}

.site-footer__country-flag {
  display: inline-flex;
  border-radius: 2px;
  overflow: hidden;
  box-shadow: 0 0 0 1px var(--border-subtle);
}

/* The switcher above already draws the rule when it's present. */
.site-footer__countries + .site-footer__bottom {
  margin-top: var(--space-4);
  padding-top: 0;
  border-top: none;
}

.site-footer__bottom {
  max-width: var(--content-max-width);
  margin: var(--space-6) auto 0;
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  font: var(--text-caption);
  color: var(--text-tertiary);
}

.newsletter-form {
  display: flex;
  gap: var(--space-2);
}

.newsletter-form input[type="email"],
.newsletter-form input[type="text"] {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  padding: 6px 10px;
  font: var(--text-body-sm);
  color: var(--text-primary);
  font-family: var(--font-body);
  min-width: 0;
  flex: 1;
}

.newsletter-form input[type="email"]:focus,
.newsletter-form input[type="text"]:focus {
  outline: none;
  border-color: var(--brand-primary);
  box-shadow: 0 0 0 3px var(--primary-100);
}

/* Same quiet treatment as the topbar pill — the partner ask appears once
   at the top and once at the very bottom, never shouting in between. */
.site-footer__partner-cta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-secondary);
}

.site-footer__partner-cta:hover {
  color: var(--text-primary);
}

.site-footer__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--brand-accent);
  flex-shrink: 0;
}

@media (max-width: 700px) {
  .site-footer__grid {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }

  /* Side-by-side email + button doesn't fit a phone width without
     overflowing the viewport - stack the button under the field instead.
     `.newsletter-form` is itself a flex item of `.dashed-cta`, so without
     an explicit width it keeps its unshrunk side-by-side min-content
     width even after `.dashed-cta` wraps it onto its own row. */
  .newsletter-form {
    flex-direction: column;
    width: 100%;
  }
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 10px 18px;
  font: var(--text-body-md);
  font-weight: 600;
  font-family: var(--font-body);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard);
  text-decoration: none;
  background: var(--brand-primary);
  color: var(--text-on-primary);
}

.btn:hover {
  background: var(--brand-primary-hover);
  color: var(--text-on-primary);
  text-decoration: none;
}

.btn-secondary {
  background: var(--bg-surface);
  color: var(--text-primary);
  border-color: var(--border-default);
}

.btn-secondary:hover {
  background: var(--neutral-50);
  color: var(--text-primary);
}

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

.btn-ghost:hover {
  background: var(--neutral-50);
  color: var(--brand-primary);
}

.btn-accent {
  background: var(--brand-accent);
  color: var(--text-on-accent);
}

.btn-accent:hover {
  background: var(--accent-700);
  color: var(--text-on-accent);
}

.btn-danger {
  background: var(--danger-500);
  color: var(--neutral-0);
}

.btn-danger:hover {
  background: var(--danger-700);
  color: var(--neutral-0);
}

.btn:disabled {
  background: var(--neutral-100);
  color: var(--neutral-400);
  border-color: var(--neutral-200);
  cursor: not-allowed;
}

.btn-sm {
  padding: 6px 14px;
  font: var(--text-body-sm);
  font-weight: 600;
}

.btn-lg {
  padding: 13px 24px;
  font: var(--text-body-lg);
  font-weight: 600;
}

.btn-group {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.date-input {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  padding: 6px 10px;
  font: var(--text-body-sm);
  font-weight: 600;
  font-family: var(--font-body);
  color: var(--text-primary);
  cursor: pointer;
}

.date-input:focus {
  outline: none;
  border-color: var(--brand-primary);
  box-shadow: 0 0 0 3px var(--primary-100);
}

/* ---------- Chips (lightweight presets, e.g. example-site picks) ---------- */

.chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.chip {
  display: inline-flex;
  align-items: center;
  padding: 5px 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface-sunken);
  color: var(--text-secondary);
  font: var(--text-body-sm);
  font-weight: 500;
  text-decoration: none;
  transition: border-color var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.chip:hover {
  border-color: var(--brand-primary);
  color: var(--brand-primary);
  text-decoration: none;
}

/* ---------- Labeled groups (e.g. "Examples:" / "Learn more:") ---------- */

.label-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.label-row__label {
  font: var(--text-label);
  color: var(--text-tertiary);
  flex: 0 0 auto;
}

.stacked-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.stacked-actions .btn {
  width: 100%;
}

/* ---------- Segmented toggle (e.g. chart unit switch) ---------- */

.segmented-toggle {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--bg-surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
}

.segmented-toggle__option {
  padding: 6px 16px;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-secondary);
  font: var(--text-body-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.segmented-toggle__option:hover:not(.is-active) {
  color: var(--text-primary);
}

.segmented-toggle__option.is-active {
  background: var(--brand-primary);
  color: var(--text-on-primary);
}

/* ---------- Positive-highlight inline text ---------- */

.text-positive {
  color: var(--success-500);
}

.btn.is-active {
  background: var(--brand-primary);
  border-color: var(--brand-primary);
  color: var(--text-on-primary);
}

/* ---------- Cards ---------- */

.card {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-5);
}

.card-flush {
  padding: 0;
  overflow: hidden;
}

/* A card that holds a chart (or its "no data yet" placeholder) - a
   min-height so an empty state doesn't collapse to one line of muted
   text and read as broken, roughly matching a Chart.js aspectRatio:5
   canvas at typical content width. `.chart-card--empty` centers the
   placeholder message the same way a populated chart fills the card,
   so the two states don't visibly jump in height/shape when data
   arrives. */
.chart-card {
  min-height: 220px;
}

.chart-card--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.chart-card--empty p {
  margin: 0;
}

/* Below Pro Plus, the inverter power chart has a shape but no real
   data behind it (see device_detail.html's own comment on why
   .chart-card__preview is decorative, never a real reading) - the
   muted line sits full-bleed behind a centered upgrade prompt, same
   min-height/layout as the populated and empty states above so this
   doesn't visibly jump in height when a device is upgraded. */
.chart-card--locked {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.chart-card__preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.chart-card__preview path {
  fill: none;
  stroke: var(--border-default);
  stroke-width: 2;
}

.chart-card__lock {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  text-align: center;
  max-width: 260px;
  padding: var(--space-4);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  color: var(--text-secondary);
}

.chart-card__lock .icon-lock {
  color: var(--text-tertiary);
}

.chart-card__lock p {
  margin: 0;
}

.panel {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--space-5);
  margin-bottom: var(--space-5);
}

.panel-title {
  font: var(--text-heading-sm);
  color: var(--text-primary);
  margin: 0 0 var(--space-3);
}

/* ---------- Modal (native <dialog>) ---------- */

dialog.modal {
  border: none;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6);
  background: var(--bg-surface);
  color: var(--text-primary);
  /* Shrink-wraps to its content (a one-field pencil-edit modal shouldn't
     render as a mostly-empty 680px box) rather than always stretching to
     max-width - see .modal-wide below for the handful that genuinely
     need more room. */
  width: fit-content;
  min-width: 340px;
  max-width: min(420px, calc(100% - var(--space-8)));
  max-height: calc(100% - var(--space-8));
  overflow-y: auto;
}

/* The daily-stats (chart) and detailed-results (table) modals need
   real width to be usable - everything else is a short form or a
   couple of paragraphs, which reads better snug than stretched. */
dialog.modal.modal-wide {
  max-width: min(680px, calc(100% - var(--space-8)));
}

dialog.modal::backdrop {
  background: oklch(0% 0 0 / 0.5);
}

/* Title and close button as one flex row, so the close button is
   always vertically centered against the title regardless of title
   length/wrapping - an absolutely-positioned corner button (the
   previous approach) drifts out of alignment the moment the title
   isn't exactly one line. The divider under it is what the reference
   designs use to separate header from body. */
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
}

.modal-header .section-title-flush {
  margin-bottom: 0;
}

.modal-close-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  flex: 0 0 auto;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-tertiary);
  border-radius: var(--radius-sm);
  /* Optically aligns the X glyph with the title's right edge - the
     button's own hit-box is bigger than the glyph it draws. */
  margin: 0 calc(-1 * var(--space-2)) 0 0;
}

.modal-close-btn:hover {
  color: var(--text-primary);
  background: var(--neutral-100);
}

@media (max-width: 700px) {
  /* The daily-stats modal holds a two-axis chart, which needs
     every spare pixel of width it can get on a narrow screen -
     the default modal gutter/padding (sized for form modals)
     leaves it too cramped to read. */
  #daily-stats-modal {
    width: calc(100% - var(--space-4));
    padding: var(--space-3);
  }
}

/* ---------- Country list (flags, no search - see the checkout's
   edit-country-modal) ---------- */

.country-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.country-list__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-standard),
    background var(--duration-fast) var(--ease-standard);
}

.country-list__item:hover {
  border-color: var(--brand-primary);
}

.country-list__item:has(input:checked),
.country-list__item.is-current {
  border-color: var(--brand-primary);
  background: var(--primary-100);
}

/* The site-wide switcher's rows are links, not radio labels (switching
   country switches host - see partials/_country_switcher.html), so they
   need the anchor treatment the label version never did. */
a.country-list__item {
  color: var(--text-primary);
}

a.country-list__item:hover {
  text-decoration: none;
}

/* The hostname, right-aligned: switching country changes the address
   bar, so the row says which address it's about to become. */
.country-list__host {
  margin-left: auto;
  font: var(--text-caption);
  color: var(--text-tertiary);
}

.country-list__item input[type="radio"] {
  accent-color: var(--brand-primary);
}

.country-list__flag {
  display: flex;
  flex: 0 0 auto;
}

.flag-icon {
  display: block;
  border-radius: 2px;
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 8%);
}

/* The simulation's "Country" assumption row - which country's prices the
   figures came from, stated but no longer editable in place (the domain
   decides it now), so this is a plain label with a flag, not a control. */
.assumptions-row__flag {
  display: inline-flex;
  vertical-align: -2px;
  margin-right: var(--space-2);
}

.country-list__name {
  font: var(--text-body-md);
  color: var(--text-primary);
}

/* The checkout country field's trigger button - opens edit-country-modal.
   Sized/bordered like .field select so it reads as "an input" among the
   other checkout fields, not as a distinct button style. 308px matches
   one .grid-2 column's width at the wizard's 640px max-width (a full
   640px trigger would just be a mostly-empty box around a short flag +
   name), so it reads as the same size as the fields below it. */
.country-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  font: var(--text-body-md);
  color: var(--text-primary);
  font-family: var(--font-body);
  width: 100%;
  max-width: 308px;
  cursor: pointer;
  text-align: left;
}

.country-trigger:hover,
.country-trigger:focus-visible {
  outline: none;
  border-color: var(--brand-primary);
}

/* ---------- Badges & tags ---------- */

.badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  font: var(--text-caption);
  letter-spacing: var(--letter-spacing-wide);
  text-transform: uppercase;
  background: var(--neutral-100);
  color: var(--text-secondary);
}

.badge-primary {
  background: var(--primary-100);
  color: var(--primary-700);
}

.badge-accent {
  background: var(--accent-100);
  color: var(--accent-700);
}

.badge-success {
  background: oklch(92% 0.05 150);
  color: var(--success-700);
}

.badge-danger {
  background: oklch(93% 0.06 25);
  color: var(--danger-700);
}

/* Icon-only badge (the solar-panel glyph closing out .hero__badges) -
   text badges get their shape from letter-spacing + uppercase padding,
   which reads as lopsided once there's no text left to balance it. */
.badge-icon {
  padding: 4px 8px;
  line-height: 0;
}

.tag {
  display: inline-flex;
  align-items: center;
  padding: 4px 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: var(--bg-surface);
  font: var(--text-body-sm);
  color: var(--text-primary);
}

/* ---------- Alerts / messages ---------- */

.alert {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface-sunken);
  color: var(--text-primary);
  font: var(--text-body-sm);
  margin-bottom: var(--space-3);
}

.alert-error,
.alert-danger {
  background: oklch(96% 0.03 25);
  border-color: oklch(85% 0.08 25);
  color: var(--danger-700);
}

.alert-success {
  background: oklch(95% 0.03 150);
  border-color: oklch(85% 0.07 150);
  color: var(--success-700);
}

.alert-warning {
  background: var(--accent-100);
  border-color: var(--accent-300);
  color: var(--accent-900);
}

.alert-info,
.alert-debug {
  background: var(--bg-surface-sunken);
  border-color: var(--border-subtle);
  color: var(--text-secondary);
}

.alert__close {
  border: none;
  background: none;
  color: inherit;
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 0;
  opacity: 0.6;
}

.alert__close:hover {
  opacity: 1;
}

/* ---------- Forms ---------- */

form {
  font-family: var(--font-body);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: var(--space-4);
}

.field label {
  font: var(--text-label);
  color: var(--text-secondary);
}

.field input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
.field select,
.field textarea {
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  font: var(--text-body-md);
  color: var(--text-primary);
  font-family: var(--font-body);
  width: 100%;
  max-width: 420px;
  transition: border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

.field textarea {
  max-width: 100%;
  min-height: 100px;
}

.field select {
  max-width: 260px;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--brand-primary);
  box-shadow: 0 0 0 3px var(--primary-100);
}

.field.has-error input,
.field.has-error select,
.field.has-error textarea {
  border-color: var(--danger-500);
}

.field-checkbox label {
  display: flex;
  /* flex-start, not center: a short one-line label (e.g. the newsletter
     opt-out) still looks fine top-aligned, but a long label wrapping to
     several lines (e.g. the T&C consent tick's three linked documents -
     see orders.forms._consent_label) needs the checkbox pinned to the
     first line, not vertically centered against the whole wrapped block. */
  align-items: flex-start;
  gap: var(--space-2);
  font: var(--text-body-md);
  color: var(--text-primary);
}

.field-checkbox label input[type="checkbox"] {
  /* Nudge down to align with the label text's first line, not the top of
     the flex row. */
  margin-top: 0.2em;
}

input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--brand-primary);
  width: 16px;
  height: 16px;
}

input[type="range"].slider-input {
  display: block;
  width: 100%;
  max-width: 420px;
  margin-top: var(--space-2);
  accent-color: var(--brand-primary);
}

.field-help {
  font: var(--text-caption);
  color: var(--text-tertiary);
  margin: 0;
  max-width: 480px;
}

.field-error {
  font: var(--text-caption);
  color: var(--danger-500);
  margin: 0;
}

.field-warning {
  font: var(--text-caption);
  color: var(--accent-700);
  margin: var(--space-1) 0 0;
}

.form-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: flex-end;
}

.form-row .field {
  margin-bottom: 0;
}

.form-actions {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  margin-top: var(--space-4);
}

.string-form {
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-3);
  margin-bottom: var(--space-3);
}

.form-section-intro {
  margin-bottom: var(--space-4);
}

/* ---------- Tables ---------- */

table.table {
  width: 100%;
  border-collapse: collapse;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-surface);
  font: var(--text-body-sm);
}

table.table th,
table.table td {
  padding: 12px 16px;
  text-align: left;
  border-top: 1px solid var(--border-subtle);
}

table.table thead th {
  border-top: none;
  font: var(--text-label);
  color: var(--text-tertiary);
  background: var(--bg-surface-sunken);
}

.hardware-col__name {
  font: var(--text-heading-sm);
  color: var(--text-primary);
  margin-top: var(--space-2);
}

.hardware-col__price {
  font: var(--text-display-lg);
  color: var(--brand-primary);
  font-feature-settings: "tnum" 1;
  margin-top: var(--space-2);
}

table.table tbody td {
  color: var(--text-primary);
  font-feature-settings: "tnum" 1;
}

table.table tfoot th,
table.table tfoot td {
  font-weight: 600;
  background: var(--bg-surface-sunken);
  font-feature-settings: "tnum" 1;
}

/* ---------- Definition lists (key/value rows) ---------- */

.kv-list {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  overflow: hidden;
  margin: 0 0 var(--space-5);
}

.kv-row {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border-subtle);
}

.kv-row:first-child {
  border-top: none;
}

.kv-row dt {
  font: var(--text-label);
  color: var(--text-tertiary);
}

.kv-row dd {
  margin: 0;
  font: var(--text-body-md);
  color: var(--text-primary);
  font-feature-settings: "tnum" 1;
}

@media (max-width: 600px) {
  .kv-row {
    grid-template-columns: 1fr;
    gap: var(--space-1);
  }
}

/* A value cell that also carries a pencil-edit button - the button sits
   at the end of the same flex row as the value instead of its own grid
   column, so plain (non-editable) kv-rows don't need to reserve space
   for one. */
.kv-value-editable {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

/* A kv-row that also shows a read-only "Status" alongside the editable
   "Setting" (the device page's control mode row: a 3-button toggle plus
   the mode the device last reported back) - a third grid column, headed
   by .kv-row--dual-head once per list rather than per row. */
.kv-row--dual {
  grid-template-columns: 220px 1fr 1fr;
}

.kv-row--dual-head {
  grid-template-columns: 220px 1fr 1fr;
  padding-bottom: 0;
  border-top: none;
}

.kv-row--dual-head span {
  font: var(--text-label);
  color: var(--text-tertiary);
  font-style: italic;
}

@media (max-width: 600px) {
  .kv-row--dual,
  .kv-row--dual-head {
    grid-template-columns: 1fr;
  }
}

/* Device ID, masked like a password - monospace so every "*" and the
   one revealed digit per group line up predictably. */
.masked-id {
  font-family: var(--font-mono, ui-monospace, monospace);
  letter-spacing: 0.03em;
}

/* ---------- Lists (device list, claim candidates) ---------- */

.list-group {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-surface);
}

.list-group-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4);
  border-top: 1px solid var(--border-subtle);
  color: var(--text-primary);
  font: var(--text-body-md);
}

.list-group-item:first-child {
  border-top: none;
}

a.list-group-item:hover {
  background: var(--bg-surface-sunken);
  text-decoration: none;
}

/* ---------- Marketing sections ---------- */

.hero {
  padding: var(--space-9) var(--space-6) var(--space-7);
  max-width: 820px;
  margin: 0 auto;
  text-align: center;
}

.hero__badges {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
}

.hero__title {
  font: var(--text-display-2xl);
  color: var(--text-primary);
  letter-spacing: var(--letter-spacing-tight);
  margin: var(--space-5) 0 0;
}

.hero__lead {
  font: var(--text-body-lg);
  color: var(--text-secondary);
  margin: var(--space-5) auto 0;
  max-width: 600px;
}

.hero__actions {
  display: flex;
  gap: var(--space-3);
  justify-content: center;
  margin-top: var(--space-6);
  flex-wrap: wrap;
}

@media (max-width: 700px) {
  .hero {
    padding: var(--space-7) var(--space-4) var(--space-6);
  }

  .hero__title {
    font: var(--text-display-lg);
  }
}

.stat-bar__value {
  font: var(--text-display-lg);
  color: var(--brand-primary);
  font-feature-settings: "tnum" 1;
}

.stat-bar__label {
  font: var(--text-body-sm);
  color: var(--text-secondary);
  margin-top: var(--space-1);
}

/* ---------- Simulation profit widget ---------- */

.simulation-widget {
  display: grid;
  grid-template-columns: 1.15fr 1fr; /* chart side gets slightly more room */
  align-items: stretch;
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.simulation-widget__chart {
  padding: var(--space-5);
}

.simulation-widget__summary {
  padding: var(--space-5);
  background: var(--bg-surface-sunken);
  border-left: 1px solid var(--border-subtle);
}

@media (max-width: 900px) {
  .simulation-widget {
    grid-template-columns: 1fr;
  }

  .simulation-widget__summary {
    border-left: none;
    border-top: 1px solid var(--border-subtle);
  }
}

.profit-figure {
  font: var(--text-display-xl);
  color: var(--brand-primary);
  font-feature-settings: "tnum" 1;
  margin: var(--space-2) 0 var(--space-4);
}

.assumptions-list {
  display: flex;
  flex-direction: column;
  margin: var(--space-3) 0 var(--space-5);
}

.assumptions-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-top: 1px solid var(--border-subtle);
}

.assumptions-row:first-child {
  border-top: none;
}

.assumptions-row__label {
  color: var(--text-tertiary);
  font: var(--text-label);
  flex: 1 1 auto;
}

.assumptions-row__value {
  font: var(--text-body-md);
  color: var(--text-primary);
  font-feature-settings: "tnum" 1;
  text-align: right;
}

.pencil-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-1);
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-tertiary);
  border-radius: var(--radius-sm);
  flex: 0 0 auto;
}

.pencil-btn:hover {
  color: var(--brand-primary);
}

.section {
  padding: var(--space-8) var(--space-6);
  max-width: var(--content-max-width);
  margin: 0 auto;
}

/* The newsletter signup directly under .final-cta (home/pricing/partners)
   - plain .section's equal top/bottom padding reads as closer to the dark
   CTA above it than to the footer below: a hard color-contrast edge (the
   CTA's dark background ending) makes the top gap look smaller than the
   same pixel gap ending in the footer's own faint 1px border, on top of
   which the footer then adds its own top padding past that border. Shift
   some of the padding from bottom to top so the block reads centered. */
.section--newsletter-after-cta {
  padding-top: var(--space-9);
  padding-bottom: var(--space-6);
}

.section-sunken {
  background: var(--bg-surface-sunken);
}

.section-sunken > .section {
  padding-left: var(--space-6);
  padding-right: var(--space-6);
}

.section-dark {
  background: var(--neutral-950);
  color: var(--neutral-0);
  text-align: center;
}

.section-dark .section-title,
.section-dark .page-title {
  color: var(--neutral-0);
}

.section-dark p {
  color: var(--neutral-400);
}

/* ---------- Final CTA (shared by home/pricing/partners) ---------- */

/* One component now stands in for the `.section-dark` + `.hero__actions`
   markup previously hand-duplicated across 3 pages - see
   partials/_final_cta.html for why, and for the image-box mode this
   layout is ready for once a product photo is supplied. */
.final-cta__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
}

.final-cta--with-image .final-cta__inner {
  flex-direction: row;
  align-items: center;
  text-align: left;
}

/* A <button>, not a <div> - it opens the enlarge-photo modal (see the
   partial's own comment) - so the usual button chrome (border, its own
   background/font, the pointer default cursor) needs resetting back to
   what the box already looked like as a plain div. */
.final-cta__image-box {
  flex-shrink: 0;
  /* min(), not a plain 350px, so it stays square (aspect-ratio: 1) by
     shrinking width and height together on a narrow phone instead of
     width alone overflowing the section's own side padding. */
  width: min(350px, 100%);
  aspect-ratio: 1;
  /* #fdfdfd, not --neutral-0 (oklch 99% with a faint cool tint) - matches
     the product photo's own near-white background exactly, since even
     that faint tint shows up as a visible seam around the device. */
  background: #fdfdfd;
  /* Deliberately rounder than --radius-lg (16px, the largest existing
     token) - "strongly rounded corners" was specifically requested for
     this box, and no bigger token exists yet to reach for. */
  border-radius: 56px;
  border: none;
  padding: var(--space-6);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: transform 150ms var(--ease-standard), box-shadow 150ms var(--ease-standard);
}

.final-cta__image-box:hover,
.final-cta__image-box:focus-visible {
  transform: scale(1.02);
  box-shadow: var(--shadow-lg);
}

.final-cta__image-box img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* The enlarged view opened by clicking the box above - default (non
   -wide) .modal sizing already tops out around the photo's own native
   width, so it displays crisp rather than upscaled. */
.final-cta__image-modal img {
  display: block;
  max-width: 100%;
  max-height: 70vh;
  margin: 0 auto;
}

.final-cta__body {
  min-width: 0;
}

.final-cta__title {
  margin: 0 0 var(--space-2);
}

/* Repeats the top-of-page launch banner's own copy (see
   catalog/_launch_offer_banner.html) rather than paraphrasing it - a
   visitor who scrolled straight past that banner sees the identical
   offer again here, not a second, slightly different claim. Warm
   accent ("Solar Amber") instead of the neutral --neutral-400 that
   `.section-dark p` uses, so it's the one thing in the band that reads
   as urgent rather than descriptive. Selector is `.final-cta
   .final-cta__urgency`, not the bare class - `.section-dark p` (class
   + element) otherwise outranks a single-class selector and silently
   wins back the neutral color regardless of source order. */
.final-cta .final-cta__urgency {
  display: flex;
  /* flex-start, not center: matches .tier-feature's own icon+wrapping-text
     pattern - centering vertically against the *whole* wrapped block would
     otherwise float the icon between lines once "Ends in ..." wraps under
     the offer on a narrow viewport. */
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-1);
  color: var(--accent-500);
  font: var(--text-label);
  margin: var(--space-2) 0 0;
}

.final-cta__urgency svg {
  flex-shrink: 0;
  margin-top: 2px;
}

.final-cta--with-image .final-cta__urgency,
.final-cta--with-image .hero__actions {
  justify-content: flex-start;
}

/* Closer than .hero__actions' own default var(--space-6) - the urgency
   line moved above the heading (see the partial) leaves noticeably more
   air above the button than .hero__actions was tuned for. */
.final-cta .hero__actions {
  margin-top: var(--space-4);
}

/* The urgency line gets the same glow pulse as the hero's "today" and
   the featured-example profit figure (feature-split-glow, defined
   above) once the section's own scroll-reveal settles - ties this
   band's "spice it up" pass to the motion language already used
   elsewhere instead of inventing a new one. */
.final-cta.reveal-on-scroll.is-visible .final-cta__urgency {
  animation: feature-split-glow 1300ms ease-out 500ms both;
}

@media (max-width: 700px) {
  .final-cta--with-image .final-cta__inner {
    flex-direction: column;
    text-align: center;
  }

  /* A wide, short card reads better than the desktop square once it's
     stacked above the copy instead of beside it - a tall portrait photo
     inside a landscape box still centers itself via object-fit: contain,
     just letterboxed left/right rather than filling the card. height is
     an explicit calc(), not `aspect-ratio: 3 / 2` - as a flex item in a
     column-direction container (.final-cta--with-image .final-cta__inner
     switches to flex-direction: column above), the box's main-size (its
     height here) hugs its content instead of resolving from aspect-ratio,
     so it rendered taller than intended - the photo's own height plus
     padding, not 2/3 of the card's width. */
  .final-cta--with-image .final-cta__image-box {
    width: 80vw;
    height: calc(80vw * 2 / 3);
  }

  .final-cta--with-image .final-cta__urgency,
  .final-cta--with-image .hero__actions {
    justify-content: center;
  }
}

@media (prefers-reduced-motion: reduce) {
  .final-cta.reveal-on-scroll.is-visible .final-cta__urgency {
    animation: none;
  }
}

/* Country-coverage band, directly under the hero on every domain - a
   solid brand-blue break from the hero's white background (bolder than
   .section-dark's near-black, which stays reserved for the "Ready to
   start curtailing today?" CTA further down the page). */
.hero-countries {
  background: var(--brand-primary);
  color: var(--neutral-0);
  text-align: center;
}

.hero-countries .page-title {
  color: var(--neutral-0);
  margin-bottom: var(--space-5);
}

/* The track is the flag list rendered twice back to back (see home.html)
   and scrolled exactly half its own width, so the seam between the first
   and second copy is where the loop resets - invisible as long as both
   copies are identical. overflow:hidden has to stay (it's what keeps the
   doubled track from spilling past the fade at each edge), but it clips
   in both directions, not just the one the marquee actually needs - the
   padding-top gives .flag-carousel__label's tooltip room to render above
   a flag instead of being clipped away invisibly the moment it shows. */
.flag-carousel {
  overflow: hidden;
  padding-top: var(--space-6);
  mask-image: linear-gradient(
    to right,
    transparent,
    black var(--space-6),
    black calc(100% - var(--space-6)),
    transparent
  );
  -webkit-mask-image: linear-gradient(
    to right,
    transparent,
    black var(--space-6),
    black calc(100% - var(--space-6)),
    transparent
  );
}

.flag-carousel__track {
  display: flex;
  width: max-content;
  gap: var(--space-4);
  animation: flag-carousel-scroll 36s linear infinite;
}

.flag-carousel__flag {
  position: relative;
  display: flex;
  flex: 0 0 auto;
}

/* Hovering (mouse) or tapping (touch, via the .is-active class the
   javascript block below toggles - :hover alone doesn't fire reliably on
   touch) pauses the whole track, not just the one flag: it's still
   scrolling underneath, so without this the tooltip would be reading a
   flag that's already drifted out from under it. */
.flag-carousel__track:has(.flag-carousel__flag:hover),
.flag-carousel__track:has(.flag-carousel__flag.is-active) {
  animation-play-state: paused;
}

.flag-carousel__label {
  position: absolute;
  bottom: calc(100% + var(--space-2));
  left: 50%;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  background: var(--neutral-950);
  color: var(--neutral-0);
  font: var(--text-caption);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, 4px);
  transition:
    opacity var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

.flag-carousel__flag:hover .flag-carousel__label,
.flag-carousel__flag.is-active .flag-carousel__label {
  opacity: 1;
  transform: translate(-50%, 0);
}

.grid-4 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-5);
}

.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-5);
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-5);
}

/* A .field's own bottom margin would otherwise stack on top of the
   grid's own row gap, making the gap between grid rows bigger than the
   gap between the grid and whatever follows it - see .wizard-wrap
   below, which restores a single consistent gap after the whole grid
   instead. */
.grid-2 .field {
  margin-bottom: 0;
}

@media (max-width: 900px) {
  .grid-4 {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 700px) {
  .grid-3,
  .grid-2 {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 520px) {
  .grid-4 {
    grid-template-columns: 1fr;
  }
}

.step-number {
  font: var(--text-display-lg);
  color: var(--primary-200);
}

/* ---------- Feature split (big claim + real data, side by side) ---------- */

.feature-split {
  max-width: var(--content-max-width);
  margin: 0 auto;
  padding: var(--space-8) var(--space-6);
}

.feature-split__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-8);
  align-items: center;
}

.feature-split__title {
  font: var(--text-display-lg);
  color: var(--text-primary);
  letter-spacing: var(--letter-spacing-tight);
  margin: 0;
}

.feature-split__note {
  font: var(--text-body-sm);
  color: var(--text-tertiary);
  margin-top: var(--space-1);
}

.feature-split__pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-6);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  background: var(--bg-surface);
  box-shadow: var(--shadow-sm);
  color: var(--text-primary);
  font: var(--text-body-sm);
  font-weight: 600;
  transition: box-shadow var(--duration-fast) var(--ease-standard);
}

.feature-split__pill:hover {
  box-shadow: var(--shadow-md);
  text-decoration: none;
}

.feature-split__pill svg {
  flex-shrink: 0;
  color: var(--brand-primary);
}

.feature-split__visual {
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  padding: var(--space-5);
}

.feature-split__hint {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
  font: var(--text-caption);
  color: var(--text-tertiary);
}

.feature-split__hint svg {
  flex-shrink: 0;
}

.feature-split__next {
  display: block;
  margin: var(--space-5) 0 0;
}

@media (max-width: 900px) {
  .feature-split__grid {
    grid-template-columns: 1fr;
  }
}

/* A "pop" on the text block and the chart card whenever the featured
   example changes (Next example button) - a small overshoot (past 100%
   scale, past 0 on the way up) reads as more alive than a flat fade, and
   draws the eye to the new numbers instead of them just silently
   swapping. */
@keyframes feature-split-pop {
  0% {
    opacity: 0;
    transform: translateY(18px) scale(0.96);
  }

  55% {
    opacity: 1;
    transform: translateY(-4px) scale(1.015);
  }

  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.feature-split--pop {
  animation: feature-split-pop 750ms var(--ease-standard) both;
}

/* The profit figure itself gets an extra glow pulse on top of the
   block-level pop, timed to land as the pop settles. */
@keyframes feature-split-glow {
  0%,
  100% {
    text-shadow: 0 0 0 transparent;
  }

  55% {
    text-shadow: 0 0 20px currentColor;
  }
}

.feature-split--pop .feature-split__profit {
  animation: feature-split-glow 1100ms ease-out;
}

/* ---------- Scroll-triggered reveal ---------- */

@keyframes reveal-in {
  0% {
    opacity: 0;
    transform: translateY(40px) scale(0.97);
  }

  60% {
    opacity: 1;
    transform: translateY(-6px) scale(1.008);
  }

  100% {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

.reveal-on-scroll {
  opacity: 0;
}

.reveal-on-scroll.is-visible {
  animation: reveal-in 900ms var(--ease-standard) both;
}

/* Grid cards inside a revealed section cascade in one after another
   rather than all popping at once - each column waits a little longer
   than the last (capped at 4, the widest grid on this page). */
.reveal-on-scroll .grid-2 > *,
.reveal-on-scroll .grid-3 > *,
.reveal-on-scroll .grid-4 > * {
  opacity: 0;
}

.reveal-on-scroll.is-visible .grid-2 > *,
.reveal-on-scroll.is-visible .grid-3 > *,
.reveal-on-scroll.is-visible .grid-4 > * {
  animation: reveal-in 700ms var(--ease-standard) both;
  animation-delay: var(--reveal-delay, 0ms);
}

.reveal-on-scroll.is-visible .grid-2 > *:nth-child(2),
.reveal-on-scroll.is-visible .grid-3 > *:nth-child(2),
.reveal-on-scroll.is-visible .grid-4 > *:nth-child(2) {
  --reveal-delay: 120ms;
}

.reveal-on-scroll.is-visible .grid-3 > *:nth-child(3),
.reveal-on-scroll.is-visible .grid-4 > *:nth-child(3) {
  --reveal-delay: 240ms;
}

.reveal-on-scroll.is-visible .grid-4 > *:nth-child(4) {
  --reveal-delay: 360ms;
}

/* ---------- Hero entrance ---------- */

/* The hero is already in view at page load, so there's nothing for the
   scroll IntersectionObserver to trigger on - this plays once,
   unconditionally, as soon as the page renders, each of the hero's 4
   direct children (badges row, title, lead, actions) cascading in after
   the last. */
.hero > * {
  opacity: 0;
  animation: reveal-in 800ms var(--ease-standard) both;
}

.hero > *:nth-child(2) {
  animation-delay: 120ms;
}

.hero > *:nth-child(3) {
  animation-delay: 240ms;
}

.hero > *:nth-child(4) {
  animation-delay: 360ms;
}

/* "today" is the headline's whole point (immediacy, not just "someday")
   - color + italic sets it apart from the rest of the title on its own,
   and a glow pulse (reusing the same sweep as the featured-example
   profit figure) lands just as the title's own entrance settles. */
.hero__today {
  color: var(--brand-primary);
  font-style: italic;
  animation: feature-split-glow 1200ms ease-out 950ms both;
}

/* Scrolls the doubled flag list (see home.html) left by exactly one copy's
   width, then snaps back to 0 - since both copies are identical, the snap
   is invisible and the strip reads as an endless loop. */
@keyframes flag-carousel-scroll {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(-50%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .feature-split--pop,
  .feature-split--pop .feature-split__profit,
  .hero__today {
    animation: none;
  }

  .reveal-on-scroll,
  .reveal-on-scroll .grid-2 > *,
  .reveal-on-scroll .grid-3 > *,
  .reveal-on-scroll .grid-4 > *,
  .hero > * {
    opacity: 1;
    animation: none;
    transform: none;
  }

  .flag-carousel__track {
    animation: none;
  }
}

.step-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
  background: var(--primary-100);
  color: var(--brand-primary);
  margin-bottom: var(--space-3);
}

.pricing-table {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-surface);
}

.pricing-row {
  display: grid;
  grid-template-columns: 1.4fr 2fr auto;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}

.pricing-row__title {
  font: var(--text-heading-sm);
  color: var(--text-primary);
}

.section-narrow {
  max-width: 720px;
  text-align: center;
}

.prose {
  max-width: 760px;
}

.pricing-row:first-child {
  border-top: none;
}

@media (max-width: 700px) {
  .pricing-row {
    grid-template-columns: 1fr;
  }
}

/* ---------- Auth pages (login/signup/etc.) ---------- */

.auth-wrap {
  max-width: 440px;
  margin: 0 auto;
  padding: var(--space-7) 0;
}

.auth-wrap h1 {
  font: var(--text-heading-lg);
  color: var(--text-primary);
  margin-bottom: var(--space-4);
}

.auth-wrap p {
  color: var(--text-secondary);
}

.auth-wrap table.table {
  font: var(--text-body-sm);
}

/* ---------- FAQ (native details/summary, no JS) ---------- */

.faq-item {
  border-top: 1px solid var(--border-subtle);
  padding: var(--space-4) 0;
}

.faq-item:first-child {
  border-top: none;
}

.faq-item summary {
  cursor: pointer;
  font: var(--text-heading-sm);
  color: var(--text-primary);
  list-style: none;
}

.faq-item summary::-webkit-details-marker {
  display: none;
}

.faq-item summary::after {
  content: "+";
  float: right;
  color: var(--text-tertiary);
  font-weight: 400;
}

.faq-item[open] summary::after {
  content: "\2212";
}

.faq-item p {
  color: var(--text-secondary);
  margin-top: var(--space-3);
  max-width: 680px;
}

/* ---------- Checkout wizard ---------- */

.wizard-wrap {
  max-width: 640px;
  margin: 0 auto;
  padding: var(--space-6) 0 var(--space-9);
}

/* .field input/select/textarea cap at 420px site-wide (a sane readable
   width on a page with room to spare) - but the wizard is already
   capped at 640px total, so that same cap left a standalone field (e.g.
   an address) visibly narrower than the two-column grid above it.
   Removing it here only affects fields outside .grid-2 - grid columns
   are already narrower than 420px, so this is a no-op for them. */
.wizard-wrap .field input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
.wizard-wrap .field textarea {
  max-width: none;
}

/* Restores one consistent gap below the contact-details grid (matching
   every other field's own margin-bottom) now that .grid-2 .field can't
   contribute its own. */
.wizard-wrap .grid-2 {
  margin-bottom: var(--space-4);
}

.wizard-header {
  text-align: center;
  margin-bottom: var(--space-3);
}

.wizard-header__title {
  font: var(--text-display-lg);
  color: var(--text-primary);
}

.wizard-header__step {
  font: var(--text-body-md);
  color: var(--text-secondary);
  margin-top: var(--space-2);
}

.step-dots {
  display: flex;
  gap: var(--space-2);
  justify-content: center;
  margin-bottom: var(--space-6);
}

.step-dots__dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--neutral-200);
  transition: width var(--duration-fast) var(--ease-standard),
    background var(--duration-fast) var(--ease-standard);
}

.step-dots__dot.is-done {
  background: var(--brand-primary);
}

.step-dots__dot.is-current {
  width: 22px;
  background: var(--brand-primary);
}

.option-card {
  display: block;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  background: var(--bg-surface);
  margin-bottom: var(--space-4);
}

/* Selected/detail state keys off the card's OWN main radio only (a direct
   descendant of .option-card__radio-label), not any nested term/tier
   toggle radios living inside .option-card__detail - those are a
   separate child of .option-card, so this selector can't see them. */
.option-card:has(> .option-card__radio-label > .option-card__radio > input:checked) {
  border: 2px solid var(--brand-primary);
  padding: calc(var(--space-5) - 1px);
}

.option-card__radio-label {
  display: block;
  cursor: pointer;
}

.option-card__radio input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.option-card__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
}

.option-card__title {
  font: var(--text-heading-sm);
  color: var(--text-primary);
}

.option-card__price {
  font: var(--text-heading-sm);
  color: var(--brand-primary);
  white-space: nowrap;
  font-feature-settings: "tnum" 1;
}

.option-card__subtitle {
  font: var(--text-body-sm);
  color: var(--text-secondary);
  margin-top: var(--space-1);
}

.option-card__detail {
  display: none;
  margin-top: var(--space-3);
}

/* The checkout hardware step's device quantity picker - the +/-
   buttons themselves are generic (see site.js's .quantity-stepper
   delegation), this just makes them look like one control. */
.quantity-stepper {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

/* Only present when several steppers share one card (the SDE Pro tier
   picker) and each needs its own name - step_hardware.html's device
   cards don't use this, their own title above the stepper is enough. */
.quantity-stepper__label {
  flex: 1 1 auto;
  font: var(--text-body-sm);
  color: var(--text-secondary);
}

.quantity-stepper__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  font: 600 16px var(--font-body);
  color: var(--text-primary);
  cursor: pointer;
}

.quantity-stepper__btn:hover {
  border-color: var(--brand-primary);
  color: var(--brand-primary);
}

.quantity-stepper__input {
  width: 48px;
  text-align: center;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  padding: 6px 4px;
  font: var(--text-body-md);
  font-feature-settings: "tnum" 1;
  color: var(--text-primary);
  /* Spin buttons duplicate the +/- controls either side of this input
     and look inconsistent across browsers - hidden in favor of those. */
  appearance: textfield;
}

.quantity-stepper__input::-webkit-outer-spin-button,
.quantity-stepper__input::-webkit-inner-spin-button {
  appearance: none;
  margin: 0;
}

.option-card:has(> .option-card__radio-label > .option-card__radio > input:checked) .option-card__detail {
  display: block;
}

.toggle-group {
  display: flex;
  gap: var(--space-2);
}

.toggle-group label {
  flex: 1;
  text-align: center;
  padding: var(--space-2);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-default);
  background: var(--bg-surface);
  cursor: pointer;
  font: 600 13px var(--font-body);
  color: var(--text-primary);
  transition: background var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.toggle-group input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.toggle-group label:has(input:checked) {
  border: 2px solid var(--brand-primary);
  background: var(--brand-primary);
  color: var(--text-on-primary);
  padding: calc(var(--space-2) - 1px);
  box-shadow: var(--shadow-sm);
}

.toggle-group label:has(input:checked)::before {
  content: "✓ ";
}

/* The per-term price next to each 1yr/3yr toggle (populated from
   pricing_catalog by step_plan.html's JS) - a plain sub-line under the
   term name, not its own color, so it reads as "1 year, €195" rather
   than competing with the ✓/checked-state color change above. */
.toggle-group__price {
  display: block;
  font: 600 12px var(--font-body);
  opacity: 0.8;
  margin-top: 2px;
  font-feature-settings: "tnum" 1;
}

.price-recap {
  padding: var(--space-4) var(--space-5);
  border: 1px dashed var(--border-default);
  border-radius: var(--radius-lg);
  margin-top: var(--space-4);
}

/* step_hardware.html's running subtotal is filled in by script and
   emptied again when every quantity is 0 - without this the dashed
   frame stayed on screen as an empty box until something was added. */
.price-recap:empty {
  display: none;
}

.price-recap__title {
  font: 600 14px var(--font-body);
  color: var(--text-primary);
  margin-bottom: var(--space-2);
}

.price-recap__row {
  display: flex;
  justify-content: space-between;
  font: var(--text-body-sm);
  color: var(--text-secondary);
  padding: 2px 0;
  font-feature-settings: "tnum" 1;
}

.price-recap__total {
  display: flex;
  justify-content: space-between;
  font: var(--text-heading-sm);
  color: var(--brand-primary);
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--border-subtle);
  font-feature-settings: "tnum" 1;
}

.wizard-actions {
  display: flex;
  justify-content: space-between;
  margin-top: var(--space-6);
}

.wizard-status {
  text-align: center;
}

.wizard-status p {
  margin: var(--space-4) auto 0;
}

/* ---------- Pricing page: view toggle, info tips, image slots ---------- */

.view-toggle {
  display: inline-flex;
  padding: 4px;
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
}

.view-toggle button {
  padding: 9px 22px;
  border-radius: var(--radius-pill);
  border: none;
  cursor: pointer;
  font: 600 14px var(--font-body);
  text-transform: capitalize;
  background: transparent;
  color: var(--text-secondary);
  transition: background var(--duration-fast) var(--ease-standard);
}

.view-toggle button.is-active {
  background: var(--brand-primary);
  color: var(--text-on-primary);
}

.info-tip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  margin-left: 5px;
  border-radius: 50%;
  border: 1px solid var(--border-default);
  background: var(--bg-surface-sunken);
  color: var(--text-tertiary);
  font: 600 10px var(--font-body);
  vertical-align: middle;
  cursor: help;
}

.dashed-cta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
  padding: var(--space-5) var(--space-6);
  border: 1px dashed var(--border-default);
  border-radius: var(--radius-lg);
}

.dashed-cta p {
  max-width: 520px;
  margin: 0;
}

/* Phone-first alternative to the contact form, sat directly above it. */
.contact-direct {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-6);
  padding: var(--space-3) var(--space-4);
  background: var(--bg-surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  max-width: var(--content-max-width-narrow);
}

.contact-direct__label {
  font: var(--text-body-sm);
  color: var(--text-secondary);
}

.contact-direct__phone {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font: var(--text-body-md);
  font-weight: 600;
}

.cta-box {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
  padding: var(--space-6);
  background: var(--brand-primary);
  border-radius: var(--radius-lg);
}

.cta-box__title {
  font: var(--text-heading-sm);
  color: var(--text-on-primary);
  margin: 0 0 var(--space-1);
}

.cta-box__body {
  font: var(--text-body-md);
  color: var(--text-on-primary);
  margin: 0;
  max-width: 480px;
}

.cta-box .btn {
  background: var(--bg-surface);
  color: var(--brand-primary);
  flex-shrink: 0;
}

.cta-box .btn:hover {
  background: var(--neutral-50);
  color: var(--brand-primary);
}

/* The hardware tab's shared product photo, sat above the comparison
   table rather than duplicated per column - see pricing.html's own
   comment on why one photo, not two identical ones side by side. */
.hardware-photo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  text-align: center;
}

.hardware-photo img {
  max-width: 100%;
  max-height: 220px;
}

/* ---------- Pricing tiers ---------- */

.tier-card {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  padding: var(--space-6) var(--space-5);
  display: flex;
  flex-direction: column;
}

/* The three subscription cards sit in a shared 7-row subgrid (badge,
   name, price, price-alt, period, features, footnote) rather than as
   three independent flex columns, so the features divider - and the
   footnote below it - line up across all three regardless of one card
   having an extra line (Pro/Pro Plus's "or 3 years for..." row Basic
   doesn't). Each row's height is set by the tallest cell in that row
   across the three cards; an empty cell (Basic's own
   .tier-card__price-alt-slot) just occupies whatever height that row
   ends up needing, adding none of its own. Markup for each card has to
   keep exactly 7 direct children, in the same order, for this to line
   up - see pricing.html. A card short one child (this went unnoticed
   for a while on Basic, which is why this comment exists at all) shifts
   every row below it up by one for that card alone, misaligning the
   whole grid without erroring anywhere. */
.tier-grid {
  grid-template-rows: repeat(7, auto);
}

.tier-grid .tier-card {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 7;
}

.tier-card--highlight {
  border: 2px solid var(--brand-primary);
}

.tier-card__badge-slot {
  height: 24px;
}

.tier-card__name {
  font: var(--text-heading-sm);
  color: var(--text-primary);
  margin-top: var(--space-2);
}

.tier-card__price {
  font: var(--text-display-lg);
  color: var(--brand-primary);
  font-feature-settings: "tnum" 1;
  margin-top: var(--space-4);
  /* This row's shared subgrid height is set by Pro/Pro Plus's two lines
     (struck list price + .tier-card__price-now below it) - without this,
     the default grid stretch leaves Basic's single "€0" sitting at the
     top of that same-height cell instead of level with the other cards'
     own current-price line, which is always the bottom one. */
  align-self: end;
}

/* The launch price, stacked on its own line under the struck-through
   list price rather than sitting inline beside it (a plain <strong>
   used to) - a block-level element forces that line break since
   .tier-card__price itself isn't flex/grid. */
.tier-card__price-now {
  margin-top: var(--space-1);
}

/* The "or 3 years for <del>€X</del> €Y" line under a struck-through
   annual price (Pro/Pro Plus during the launch window) - smaller and
   quieter than .tier-card__price itself, same relationship
   .tier-card__price-row-label has to .tier-card__price-row. */
.tier-card__price-alt {
  font: var(--text-body-md);
  color: var(--text-secondary);
  font-feature-settings: "tnum" 1;
  margin-top: var(--space-1);
}

/* "/year" after a price, in both .tier-card__price (Pro) and a
   .tier-card__price-row (SDE Pro) - only overrides size/weight so it
   still inherits each context's own color and font-family rather than
   fixing on one card's serif display font. */
.tier-card__price-suffix {
  font-size: 0.5em;
  font-weight: 400;
}

.tier-card__price-stack {
  /* No gap - matches the zero gap the three .tier-card__price-row lines
     already had as bare flex-column children before this wrapper existed. */
  display: flex;
  flex-direction: column;
  margin-top: var(--space-4);
}

.tier-card__price-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  font: var(--text-heading-md);
  color: var(--brand-primary);
  font-feature-settings: "tnum" 1;
}

.tier-card__price-row-label {
  font: var(--text-body-sm);
  color: var(--text-tertiary);
}

.tier-card__period {
  font: var(--text-caption);
  color: var(--text-tertiary);
  margin-top: var(--space-2);
}

.tier-card__features {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border-subtle);
}

.tier-feature {
  display: flex;
  gap: var(--space-2);
  align-items: flex-start;
  font: 600 14px var(--font-body);
  color: var(--text-primary);
}

.tier-feature svg {
  flex-shrink: 0;
  margin-top: 2px;
}

.tier-feature .badge {
  flex-shrink: 0;
  margin-left: auto;
}

/* Pro's market-coverage footnote - a "works best in" row of real flags
   plus a "works in" EU-flag link to the FAQ's full coverage list,
   replacing a sentence that had to name every country in prose. */
.tier-card__flags {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.tier-card__flags-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

/* The label ("Works best in:"/"Works in:") and everything after it
   (icons + the "(support for N countries)" text, wrapped together in
   .tier-card__flags-line so they move as one unit) - kept as two
   separate elements rather than one run of inline content specifically
   so the mobile rule below can break after the colon instead of
   wherever the text happens to wrap. */
.tier-card__flags-line {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.tier-card__flags-icons {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

@media (max-width: 700px) {
  /* Stacks the label above the flags/count instead of letting the row
     wrap mid-sentence wherever it runs out of width. */
  .tier-card__flags-row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
  }
}

/* The sitewide launch-offer banner - a single compact pill-shaped bar,
   not a stacked heading/sentence/caption card (an earlier three-line
   version read as heavier and more "shouty" than a two-week countdown
   warrants - see catalog/_launch_offer_banner.html's own comment).
   Two short segments separated by a divider, same shape as a typical
   crowdfunding-site countdown strip: the offer itself in accent color
   ("Solar Amber" - this app's own token for free/savings highlights,
   see tokens/colors.css), the countdown in muted text next to it. Wraps
   to two centered lines only on a narrow viewport; stays one line
   everywhere else. */
.launch-offer-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  border: 1px solid var(--accent-300);
  background: var(--accent-100);
  text-decoration: none;
  transition: background-color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
}

/* Now a link into the get-started flow (see the partial's own
   comment) - a hover/focus lift signals that, since nothing else
   about a pill-shaped bar otherwise reads as clickable. */
.launch-offer-banner:hover,
.launch-offer-banner:focus-visible {
  background: var(--accent-300);
  box-shadow: var(--shadow-sm);
  transform: translateY(-1px);
  text-decoration: none;
}

.launch-offer-banner__offer {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font: var(--text-label);
  color: var(--accent-900);
  white-space: nowrap;
}

.launch-offer-banner__icon {
  flex-shrink: 0;
}

.launch-offer-banner__divider {
  width: 1px;
  height: 16px;
  background: var(--accent-300);
}

/* Hidden alongside its divider once there's no room for a second
   segment on the same line - the offer itself is the half worth
   keeping if something has to give. */
@media (max-width: 420px) {
  .launch-offer-banner__divider,
  .launch-offer-banner__countdown {
    display: none;
  }
}

.launch-offer-banner__countdown {
  font: var(--text-body-sm);
  color: var(--accent-900);
  white-space: nowrap;
}

/* The Pro Plus monitoring blurb (pricing.html) - plain prose next to
   the tier grid, not another .tier-card (it isn't a priced thing on its
   own, and must never carry a savings/payback figure - see the launch
   pricing copy brief's business rule against pricing subscriptions
   against curtailment savings). */
.monitoring-blurb {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-surface);
  padding: var(--space-5);
}

.monitoring-blurb__title {
  font: var(--text-heading-sm);
  color: var(--text-primary);
}

.monitoring-blurb__body {
  color: var(--text-secondary);
  margin-top: var(--space-2);
}

.monitoring-blurb__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.addon-row {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}

.addon-row:first-child {
  border-top: none;
}

.addon-row--header {
  border-top: none;
  background: var(--bg-surface-sunken);
}

.addon-row__title {
  font: 600 14px var(--font-body);
  color: var(--text-primary);
}

.addon-row__note {
  font: var(--text-body-sm);
  color: var(--text-tertiary);
  font-style: italic;
}

@media (max-width: 700px) {
  .addon-row {
    grid-template-columns: 1fr;
  }
}

/* ---------- Use case cards ---------- */

/* The "these plants are made up, the numbers are not" note above the
   use-case grid - a quiet band, not a warning. */
.usecase-note {
  background: var(--bg-surface-sunken);
  box-shadow: none;
  max-width: 820px;
}

/* Tag badge and country flag share a row, flag pinned to the far side -
   pulls the flag out of the title text (where it read as part of the
   plant's name) and up next to the tag it's actually describing. */
.usecase-card__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.usecase-gain {
  font: var(--text-heading-md);
  color: var(--accent-700);
  font-feature-settings: "tnum" 1;
}

.usecase-link {
  display: inline-block;
  font: 600 13px var(--font-body);
  color: var(--brand-primary);
}

/* ---------- Threshold chart (brand motif) ---------- */

.threshold-chart {
  display: block;
  max-width: 100%;
  height: auto;
}

/* ---------- Legal pages (terms, service terms, privacy) ---------- */

/* Vertical rhythm for legal prose. The site-wide reset zeroes every
   heading and paragraph's margin (tokens/base.css's `h1, h2, h3, h4, p {
   margin: 0; }`), which is right for hand-laid-out marketing sections
   that use `--space-*` gaps directly, but reads as cramped in a long
   run of prose - paragraphs touching, and a subheading sitting flush
   against the paragraph above it instead of separating from it. These
   rules apply that spacing back for anything under `.legal-doc`
   specifically, rather than loosening the reset globally. */
.legal-doc p,
.legal-doc ul,
.legal-doc ol,
.legal-doc .table-scroll {
  margin-bottom: var(--space-4);
}

.legal-doc h2 {
  margin-top: var(--space-7);
  margin-bottom: var(--space-3);
}

.legal-doc h3 {
  margin-top: var(--space-5);
  margin-bottom: var(--space-2);
}

.legal-doc li + li {
  margin-top: var(--space-2);
}

/* No trailing gap under the last element of a section/the whole page. */
.legal-doc > :last-child,
.legal-doc section > :last-child {
  margin-bottom: 0;
}

.site-footer__legal {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
}

/* The rule between the Dutch and English halves of the disclaimer - a
   language boundary, not a section break, so it reads as quieter than the
   headings on either side of it. */
.legal-language-rule {
  border: none;
  border-top: 1px solid var(--border-subtle);
  margin: var(--space-7) 0;
}

.legal-translation-note {
  font: var(--text-body-sm);
  color: var(--text-tertiary);
}

/* ---------- General terms (algemene voorwaarden) ---------- */

.legal-meta {
  font: var(--text-body-sm);
  color: var(--text-tertiary);
}

.legal-clause-list {
  list-style: none;
  padding-left: 0;
}

.legal-clause-list ul {
  list-style: disc;
  padding-left: var(--space-6);
  margin-top: var(--space-2);
}

.legal-clause-number {
  font-weight: 600;
  margin-right: var(--space-2);
}

/* ---------- Subscriptions table (orders/subscription_list.html) ---------- */

.subscription-inline-form {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.subscription-inline-form:first-child {
  margin-top: 0;
}

.subscription-inline-form select {
  font: var(--text-body-sm);
}

/* ---------- Production site map (installations/production_site_form.html) ---------- */

.site-map {
  margin-bottom: var(--space-6);
}

.site-map__search {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.site-map__search input {
  flex: 1;
  min-width: 0;
  background: var(--bg-surface);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  font: var(--text-body-md);
  color: var(--text-primary);
}

.site-map__search input:focus {
  outline: none;
  border-color: var(--brand-primary);
  box-shadow: 0 0 0 3px var(--primary-100);
}

.site-map__canvas {
  height: 400px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.site-map-view {
  margin-bottom: var(--space-6);
  max-width: 480px;
}

.site-map-view__canvas {
  height: 200px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.site-map-overview {
  margin-bottom: var(--space-6);
}

.site-map-overview__canvas {
  height: 400px;
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  overflow: hidden;
}

/* ---------- App overview: KPI tiles, device cards, next steps ---------- */

/* A row of headline numbers (the home page across all devices, the
   device page's mini dashboard). auto-fit so four tiles sit in one row
   on a wide screen and wrap to two-up/one-up on narrower ones without
   a breakpoint per count. */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

.kpi {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4) var(--space-5);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  min-width: 0;
}

/* A tile that navigates somewhere (the home page's "Devices online",
   linking to the device list) rather than just reporting a number -
   an <a> instead of a <div>, so it needs the interactive affordances a
   plain KPI tile doesn't: no underline by default, a hover/focus state,
   and a chevron (.icon-chevron, same partial the device page's
   Inverters list already uses) so it reads as clickable rather than as
   one more static stat. */
a.kpi.kpi--link {
  position: relative;
  color: inherit;
  transition: border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

a.kpi.kpi--link:hover,
a.kpi.kpi--link:focus-visible {
  text-decoration: none;
  border-color: var(--primary-300);
  box-shadow: var(--shadow-md);
}

.kpi--link .icon-chevron {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  color: var(--text-tertiary);
}

.kpi__label {
  font: var(--text-label);
  color: var(--text-tertiary);
}

.kpi__value {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font: var(--text-heading-lg);
  color: var(--text-primary);
  font-feature-settings: "tnum" 1;
  white-space: nowrap;
}

.kpi__unit {
  font: var(--text-body-sm);
  color: var(--text-tertiary);
}

.kpi__empty {
  color: var(--text-tertiary);
}

.device-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-4);
}

.device-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  color: var(--text-primary);
  transition: border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard);
}

.device-card:hover {
  text-decoration: none;
  border-color: var(--primary-300);
  box-shadow: var(--shadow-md);
}

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

.device-card__name {
  font: var(--text-heading-sm);
}

.device-card__site {
  font: var(--text-body-sm);
  color: var(--text-secondary);
}

.device-card__stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
  margin: var(--space-2) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}

.device-card__stats dt {
  font: var(--text-caption);
  color: var(--text-tertiary);
}

.device-card__stats dd {
  margin: 0;
  font: var(--text-body-sm);
  font-weight: 600;
  font-feature-settings: "tnum" 1;
}

.next-steps {
  margin: 0;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font: var(--text-body-md);
}

/* ---------- Tabs (device/inverter pages) ---------- */

/* Long pages are split into tabs rather than one scroll: each
   [data-tabs] wrapper holds a row of [role=tab] buttons and matching
   [role=tabpanel] sections, wired up generically in site.js - the
   active tab is mirrored into the URL hash so a link like
   /app/devices/<id>/#settings (and a redirect back after saving a
   setting) lands on the right tab, and every panel is still present in
   the HTML for a reader without JS (they then just stack). */
.tabs__list {
  display: flex;
  gap: var(--space-1);
  margin-bottom: var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
  overflow-x: auto;
}

.tabs__tab {
  padding: var(--space-2) var(--space-4);
  margin-bottom: -1px;
  border: none;
  border-bottom: 2px solid transparent;
  background: none;
  color: var(--text-secondary);
  font: var(--text-body-sm);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard);
}

.tabs__tab:hover {
  color: var(--text-primary);
}

/* Four tabs don't fit a phone in one row - wrap rather than hide the
   last one behind a horizontal scroll nobody notices. */
@media (max-width: 600px) {
  .tabs__list {
    flex-wrap: wrap;
    overflow-x: visible;
  }
}

.tabs__tab[aria-selected="true"] {
  color: var(--brand-primary);
  border-bottom-color: var(--brand-primary);
}

/* A count/status chip on a tab label ("Inverters 2"). */
.tabs__count {
  margin-left: var(--space-2);
  padding: 1px 7px;
  border-radius: var(--radius-pill);
  background: var(--neutral-100);
  color: var(--text-tertiary);
  font: var(--text-caption);
}

.tabs__panel[hidden] {
  display: none;
}

/* ---------- Profile page ---------- */

.profile-grid {
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* A kv-list inside a panel: the panel already draws the border, so the
   list drops its own and its bottom margin. */
.kv-list--flush {
  border: none;
  border-radius: 0;
  margin: 0;
}

.kv-list--flush .kv-row {
  padding-left: 0;
  padding-right: 0;
}

.email-list {
  display: flex;
  flex-direction: column;
}

/* flex-wrap so the Verified/Primary badges drop under a long address on
   a narrow screen instead of pushing the row (and the page) wider than
   the viewport. */
.email-list__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--border-subtle);
  cursor: pointer;
}

.email-list__row:first-child {
  border-top: none;
}

.email-list__address {
  font: var(--text-body-md);
  min-width: 0;
  overflow-wrap: anywhere;
}

/* A form value shown as plain text because it can't be edited here
   (billing info's COC number/country) - sits where the input would. */
.field-readonly {
  margin: 0;
  padding: 10px 0;
  font: var(--text-body-md);
  color: var(--text-primary);
}

/* ---------- Breadcrumbs ---------- */

/* Above the page title on every app page that sits more than one level
   deep (a device, an inverter, a production site) - a flat list page
   (Devices, Production sites) has nothing to show a trail *to*, so it
   skips this entirely rather than rendering a single, redundant crumb
   that just repeats the H1. */
.breadcrumbs {
  margin-bottom: var(--space-3);
}

.breadcrumbs ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  list-style: none;
  margin: 0;
  padding: 0;
  font: var(--text-body-sm);
  color: var(--text-tertiary);
}

.breadcrumbs li {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.breadcrumbs li:not(:last-child)::after {
  content: "/";
  color: var(--border-default);
}

.breadcrumbs a {
  color: var(--text-tertiary);
}

.breadcrumbs a:hover {
  color: var(--brand-primary);
}

.breadcrumbs li[aria-current="page"] {
  color: var(--text-secondary);
  font-weight: 600;
}

/* ---------- Stat strip (compact multi-value summary above a table) ---------- */

/* A quieter, denser cousin of .kpi-grid - the Inverters tab's "3
   inverters, 82.5 kWp AC, 60.0 kWp DC" line above the list, shown only
   once there's more than one row to total up (a single inverter's own
   numbers already read straight off its own row). Not another
   .kpi-grid: that one is the page's hero dashboard, this is a small
   aside next to a table it's summarizing. */
.stat-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--bg-surface-sunken);
  border-radius: var(--radius-md);
}

.stat-strip__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.stat-strip__item dt {
  font: var(--text-caption);
  color: var(--text-tertiary);
}

.stat-strip__item dd {
  margin: 0;
  font: var(--text-body-md);
  font-weight: 600;
  font-feature-settings: "tnum" 1;
}

/* A trailing affordance on a .list-group-item that otherwise has no
   visible cue that the row is clickable (badges alone read as status,
   not as a link) - a plain chevron, not another pencil, since the row
   navigates rather than edits in place. */
.list-group-item .icon-chevron {
  color: var(--text-tertiary);
  flex-shrink: 0;
}
