/* Workspace chrome for the portal and the administration area. */

.layout { display: grid; grid-template-columns: var(--rail) 1fr; min-height: 100dvh; }

.rail {
  position: sticky;
  top: 0;
  height: 100dvh;
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  padding: 20px 16px 16px;
  gap: 20px;
  overflow-y: auto;
  background: var(--bg);
}

/* The brand row, composed the way brinsolutions.com composes its own header:
 * the mark as artwork beside a wordmark set live in the display serif, rather
 * than one flattened image. Their mark is 36px and the wordmark is Fraunces at
 * 20px/500 with tight tracking; both are matched here.
 *
 * 36px is not arbitrary. The mark's strokes are 2.8% of its width, so at 28px
 * they fall under one pixel and the open frame breaks into fragments. At 36px a
 * stroke is a clean 1px, which is why the site uses that size. */
.brand-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.brand-word {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 500;
  letter-spacing: var(--ls-display);
  color: var(--ink);
  white-space: nowrap;
}

.brand-word .product { color: var(--ink-3); }

.brand-row.compact { gap: 10px; }
.brand-row.compact .brand-word { font-size: 17px; }

.rail-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 14px;
  letter-spacing: var(--ls-tight);
}

.rail-brand b { font-weight: 600; color: var(--ink); }
.rail-brand span { color: var(--ink-3); }

/* The profile row in the rail. A link, so it reads as somewhere to go. */
.identity {
  display: flex;
  align-items: center;
  gap: 12px;
}

.identity-link {
  padding: 7px 9px;
  margin: -7px -9px;
  border-radius: var(--r-3);
  transition: background var(--dur) var(--ease);
}

.identity-link:hover { background: var(--surface-hover); }
.identity-link.on { background: var(--accent-wash); }

.identity-photo {
  flex: none;
  width: 42px;
  height: 42px;
  border-radius: var(--r-pill);
  object-fit: cover;
  border: 1px solid var(--line-strong);
}

.identity-chevron {
  flex: none;
  color: var(--ink-4);
  transition: transform var(--dur) var(--ease), color var(--dur) var(--ease);
}

.identity-link:hover .identity-chevron {
  color: var(--ink-2);
  transform: translateX(2px);
}
.identity-name {
  display: block;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.identity-org {
  display: block;
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--accent);
}

html[data-theme="light"] .identity-org { color: var(--accent-soft); }

.nav { display: grid; gap: 2px; }

.nav a,
.nav button:not(.theme-btn) {
  display: flex;
  align-items: center;
  gap: 11px;
  white-space: nowrap;
  padding: 9px 11px;
  border-radius: var(--r-3);
  font-size: 13px;
  color: var(--ink-3);
  width: 100%;
  text-align: left;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}

.nav a:hover, .nav button:not(.theme-btn):hover { background: var(--surface-hover); color: var(--ink); }

/* The views inside Activity, shown only while you are in that section. Indented
 * under their parent and set quieter, so the rail still reads as four places. */
.nav-sub {
  display: grid;
  gap: 1px;
  margin: 2px 0 2px 28px;
  padding-left: 11px;
  border-left: 1px solid var(--line);
}

.nav-sub a {
  padding: 6px 11px;
  border-radius: var(--r-2);
  font-size: 12.5px;
  color: var(--ink-3);
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}

.nav-sub a:hover { background: var(--surface-hover); color: var(--ink); }
.nav-sub a.on { color: var(--accent); font-weight: 500; background: transparent; }

/* Configured once, revisited rarely. Present, but not competing with the four. */
.nav-quiet a { font-size: 12.5px; color: var(--ink-4); }
.nav-quiet a:hover { color: var(--ink-2); }
.nav-quiet .ico { opacity: 0.75; }

.nav a.on {
  background: var(--accent-wash);
  color: var(--accent);
  font-weight: 500;
}

.nav .ico { flex: none; display: grid; place-items: center; width: 17px; height: 17px; }
.nav .n {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  color: var(--ink-4);
  font-variant-numeric: tabular-nums;
}
.nav a.on .n { color: var(--accent); }
.nav .alarm-dot {
  margin-left: auto;
  width: 5px;
  height: 5px;
  border-radius: 99px;
  background: var(--alarm);
  flex: none;
}

.rail-section { display: flex; align-items: center; gap: 8px; padding: 0 11px; margin-bottom: 10px; }
.rail-rule { width: 12px; height: 1px; background: var(--line-strong); flex: none; }
.rail-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-4);
}
.rail-count {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  color: var(--ink-4);
}

.client-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 11px;
  border-radius: var(--r-3);
}
/* Scoped to the anchor. The rail's own copy is a status block now, not a
   link, and a hover highlight on it would offer a click that does nothing. */
a.client-row:hover { background: var(--surface-hover); }
.client-name { display: block; font-size: 13px; font-weight: 500; color: var(--ink); }
.client-meta { display: block; margin-top: 2px; font-size: 11px; color: var(--ink-3); }

.rail-foot {
  margin-top: auto;
  display: grid;
  gap: 2px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.foot-row { display: flex; align-items: center; gap: 8px; }
.foot-row form { flex: 1; min-width: 0; }

.theme-btn {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  flex: none;
  border-radius: 99px;
  border: 1px solid var(--line-strong);
  color: var(--ink-3);
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.theme-btn:hover { color: var(--ink); background: var(--surface-hover); }

/* The floating variant, for the centred-card pages that have no chrome to hang
 * it on. Slightly larger than the one in the rail, because out here it is a
 * standalone target rather than part of a row, and opaque so it stays readable
 * over whatever it happens to sit above. */
.theme-float {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 40;
}

.theme-float .theme-btn {
  width: 34px;
  height: 34px;
  background: var(--surface-2);
  box-shadow: var(--shadow-elev);
}

.theme-float .theme-btn:hover { background: var(--surface-hover); }

/* In the public header bar it sits among the buttons, so it matches their
 * height rather than the rail's tighter 28px. */
.pub-bar .theme-btn { width: 30px; height: 30px; }

/* --- Main column --------------------------------------------------------- */

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

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 22px;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 13px;
  border-radius: 99px;
  border: 1px solid var(--line-strong);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.chip:hover { background: var(--surface-hover); }
.chip .d { width: 6px; height: 6px; border-radius: 99px; flex: none; }
.chip .d.live {
  background: var(--good);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--good) 22%, transparent);
}
.chip .d.off { background: var(--signal); }

.chip-sans {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  text-transform: none;
  letter-spacing: var(--ls-body);
}

.chip .badge {
  display: grid;
  place-items: center;
  min-width: 19px;
  height: 19px;
  padding: 0 6px;
  border-radius: 99px;
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  color: var(--ink-2);
}
.chip .badge.hot { background: var(--alarm); color: var(--bg); }

.main {
  flex: 1;
  width: 100%;
  max-width: calc(var(--content-max) + 44px);
  margin: 0 auto;
  /* 18px, not 6. The 6 assumed a 62px top bar above it; with the bar gone the
   * first heading sat almost against the top edge of the window. */
  padding: 18px 22px 70px;
}

.page-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 13px; padding: 20px 0 20px; }
.page-title {
  font-family: var(--font-display);
  font-size: 25px;
  font-weight: 500;
  letter-spacing: var(--ls-display);
  color: var(--ink);
}
.page-blurb { margin-top: 5px; font-size: 13px; color: var(--ink-3); max-width: 64ch; }
.greeting {
  font-family: var(--font-display);
  font-size: 27px;
  font-weight: 500;
  letter-spacing: var(--ls-display);
}

/* An operator area should not look like a client's own workspace. */
.ops-band {
  border-bottom: 1px solid color-mix(in srgb, var(--signal) 34%, transparent);
  background: color-mix(in srgb, var(--signal) 9%, transparent);
  padding: 7px 22px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--signal);
  display: flex;
  align-items: center;
  gap: 9px;
}

@media (max-width: 900px) {
  .layout { grid-template-columns: 1fr; }
  .rail {
    position: static;
    height: auto;
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }
  .main { padding: 6px 15px 70px; }
  .topbar { padding: 0 15px; }
}

/* --- Centred card pages (auth, receipts) --------------------------------- */

.centre {
  min-height: 100dvh;
  display: grid;
  /* `place-items` alone was the bug behind "the Home button is nowhere near the
     card". It centres each item inside its own track and says nothing about the
     tracks, which are auto-sized and therefore stretch to fill a tall viewport:
     the back bar got half the spare height to itself and its little pill floated
     in the middle of it, a hundred pixels above the card.

     `align-content: center` sizes the tracks to their content and centres the
     pair, so the only space between them is the back bar's own 8px margin. */
  align-content: center;
  justify-items: center;
  align-items: center;
  gap: 0;
  padding: 46px 22px;
}

/* 430px rather than 400. Sign-in is the narrowest thing in the product and the
 * one page where a person is typing rather than reading, and at 400 the
 * helper text under the fields was wrapping to three lines. */
.card {
  width: 100%;
  max-width: 430px;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 30px 30px 26px;
}

.card-wide { max-width: 620px; }

.card-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 24px; }

/* The sign-in cards lead with the company lockup and name the product beneath
 * it, so the hierarchy reads Brin Solutions first, Front Desk second. */
.card-brand.stacked {
  flex-direction: column;
  gap: 10px;
  margin-bottom: 28px;
}

.brand-lockup {
  display: grid;
  justify-items: center;
}

.brand-lockup-art {
  aspect-ratio: 366 / 498;
  background: url("../brand/lockup-frontdesk.png") center / contain no-repeat;
}

/* Set where SOLUTIONS was printed, in the wordmark's own colour. The trailing
 * letter-space would otherwise push the word visibly left of centre, so it is
 * given back as an indent. */
/* Qualified by the parent on purpose: the sign-in card styles its own spans
 * with `.card-brand span`, which outranks a lone class and would otherwise
 * repaint this in body-text grey. */
.brand-lockup .brand-lockup-word {
  font-family: var(--font-sans);
  font-weight: 300;
  letter-spacing: 0.26em;
  text-indent: 0.26em;
  text-transform: uppercase;
  color: #31c0df;
  white-space: nowrap;
}

/* The wordmark's printed cyan is a dark-theme colour. On warm paper it
 * measures 2.02:1 and reads as a watermark rather than a logo, so the light
 * theme takes a darker step of the same hue: 5.3:1, and still recognisably
 * the mark's own colour rather than a substitute for it.
 *
 * A logotype is exempt from the contrast rules. This is not changed to satisfy
 * a rule, it is changed because it looked broken. */
html:not([data-theme="dark"]) .brand-lockup .brand-lockup-word { color: #177087; }

.card-brand-product {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-3);
}
.card-brand span { font-size: 13px; color: var(--ink-3); }
.card-brand b { font-weight: 500; color: var(--ink-2); }

.card-title {
  font-family: var(--font-display);
  font-size: 25px;
  font-weight: 500;
  letter-spacing: var(--ls-display);
  margin-bottom: 8px;
}
.card-blurb { font-size: 13px; color: var(--ink-2); margin-bottom: 22px; }

.card-links { margin-top: 17px; display: grid; gap: 8px; justify-items: center; }
/* Picking a plan before the account exists. Rows rather than the three wide
 * columns the public page uses, because this sits inside a sign-in card. */
.plan-pick {
  display: grid;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  overflow: hidden;
}

.plan-pick-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 15px;
  background: var(--surface-1);
  transition: background var(--dur) var(--ease);
}

.plan-pick-row:hover { background: var(--surface-hover); }
.plan-pick-name { display: block; font-size: 14px; font-weight: 600; color: var(--ink); }
.plan-pick-note { display: block; margin-top: 3px; font-size: 12px; color: var(--ink-3); }
.plan-pick-price { flex: none; text-align: right; }
.plan-pick-amount {
  display: block;
  font-family: var(--font-mono);
  font-size: 17px;
  font-weight: 600;
  color: var(--ink);
}
.plan-pick-per { display: block; font-size: 10px; color: var(--ink-4); }

.plan-chosen {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 15px;
  border: 1px solid var(--accent);
  background: var(--accent-wash);
  border-radius: var(--r-3);
}

.plan-chosen-name {
  display: block;
  margin-top: 4px;
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 500;
  letter-spacing: var(--ls-display);
  color: var(--ink);
}
.plan-chosen-note { display: block; margin-top: 2px; font-size: 12px; color: var(--ink-3); }

.plan-change {
  display: inline-block;
  /* Space below as well as above. It used to sit under the panel listing the
     chosen plan, so 9px above it was the whole spacing it needed. With that
     panel gone, which is Ramoel's point 3, it is the last thing before "Your
     name" and read as part of the form: an underlined line of text hanging
     immediately over the first label. */
  margin: 2px 0 20px;
  font-size: 12px;
  color: var(--ink-3);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.plan-change:hover { color: var(--ink); }

.card-links a { font-size: 12px; text-decoration: underline; text-underline-offset: 2px; }
.card-links a:hover { color: var(--accent); }
.card-links .muted { font-size: 12px; color: var(--ink-3); }

.card-notice {
  margin-top: 20px;
  padding-top: 15px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--ink-4);
  text-align: center;
}

/* --- Public pages -------------------------------------------------------- */

/* The public pages carry the site's mesh and grid. Both are fixed to the
 * viewport rather than to the document: a mesh that scrolls with a long page
 * tiles visibly and gives away that it is an image, and the site's does not
 * move either. */
.pub {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  position: relative;
  isolation: isolate;
}

.pub::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--mesh-page), var(--grid-faint);
  background-size: auto, auto, auto, var(--grid-cell), var(--grid-cell);
  opacity: 0.85;
}

/* The grid is texture, and texture at full strength behind a form is noise. */
.pub:has(form)::before { opacity: 0.5; }

/* The public header, and it stays on screen.

   Sticky rather than static, following brinfrontdesk.com. The page is long and
   the decision to buy is made somewhere in the middle of it; a header that left
   the screen four sections ago means scrolling back to the top to act on it.

   The translucent ground and the blur are the treatment the portal's own
   `.topbar` already uses, so the two halves of the product behave the same way.
   Below the tour's veil at 90 and below any dialog, which is in the browser's
   own top layer: a header floating over a modal would be the one thing worse
   than a header that scrolls away. */
.pub-bar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 20px;
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

/* The section links. Deliberately plain: they are navigation, and a row of
   four bordered controls beside two real buttons would leave nothing looking
   like the action. */
/* Between the two spacers, so it is the middle of the bar rather than the
   middle of the page. Its own margin would fight them. */
.bar-nav {
  display: flex;
  align-items: center;
  gap: 22px;
}

.bar-nav a {
  font-size: 13px;
  color: var(--ink-3);
  text-decoration: none;
  transition: color var(--dur) var(--ease);
}

.bar-nav a:hover { color: var(--ink); }

/* Under 900px five links and two buttons cannot share a line. The brand, the
   theme control and the action stay, which is what a phone needs from a header;
   every section is still reachable by scrolling, which is what a phone does. */
@media (max-width: 900px) {
  .bar-nav { display: none; }
}

@media (max-width: 560px) {
  .bar-signin { display: none; }
}

/* A jump to a section must not land under the header that is covering it.
   Set on the sections rather than as a global rule, because it is the sticky
   bar's height plus a breath, and only these are jumped to. */
[id="who"], [id="how"], [id="calls"], [id="control"], [id="limits"], [id="pricing"] {
  scroll-margin-top: 78px;
}

/* A fading rule rather than a border, so the bar sits on the page instead of
 * being boxed off from it. Same treatment as the section dividers below. */
.pub-bar::after,
.pub-foot::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--rule-fade);
}

.pub-bar::after { bottom: 0; }

/* The header's links scroll rather than jump, which is what says the page moved
   rather than that a new one arrived.

   Honoured only where motion is welcome. `prefers-reduced-motion` exists
   because animated scrolling makes some people ill, and a marketing flourish is
   not worth that. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
.pub-foot::before { top: 0; }

.pub-bar .note {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-4);
}

.pub-wrap {
  flex: 1;
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  padding: 26px 20px 64px;
  display: grid;
  gap: 17px;
  align-content: start;
}

.pub-wide { max-width: 960px; }

/* The landing page runs wider than the rest, because its hero is two columns
 * and 960px would leave neither of them room to breathe. 1152px is the
 * marketing site's own container. */
.pub-hero { max-width: var(--content-max); }

/* Sections separated by one value and nothing else, which is what the site
 * does. One value used everywhere is the whole of what "evenly separated"
 * means, so this is the only spacing rule the landing page has.
 *
 * The whole gap is margin, and the rule is pulled up into the middle of it.
 * Splitting it into margin plus padding read the same on an unstyled section
 * and broke every one that had a surface of its own: .doors paints its own
 * background and clips to a radius, so padding landed as an empty band inside
 * the card rather than as space above it.
 *
 * Centring the rule in the gap also matters. Flush against the section above,
 * it reads as that section's underline rather than as a divider between two. */
/* Its own class rather than riding on .pub-hero, which only ever meant "wide".
 * The demo page is also .pub-hero and sets its own tighter gap inline, so the
 * rhythm landed there too and stacked on top of it. A page that separates
 * marketing sections and a page that lays out a working call panel want
 * different spacing, and one class cannot mean both. */
.pub-rhythm { gap: 0; }

.pub-rhythm > * + * {
  position: relative;
  margin-top: var(--section-gap);
}

.pub-rhythm > * + *::before {
  content: "";
  position: absolute;
  top: calc(var(--section-gap) / -2);
  left: 0;
  right: 0;
  height: 1px;
  background: var(--rule-fade);
}

@media (min-width: 900px) {
  :root { --section-gap: 96px; }
}

/* The footer, once it carries who and where as well as a line of copy.

   Centred and stacked rather than a row of columns: there are seven links and
   three lines of identity, which is a paragraph's worth of content, and three
   columns of two items each reads as a site map for a site with four pages. */
.pub-foot {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 13px;
  padding: 26px 20px 34px;
  text-align: center;
  font-size: 11px;
  color: var(--ink-4);
}

.pub-foot a { color: var(--ink-3); text-decoration: underline; text-underline-offset: 2px; }

.foot-who { display: grid; gap: 3px; }

/* The product name, in the display face at body size. It is the one line in
   here that names the thing somebody is looking at. */
.foot-product {
  font-family: var(--font-display);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: var(--ls-display);
  color: var(--ink-2);
}

/* <address> is italic by default in every browser, which reads as a quotation
   rather than as a postal address. */
.foot-address { font-style: normal; }

.foot-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 7px 16px;
}

/* The sentence a page adds above the footer, on the page where it matters. */
.foot-note {
  margin: 0 auto;
  padding: 0 20px;
  max-width: 68ch;
  text-align: center;
  font-size: 11px;
  line-height: 1.6;
  color: var(--ink-4);
}

/* Both block, so the descriptor sits under the name rather than beside it.
 *
 * They are two spans, and `.biz-desc` has carried `margin-top` since it was
 * written, which says plainly that under was always the intent. A vertical
 * margin does nothing on an inline box, so the two ran together on one line
 * and the practice's name and its category read as one long heading. */
.biz-name {
  display: block;
  font-family: var(--font-display);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: var(--ls-display);
}
.biz-desc {
  display: block;
  margin-top: 4px;
  font-size: 13px;
  color: var(--ink-3);
}

.facts { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }

.fact {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 10px;
  border-radius: var(--r-2);
  background: var(--surface-1);
  border: 1px solid var(--line);
  font-size: 12px;
  color: var(--ink-2);
}

.thread { padding: 15px 17px; display: grid; gap: 13px; max-height: 440px; overflow-y: auto; }
.msg { display: grid; gap: 5px; max-width: 92%; }
.msg.mine { justify-self: end; justify-items: end; }

.prov {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-4);
}
.prov .d { width: 4px; height: 4px; border-radius: 99px; flex: none; }

.composer { display: flex; gap: 8px; padding: 13px 17px; border-top: 1px solid var(--line); }
.suggests { display: flex; flex-wrap: wrap; gap: 7px; padding: 0 17px 15px; }

.suggest {
  padding: 7px 11px;
  border-radius: 99px;
  border: 1px solid var(--line);
  font-size: 12px;
  color: var(--ink-2);
  text-align: left;
}
.suggest:hover { background: var(--surface-hover); border-color: var(--line-strong); color: var(--ink); }

/* Hairlines drawn by the gap showing the container's own background through,
 * which is why the gap is 1px and the background is a line colour.
 *
 * The container deliberately does *not* clip. It used to, so its radius would
 * cut the children's square corners, but that also cut the fading rule sitting
 * in the gap above it and the first divider on the landing page went missing.
 * The corners belong to the children instead, and each child clips its own
 * corner spark, which is the only thing that ever needed clipping. */
.doors {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
}

/* Inset by the container's border, or the child's curve sits a pixel proud of
 * it and reads as a misprint. */
.door { --r-door: calc(var(--r-card) - 1px); overflow: hidden; }
.door:first-child { border-radius: var(--r-door) 0 0 var(--r-door); }
.door:last-child { border-radius: 0 var(--r-door) var(--r-door) 0; }
/* One door, when the demo is off, keeps all four. */
.door:only-child { border-radius: var(--r-door); }

/* A door's picture.
 *
 * Fixed ratio and cropped to fill, so two images of different shapes still
 * produce two doors of the same height. The alt text is empty on purpose:
 * these are decorative, the heading beside them already says what the panel
 * is, and a screen reader announcing a filename would be noise.
 */
.door-media {
  display: block;
  margin: -22px -22px 14px;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  /* surface-1, not surface-2, and the same way round in both themes: the
   * drawing sets its own cards in surface-2, so the ground has to be the step
   * below them or the card edges disappear. */
  background: var(--surface-1);
  border-bottom: 1px solid var(--line);
}

/* --- The door artwork ----------------------------------------------------
 *
 * Both panels were dark JPEGs: a neon illustration and a screenshot of the
 * portal in dark mode. On warm paper they read as two navy holes punched in
 * the page, and being raster they could not follow the theme, so the light
 * theme had dark artwork in it and always would have.
 *
 * They are inline SVG now, and every colour below is a token. That is the
 * whole point: there is one drawing, it is warm on paper and dark on dark, and
 * no second asset exists to fall out of step with the first. It also cannot go
 * stale the way a screenshot does when the product moves.
 *
 * Nothing here is announced. The heading beside each panel already says what
 * it is, so the SVG is aria-hidden and these are shapes, not information.
 */
.door-art {
  display: block;
  width: 100%;
  height: 100%;
}

/* Panels and windows. */
.door-art .art-card {
  fill: var(--surface-2);
  stroke: var(--line-strong);
  stroke-width: 1;
}

/* The caller, ringing. The arcs are the quietest thing in the frame on
 * purpose: they are atmosphere, and at full strength they became the subject. */
.door-art .art-ring {
  fill: none;
  stroke: var(--line-strong);
  stroke-width: 1;
}

.door-art .art-disc { fill: var(--accent-dim); }
.door-art .art-glyph {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* What the caller said. The brand gradient is not used here: it is reserved
 * for things a couple of dozen pixels across, and this is a third of the
 * frame. Flat accent, which is what the rule asks for. */
.door-art .art-wave rect { fill: var(--accent); }
.door-art .art-wave rect:nth-child(even) { fill: var(--accent-soft); }

/* The approved answer, and the tick that says it was approved. */
.door-art .art-tick-disc { fill: color-mix(in srgb, var(--good) 16%, transparent); }
.door-art .art-tick {
  fill: none;
  stroke: var(--good);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Standing in for text. Deliberately not --ink: these are bars, and at
 * ink weight they read as a paragraph somebody is meant to try to focus on. */
.door-art .art-bar,
.door-art .art-bar rect { fill: var(--ink-4); }
.door-art .art-bar-faint { fill: var(--line-strong); }
.door-art .art-dot circle { fill: var(--line-strong); }
.door-art .art-avatar { fill: var(--accent-dim); }

/* --- The dashboard likeness ----------------------------------------------
 *
 * Real words, at a size somebody can actually read. Grey bars standing in for
 * text tell a prospect nothing about what they are signing up for, and this
 * panel's whole job is to answer "what do I get". The rail, the "Line live"
 * chip, the three saved-customer figures and the call list are all really on
 * that page, in that order.
 *
 * Text in the interface face rather than the display serif, because that is
 * what the portal uses, and a likeness that sets its headings in the wrong
 * family stops being a likeness.
 */
.door-art text {
  font-family: var(--font-sans);
  fill: var(--ink-3);
}

.door-art .art-h1 {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.02em;
  fill: var(--ink);
}

.door-art .art-t-strong { font-size: 8.5px; font-weight: 600; fill: var(--ink-2); }
.door-art .art-t-dim    { font-size: 7.5px; fill: var(--ink-3); }
.door-art .art-t-row    { font-size: 8px; font-weight: 500; fill: var(--ink-2); }
.door-art .art-t-figure { font-size: 13px; font-weight: 600; fill: var(--ink); }
.door-art .art-t-chip   { font-size: 7.5px; font-weight: 500; fill: var(--ink-2); }

/* The rail's current page, as a quiet wash rather than a floating pill. */
.door-art .art-nav-on { fill: var(--accent-dim); }
.door-art .art-t-on   { font-size: 7.5px; font-weight: 600; fill: var(--accent-strong); }

/* The real mark rather than a coloured square. It is the one place the
 * saturated brand gradient belongs by the rule that governs it: the artwork is
 * a dozen or so units across, and the same asset is what the portal's own rail
 * shows, so the likeness cannot drift from the product if the logo changes. */
.door-art .art-mark { opacity: 1; }

.door-art .art-t-avatar {
  font-size: 6.5px;
  font-weight: 600;
  text-anchor: middle;
  fill: var(--accent-strong);
}

.door-art .art-tile {
  fill: var(--surface-1);
  stroke: var(--line);
  stroke-width: 1;
}

.door-art .art-chip {
  fill: var(--surface-1);
  stroke: var(--line-strong);
  stroke-width: 1;
}

.door-art .art-live { fill: var(--good); }

/* Status text sits on its own filled wash, so it takes the strong step of the
 * same hue rather than the wash colour. Colour is never the only signal here:
 * every one of them is a word. */
.door-art .art-t-status { font-size: 7.5px; font-weight: 600; }
.door-art .art-t-status-good  { fill: var(--good); }
.door-art .art-t-status-warn  { fill: var(--warn); }
.door-art .art-t-status-quiet { fill: var(--ink-3); }

.door-art .art-rule {
  stroke: var(--line);
  stroke-width: 1;
}

/* One status per row, in the product's own semantic colours, so the miniature
 * agrees with the real call list rather than inventing a palette for itself. */
.door-art .art-status-good  { fill: color-mix(in srgb, var(--good) 22%, transparent); }
.door-art .art-status-warn  { fill: color-mix(in srgb, var(--warn) 22%, transparent); }
.door-art .art-status-quiet { fill: var(--surface-3); }

/* A whisper of movement on hover, matching the door's own wash. */
.door-art .art-disc,
.door-art .art-wave rect { transition: opacity var(--dur) var(--ease); }
.door:hover .art-wave rect { opacity: 0.82; }

.door {
  background: var(--surface-1);
  /* An edge, because a surface tone alone is not one. --surface-1 on --bg is a
   * three percent step in lightness, which the dark theme can carry and paper
   * cannot: on oat the two read as the same sheet and the door had no shape.
   * The border does the work and the resting shadow keeps it from looking
   * drawn on. */
  border: 1px solid var(--line);
  box-shadow: var(--shadow-card);
  padding: 22px;
  display: grid;
  gap: 8px;
  align-content: start;
  position: relative;
  isolation: isolate;
}

/* The wash sits under the content and above the surface, so hovering can
 * change the surface without the wash having to be restated. */
.door::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--mesh-card);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}

/* The corner spark, which is the site's own card detail: a radial anchored in
 * the top right corner and clipped by it. */
.door::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  width: 96px;
  height: 96px;
  z-index: -1;
  pointer-events: none;
  background: var(--corner-spark);
}

.door:hover { background: var(--surface-hover); }
.door:hover::before { opacity: 1; }
.door .who {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  /* ink-3 rather than ink-4. This is a real label at 10px in a tracked
   * uppercase mono, which is already the hardest thing on the page to read;
   * the faintest step measured 3.87:1 against the card and is not allowed to
   * carry text people are meant to act on. */
  color: var(--ink-3);
}
.door h3 { font-size: 18px; }
.door p { font-size: 13px; color: var(--ink-3); }
.door .go { margin-top: 5px; font-size: 13px; font-weight: 600; letter-spacing: var(--ls-wide); color: var(--accent); }

@media (max-width: 760px) {
  .doors { grid-template-columns: 1fr; }
  /* Stacked, the corners are top and bottom rather than left and right. */
  .door:first-child { border-radius: var(--r-door) var(--r-door) 0 0; }
  .door:last-child { border-radius: 0 0 var(--r-door) var(--r-door); }
  .door:only-child { border-radius: var(--r-door); }
}

/* Copy beside the film. The right column takes slightly more than half, since a
 * 16:9 frame needs width to read, and the two are centred against each other so
 * neither hangs off the bottom of the other. */
.hero-split {
  display: grid;
  grid-template-columns: 1fr 1.15fr;
  gap: 48px;
  align-items: center;
}

/* Inside the split the film fills its column; the 720px cap is for when it
 * stands alone. */
.hero-split .video-panel { max-width: none; }

/* One column on anything narrow. The film goes first: on a phone it is the more
 * persuasive half, and the headline still sits above the fold beneath it. */
@media (max-width: 900px) {
  .hero-split {
    grid-template-columns: 1fr;
    gap: 28px;
  }
  .hero-split .video-panel { order: -1; }
  .hero-title { max-width: none; }
}

.hero-title {
  font-family: var(--font-display);
  font-size: clamp(30px, 5.4vw, 46px);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: var(--ls-display);
  max-width: 22ch;
}
/* The promotional video. 16:9, capped so it never dominates the fold, and
 * centred with the rest of the page's rhythm. */
.video-panel {
  display: grid;
  gap: 10px;
  width: 100%;
  max-width: 720px;
}

.video-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--surface-1);
  /* The film is the one thing on the landing page that is meant to sit above
   * it, so it takes the floating level rather than the resting one. On dark
   * this is the heavier black stack; on light it is the three warm layers. */
  box-shadow: var(--shadow-float);
}

.video-frame video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #000;
}

/* The placeholder state, so an empty panel still reads as deliberate. */
.video-empty {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 7px;
  text-align: center;
  padding: 20px;
  background:
    radial-gradient(120% 90% at 50% 0%, var(--accent-wash), transparent 70%),
    var(--surface-1);
}

.video-play {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-bottom: 4px;
  border-radius: var(--r-pill);
  background: var(--brand-gradient);
  color: var(--ink-on-accent);
  box-shadow: var(--glow);
}

.video-empty-title {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 500;
  letter-spacing: var(--ls-display);
  color: var(--ink);
}

.video-empty-note { font-size: 12px; color: var(--ink-3); }
.video-caption { font-size: 12.5px; color: var(--ink-3); max-width: 62ch; }

/* 16px, not 15. This is the first paragraph a stranger reads, and it is the
 * one place on the site where saving a pixel buys nothing. */
.hero-lede { font-size: 16px; line-height: 1.55; color: var(--ink-2); max-width: 56ch; }

/* The picture and its controls, side by side so the change is visible next to
 * what it is changing. */
.profile-photo-row {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  flex-wrap: wrap;
}

.profile-photo {
  flex: none;
  width: 88px;
  height: 88px;
  border-radius: var(--r-pill);
  object-fit: cover;
  border: 1px solid var(--line-strong);
}

/* --- Documents and the consent dialog ------------------------------------ */

.doc-card {
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent);
  border-radius: var(--r-3);
  background: var(--surface-1);
  display: grid;
  gap: 9px;
}

.doc-name { font-size: 13px; font-weight: 600; }
.doc-meta { font-size: 12px; color: var(--ink-3); }

/* What a caller could actually hear, folded away until asked for. Showing the
 * passages is the only way an owner can check the assistant will not read
 * something odd out of their brochure. */
.doc-passages summary {
  font-size: 12px;
  color: var(--accent);
  cursor: pointer;
  padding: 3px 0;
}

.doc-passages ul {
  display: grid;
  gap: 7px;
  margin-top: 9px;
  padding-left: 0;
}

.doc-passages li {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-2);
  padding: 9px 11px;
  border-left: 2px solid var(--line-strong);
  background: var(--surface-2);
  border-radius: 0 var(--r-2) var(--r-2) 0;
}

.consent {
  width: min(90vw, 520px);
  padding: 24px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-card);
  background: var(--surface-1);
  color: var(--ink);
  box-shadow: var(--shadow-elev);
}

/* Backdrop styling is global, in tokens.css: every dialog blurs
 * what is behind it and holds the page still. */

.consent-title {
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 500;
  letter-spacing: var(--ls-display);
  margin-bottom: 12px;
}

.consent-body {
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-2);
  margin-bottom: 11px;
  max-width: 60ch;
}

.consent-check {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: 16px 0 20px;
  padding: 13px;
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  background: var(--surface-2);
  font-size: 12.5px;
  line-height: 1.5;
  cursor: pointer;
}

.consent-check input { margin-top: 2px; flex: none; }

.consent-actions {
  display: flex;
  gap: 9px;
  justify-content: flex-end;
}

/* --- Saved counts -------------------------------------------------------- */

/* Three periods, side by side, so they read as one answer rather than three
 * unrelated statistics. */
.saved-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
}

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

.saved-card {
  display: grid;
  gap: 3px;
  padding: 18px 20px 20px;
  background: var(--surface-1);
}

.saved-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-4);
}

.saved-number {
  font-family: var(--font-display);
  font-size: 42px;
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: var(--ls-display);
  background: var(--brand-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.saved-unit { font-size: 12px; color: var(--ink-3); }

/* A link inside running prose. */
.inline-link {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* --- The headline figure -------------------------------------------------- */

/* One number, large enough to be the answer to the only question an owner
 * really has, with the definition beside it so it cannot be misread. */
.headline {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 24px;
  align-items: center;
}

@media (max-width: 720px) {
  .headline { grid-template-columns: 1fr; gap: 14px; }
}

.headline-figure {
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: 4px 22px 4px 4px;
  border-right: 1px solid var(--line);
}

@media (max-width: 720px) {
  .headline-figure {
    justify-items: start;
    padding: 0 0 12px;
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }
}

.headline-number {
  font-family: var(--font-display);
  font-size: 60px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--ls-display);
  background: var(--brand-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.headline-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-3);
  text-align: center;
}

@media (max-width: 720px) {
  .headline-label { text-align: left; }
}

.headline-body { display: grid; gap: 7px; min-width: 0; }
.headline-said { font-size: 14px; line-height: 1.55; color: var(--ink); max-width: 62ch; }
.headline-note { font-size: 11.5px; line-height: 1.5; color: var(--ink-4); max-width: 66ch; }

/* --- Back ---------------------------------------------------------------- */

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  align-self: start;
  padding: 6px 13px 6px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface-1);
  font-size: 12.5px;
  font-weight: 500;
  /* --ink-2 rather than the --ink-3 the other quiet controls use: this is
     navigation, small, and sits on a raised surface where --ink-3 measures
     3.8:1. This clears AA in both themes. */
  color: var(--ink-2);
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.back-link:hover {
  color: var(--ink);
  background: var(--surface-hover);
  border-color: var(--line-strong);
}

.back-chevron { transition: transform var(--dur) var(--ease); }
.back-link:hover .back-chevron { transform: translateX(-2px); }

/* The bar sits above a page's own heading, so it needs to clear it without
 * inheriting whatever gap that page happens to use. */
.back-bar { display: flex; margin-bottom: 4px; }

/* On the centred-card pages the control sits above the card and shares its
   width, so it lines up with the card's left edge rather than the viewport's.

   The width was 400px against a card of 430, with a comment claiming they
   matched: somebody widened the card and this stayed where it was, so Home sat
   15px inside the card's left edge, which reads as a control that missed rather
   than one that was placed. The card's own width is the value now, so the two
   cannot disagree again, and the gap is 8px so it reads as belonging to the
   card below it rather than floating above the page. */
.back-bar-card {
  width: 100%;
  max-width: 430px;
  margin: 0 auto 8px;
}

/* The wide card, on the signup page, takes its own width with it. */
.centre:has(.card-wide) .back-bar-card { max-width: 620px; }

/* --- Interactive demo ---------------------------------------------------- */

/* Both halves side by side, because the whole argument is that the caller gets
 * an answer and the clinic gets something to work from, at the same moment. */
.demo-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  align-items: start;
}

@media (max-width: 940px) {
  .demo-split { grid-template-columns: 1fr; }
}

/* The phone. One control, big enough to be the obvious thing to press. */
.dialer {
  display: grid;
  justify-items: center;
  gap: 7px;
  padding: 18px 0 14px;
}

.dial-btn {
  position: relative;
  display: grid;
  place-items: center;
  width: 74px;
  height: 74px;
  border-radius: var(--r-pill);
  background: var(--brand-gradient);
  color: var(--ink-on-accent);
  box-shadow: var(--glow);
  transition: transform var(--dur) var(--ease);
}

.dial-btn:hover { transform: scale(1.04); }
.dialer.on-call .dial-btn { background: var(--alarm); box-shadow: none; }

/* A ring that pulses only while the microphone is actually open, so the visitor
 * can see when it is listening and when it is not. */
.dial-ring {
  position: absolute;
  inset: -6px;
  border-radius: var(--r-pill);
  border: 2px solid var(--accent);
  opacity: 0;
}

.dialer.live .dial-ring { animation: dial-pulse 1.4s ease-out infinite; }

@keyframes dial-pulse {
  0%   { opacity: 0.7; transform: scale(0.94); }
  100% { opacity: 0;   transform: scale(1.18); }
}

.dial-label { font-size: 13px; font-weight: 600; color: var(--ink); }
.dial-hint { font-size: 11px; color: var(--ink-4); text-align: center; max-width: 42ch; }

/* Lines to try. Speaking them is a button, so nobody has to invent a scenario. */
.demo-suggest { display: grid; gap: 6px; }

.demo-suggest-head {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-4);
}

.demo-suggest ul { display: grid; gap: 9px; }

/*
  The question on its own line and the note beneath it.
  
  These were on one baseline with `flex-wrap`, which reads fine for a short
  question and badly for a real one: the note wrapped to the next line and
  landed hard against the left edge, so the list had two competing left margins
  and no two items lined up. A question and its explanation are one item, not a
  row of two things.
*/
.demo-suggest li { display: grid; gap: 2px; justify-items: start; }

.demo-say {
  font-size: 12.5px;
  color: var(--accent);
  text-align: left;
  transition: color var(--dur) var(--ease);
}
.demo-say:hover { color: var(--accent-soft); text-decoration: underline; text-underline-offset: 2px; }
.demo-say-why {
  font-size: 11px;
  line-height: 1.45;
  color: var(--ink-4);
  /* Aligned under the quotation mark rather than under the first letter, so
     the note reads as belonging to the question above it. */
  padding-left: 1px;
}

.demo-thread { display: grid; gap: 10px; max-height: 340px; overflow-y: auto; }

.demo-scenarios { display: grid; gap: 7px; }

.demo-chip {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  background: var(--surface-1);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.demo-chip:hover { background: var(--surface-hover); border-color: var(--line-strong); }
.demo-chip-q { display: block; font-size: 13px; color: var(--ink); }
.demo-chip-why {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  color: var(--ink-4);
}

.demo-turn {
  padding: 10px 13px;
  border-radius: var(--r-3);
  border: 1px solid var(--line);
}

.demo-turn.caller { background: var(--surface-2); }
.demo-turn.assistant { background: var(--accent-wash); border-color: var(--accent-dim); }

.demo-who {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--ink-4);
}

.demo-said { margin-top: 4px; font-size: 13px; line-height: 1.5; color: var(--ink); }
.demo-why { display: block; margin-top: 5px; font-size: 11px; color: var(--ink-3); }

.demo-reset {
  font-size: 12px;
  color: var(--ink-3);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.demo-reset:hover { color: var(--ink); }

/* What the clinic would have found waiting. Styled as a record rather than a
 * message, because that is what it is. */
.demo-ticket {
  padding: 12px 13px;
  border: 1px solid var(--line);
  border-left: 2px solid var(--accent);
  border-radius: var(--r-3);
  background: var(--surface-1);
}

.demo-ticket-subject { margin-top: 7px; font-size: 13px; color: var(--ink); }
.demo-ticket-why { margin-top: 4px; font-size: 11.5px; color: var(--ink-3); }

.demo-channels {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 9px;
}

.demo-channels li {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--ink-3);
  padding: 3px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
}

.demo-summary {
  padding: 11px 13px;
  border-radius: var(--r-3);
  background: var(--surface-2);
}

/* --- Plans --------------------------------------------------------------- */

/* Auto-fit rather than a fixed three, because there is one plan now and a
 * `repeat(3, 1fr)` left it sitting in a third of the page with two empty
 * columns beside it. Auto-fit lets one plan fill the column it is given and
 * still lays several out side by side if a second is ever added. */
.plan-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 13px;
  align-items: start;
}

/* One plan should not stretch the full width of a wide page: a 900px price card
 * reads as a banner. Capped at the width the portal's own single plan uses. */
.plan-grid:has(> .plan:only-child) { max-width: 520px; }

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

/* 20px, the same as `.panel`, because these two sit side by side on the plan
 * page and 19px put every line of one card a pixel above its neighbour. A card
 * is a card whatever is inside it. */
.plan {
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 20px;
  display: grid;
  gap: 13px;
  align-content: start;
}
.plan.on { border-color: var(--accent); background: var(--accent-wash); }
.plan.current { border-color: color-mix(in srgb, var(--good) 45%, transparent); }
.plan-name { font-size: 17px; font-weight: 500; }
.plan-price {
  font-family: var(--font-mono);
  font-size: 26px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.feat { display: grid; grid-template-columns: 14px 1fr; gap: 9px; font-size: 12px; line-height: 1.5; }
.feat.yes { color: var(--ink-2); }
.feat.no { color: var(--ink-4); }
.feat .m { margin-top: 3px; }
.feat.yes .m { color: var(--good); }

/* --- Setup wizard -------------------------------------------------------- */

.wiz-rail { display: flex; gap: 6px; flex-wrap: wrap; }

.wiz-step {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px 7px 9px;
  border-radius: 99px;
  border: 1px solid var(--line);
  font-size: 12px;
  color: var(--ink-3);
}
.wiz-step:hover { color: var(--ink); border-color: var(--line-strong); }
.wiz-step.on {
  background: var(--accent-wash);
  border-color: color-mix(in srgb, var(--accent) 32%, transparent);
  color: var(--accent);
  font-weight: 500;
}
.wiz-num {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  flex: none;
  border-radius: 99px;
  border: 1px solid currentColor;
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
}
.wiz-num.done {
  background: color-mix(in srgb, var(--good) 20%, transparent);
  border-color: color-mix(in srgb, var(--good) 50%, transparent);
  color: var(--good);
}

.item {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  padding: 12px;
  display: grid;
  gap: 10px;
}

.item-head { display: flex; align-items: center; gap: 10px; }
.item-num { font-family: var(--font-mono); font-size: 10px; font-weight: 600; color: var(--ink-4); }

.topic-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 11px;
  border-radius: 99px;
  border: 1px solid var(--line);
  font-size: 12px;
  color: var(--ink-3);
  cursor: pointer;
}
.topic-chip:has(input:checked) {
  background: var(--alarm-wash);
  border-color: color-mix(in srgb, var(--alarm) 40%, transparent);
  color: var(--alarm);
  font-weight: 500;
}
.topic-chip input { position: absolute; opacity: 0; pointer-events: none; }
.topic-chip.locked { opacity: 0.75; cursor: not-allowed; }

/* --- The week, on the hours screen ---------------------------------------- */
/*
   Two faults, one cause each.

   The AM/PM was cut off. `input[type="time"]` is drawn by the browser and its
   meridiem segment is the last thing in the box, so a field narrower than its
   own contents clips exactly that: a client set an opening time and could not
   read whether it said ten in the morning or ten at night. There is no way to
   widen one segment, so the field has to fit all of them, which is 152px with
   the picker icon. `min-width` rather than `width`, because a locale that
   writes a 24 hour clock needs less and one that spells the meridiem needs
   more, and neither should be clipped.

   And the rows did not line up. Seven flex rows of items each sized to their
   own content line up only by luck. These are five grid tracks, declared once
   here, so every day uses the same ones and the columns are columns.
*/
.hours-week { display: grid; gap: 0; }

.hours-row {
  display: grid;
  grid-template-columns: 96px minmax(152px, max-content) auto
                         minmax(152px, max-content) 1fr;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid var(--line);
}

.hours-row:first-child { border-top: 0; }
.hours-day { font-weight: 500; }
.hours-to { text-align: center; }
.hours-time { min-width: 152px; }
.hours-closed { justify-self: end; }

/* A phone cannot hold five columns. Day above, times below it, the toggle
   under those, which is the same information in the order it is read. */
@media (max-width: 620px) {
  .hours-row {
    grid-template-columns: minmax(120px, 1fr) auto minmax(120px, 1fr);
  }
  .hours-day { grid-column: 1 / -1; }
  .hours-closed { grid-column: 1 / -1; justify-self: start; }
  .hours-time { min-width: 0; }
}

/* --- Choosing a time ------------------------------------------------------- */

/* The product's own dropdown, in place of the browser's.
 *
 * `<input type="time">` opens a panel drawn by the browser: on Chrome for
 * Windows a stack of saturated blue tiles that belongs to no design system and
 * cannot be restyled. This is the same control in the product's own tokens.
 *
 * Built by `time-picker.js` around the `<select>` that carries the value. With
 * scripting off none of this exists and the select is the control, which is why
 * every rule here is scoped to `.timefield`: nothing below can affect the
 * fallback. */
.timefield {
  position: relative;
  display: grid;
}

/* The closed state is an `.input`, deliberately, so a time sits in a row of
 * fields and looks like one of them. */
.timefield-control {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--r-3);
  padding: 10px 12px;
  font-family: inherit;
  font-size: 14px;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease);
}

.timefield-control:hover { border-color: var(--line); }
.timefield-control:focus-visible {
  outline: none;
  border-color: var(--accent);
}
.timefield.on .timefield-control { border-color: var(--accent); }

/* Tabular figures so seven rows of times share one column of colons. */
.timefield-value {
  flex: 1;
  min-width: 0;
  font-variant-numeric: tabular-nums;
}

/* The chevron turns over when the panel is open, which is the only animation
 * here and the one that says the control is a dropdown. */
.timefield-arrow {
  flex: none;
  width: 9px;
  height: 9px;
  border-right: 1.5px solid var(--ink-3);
  border-bottom: 1.5px solid var(--ink-3);
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--dur) var(--ease);
}
.timefield.on .timefield-arrow {
  transform: translateY(2px) rotate(225deg);
  border-color: var(--accent);
}

.timefield-panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + 5px);
  left: 0;
  min-width: 100%;
  max-height: 264px;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-elev);
  padding: 5px;
  display: grid;
  gap: 1px;
}

/* `display: grid` above beats the user agent's own `[hidden] { display: none }`,
 * which is author versus UA specificity and not a fight the UA wins. Without
 * this every one of the fourteen panels on the hours screen renders open at
 * once, which is exactly how it first looked. */
.timefield-panel[hidden] { display: none; }

/* Opening upwards when there is no room below. Set by the script, because only
 * it knows where the field ended up on the page. */
.timefield-panel.up {
  top: auto;
  bottom: calc(100% + 5px);
}

.timefield-option {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 0;
  background: transparent;
  border-radius: var(--r-2);
  padding: 7px 10px;
  font-family: inherit;
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
  text-align: left;
  cursor: pointer;
}

.timefield-option:hover { background: var(--surface-hover); color: var(--ink); }

/* Where the keyboard is. A ring rather than a background, so it reads
 * separately from the option that is actually chosen. */
.timefield-option.here {
  outline: 1px solid var(--accent);
  outline-offset: -1px;
  color: var(--ink);
}

.timefield-option[aria-selected="true"] {
  background: var(--accent-dim);
  color: var(--accent);
  font-weight: 500;
}

/* The tick occupies its column whether or not it is drawn, so the times do not
 * shift sideways as the selection moves. */
.timefield-tick {
  flex: none;
  width: 12px;
  text-align: center;
  color: var(--accent);
  opacity: 0;
}
.timefield-option[aria-selected="true"] .timefield-tick { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .timefield-control,
  .timefield-arrow { transition: none; }
}

/* --- The answer library --------------------------------------------------- */

.lib-group {
  display: grid;
  gap: 9px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.lib-group:first-of-type { border-top: 0; padding-top: 0; }

/* See the note on `.qa[hidden]`. A category with no matching answer left in it
 * is hidden by the same script, and stayed on screen as an empty heading. */
.lib-group[hidden] { display: none; }

.lib-title {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 500;
  margin: 0;
}

/* One question, its answer, and everything you can do to it. The left edge
 * carries the state, because that is what an owner scans for. */
/* Two things use this card and only one of them brought its own padding.

   In the portal's answer library the card is a <details> whose `.qa-head` and
   `.qa-body` are padded, so the card itself must not be. The setup wizard uses
   the same card around a plain row and a form, and those are padded by nothing:
   the question, the toggle, the textarea and Save wording all sat flush against
   the border, which is what "misplaced and awkward components" was looking at.

   So the card pads itself, and stands back when it contains a head that is
   already doing it. */
.qa {
  display: grid;
  gap: 9px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-left: 2px solid var(--line-strong);
  border-radius: var(--r-3);
  background: var(--surface-2);
}

.qa:has(> .qa-head) {
  gap: 0;
  padding: 0;
}

/* Restated, because the rule above sets `display` and an author style beats the
 * user agent's own `[hidden] { display: none }`.
 *
 * This is what made the library's three filters do nothing. `library-filter.js`
 * sets `el.hidden = true` on every answer that does not match, which is the
 * correct way to hide one, and every card carried on rendering: In use, Needs
 * review and All all showed the same fifty one rows. The only visible effect
 * was the "no answer matches that" line, which has no `display` of its own and
 * therefore obeyed `[hidden]` perfectly, so the page could say nothing matched
 * while showing everything.
 *
 * `.timefield-panel` learned this the same way, where the symptom was fourteen
 * dropdowns rendering open at once. Any rule here that sets `display` on an
 * element a script hides needs its own `[hidden]` line, and the three that do
 * are the card, the category around it, and the toolbar above both. */
.qa[hidden] { display: none; }

/* The whole collapsed row is the control, so the target is the row rather than
 * a chevron somewhere on it. */
.qa-head {
  display: flex;
  align-items: baseline;
  gap: 9px;
  flex-wrap: wrap;
  padding: 12px 14px;
  cursor: pointer;
  list-style: none;
  min-height: 44px;
}

.qa-head::-webkit-details-marker { display: none; }
.qa-head:hover { background: var(--surface-hover); }

.qa-head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: var(--r-3);
}

.qa-body {
  display: grid;
  gap: 9px;
  padding: 2px 14px 14px;
}

/* Keywords, folded away. It is the field an owner needs least often on the
 * way to the thing they came for, which is the wording itself. */
.qa-advanced > summary {
  font-size: 12.5px;
  color: var(--ink-3);
  cursor: pointer;
  padding: 3px 0;
  list-style: none;
}

.qa-advanced > summary::-webkit-details-marker { display: none; }
.qa-advanced > summary::before { content: "+ "; }
/* The pair to the "+" above. It held U+0091 followed by "2", which is what
   a minus sign turns into when a file makes a round trip through the wrong
   encoding, and it rendered as a stray digit. Written as a CSS escape so it
   cannot happen again. */
.qa-advanced[open] > summary::before { content: "− "; }
.qa-advanced > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qa-advanced[open] > summary { margin-bottom: 6px; }

/* A category, also collapsible, and open when it still has work in it. */
.lib-head {
  display: flex;
  align-items: baseline;
  gap: 9px;
  cursor: pointer;
  list-style: none;
  padding: 4px 0 10px;
  min-height: 40px;
}

.lib-head::-webkit-details-marker { display: none; }
.lib-head:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.lib-body { display: grid; gap: 9px; padding-bottom: 4px; }

/* Search and the three filters. */
.lib-tools {
  display: flex;
  gap: 9px;
  flex-wrap: wrap;
  align-items: center;
  margin-bottom: 16px;
}

/* The same fault, one step earlier, and this half was a promise rather than a
 * bug an owner could see.
 *
 * The toolbar is `hidden` in the markup and revealed by the script, so that a
 * browser with scripting off shows the whole library rather than a search box
 * that does nothing. `display: flex` above defeated that: the search field and
 * the three filters were drawn for everybody, and with no script behind them
 * they were four dead controls. */
.lib-tools[hidden] { display: none; }

.lib-search { flex: 1 1 240px; min-width: 200px; }

.lib-filters { display: flex; gap: 6px; flex-wrap: wrap; }

/* Filters stay capsules. That is what the shape is reserved for now that
 * ordinary buttons are not. */
.lib-filter {
  font: inherit;
  font-size: 12.5px;
  padding: 6px 13px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: var(--surface-2);
  color: var(--ink-2);
  cursor: pointer;
  min-height: 32px;
}

.lib-filter:hover { background: var(--surface-hover); }
.lib-filter[aria-pressed="true"] {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--accent-strong);
  font-weight: 500;
}
.lib-filter:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.lib-empty {
  font-size: 13px;
  color: var(--ink-2);
  padding: 14px 2px;
}

.qa-approved { border-left-color: var(--good); }
.qa-needs_review { border-left-color: var(--warn); }
.qa-disabled { border-left-color: var(--line-strong); opacity: 0.62; }

.qa-q { font-size: 13.5px; font-weight: 600; }

.qa textarea,
.qa input { font-size: 13px; }

/* --- Tabs, on the answers page --------------------------------------------- */

/* Four jobs that were one scroll.
 *
 * The strip is links to sections that exist, so with no JavaScript it is a row
 * of jump links over a page where everything is visible, which is a scroll aid
 * rather than a broken control. `tabs.js` upgrades it.
 */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 18px;
  border-bottom: 1px solid var(--line);
}

.tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 14px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: var(--ls-wide);
  color: var(--ink-3);
  /* The underline is drawn on the tab so it sits on the strip's own border,
   * and it is transparent rather than absent, or every tab would move by a
   * pixel when it became the current one. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.tab:hover { color: var(--ink); }

.tab[aria-selected="true"] {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

/* Only a queue gets a number. `.tab-count` is not `.pill`: a pill is a status
 * and this is a quantity, and at 10px inside a control the pill's own padding
 * makes the tab taller than its neighbours. */
.tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9999px;
  background: var(--surface-3);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}

/* The same 12 percent the warn pill uses, so a count and a status badge of
 * the same colour do not read as two different warnings. */
.tab-count-warn {
  background: color-mix(in srgb, var(--warn) 12%, transparent);
  color: var(--warn);
}

/* **This rule is why the panels can be hidden at all.**
 *
 * A panel wraps several sections and has to space them, so it sets `display`,
 * and `[hidden] { display: none }` comes from the user agent, which any author
 * rule beats. Without the line below every panel renders at once with the
 * attribute set and the tabs do nothing, which is exactly what `.qa` did in the
 * answer library and what `.timefield-panel` did before it. `check_routes`
 * asserts all five.
 */
.tab-panel { display: grid; gap: 18px; }
.tab-panel[hidden] { display: none; }

/* --- What nobody had an answer for ----------------------------------------- */

/* Three columns that agree all the way down the list.
 *
 * `.list-row` is `auto 1fr auto`, and an `auto` track is sized by the content
 * of its own row, so on this list every row disagreed with every other one.
 * Measured on the seeded account at 1440px: the question column was 169px
 * against "Message: John" and 699px against a caller who read a paragraph into
 * the phone, the answer box ran from 607px down to 77px, and the row was 128px
 * tall against 255px. So no two answer boxes began at the same place, and on
 * the widest question the owner was typing into a box two words across with
 * both buttons stacked into towers of one word each.
 *
 * Fractional tracks make the rows agree, and **`minmax(0, ...)` rather than a
 * bare `1fr` is the half that actually fixes it**: `1fr` is `minmax(auto, 1fr)`
 * and that `auto` floor is the content's own minimum, so the longest question
 * would go on setting the track whatever weight it was given.
 *
 * The answer is the wider of the two because it is the column being written
 * in. The question is only read.
 */
/* V2-021. A row that is a label and a status, with nothing between them.
 *
 * `.list-row` is `auto minmax(0, 1fr) auto`, which is right for the three part
 * rows it was written for and wrong for two: the label lands in the first
 * track, which sizes to its own content, and the pill lands in the flexible
 * one, which then absorbs everything left over. Measured on the capture screen
 * before this rule, six pills came out at six widths between 0 and 134px, each
 * floating wherever its own sentence had stopped.
 *
 * The same shape of fix as `.gap-row` below: say which track is meant to give,
 * rather than leaving it to whichever child happened to be first.
 */
.capture-row {
  grid-template-columns: minmax(0, 1fr) max-content;
  align-items: start;
}

.gap-row {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) max-content;
  align-items: start;
  padding-top: 15px;
  padding-bottom: 15px;
}

/* A question is the caller's own recorded words, so it can be a paragraph, one
 * word, or a web address with nothing to break on. It wraps, and it breaks
 * inside a word only when the word alone is wider than the track. */
.gap-row .row-title { overflow-wrap: anywhere; }

/* Never a tower of single words. The tracks above are what guarantee the room
 * for this, which is why the two go together. */
.gap-row .btn { white-space: nowrap; }

/* A textarea is resizable in both directions by default, and dragged sideways
 * it widens its own grid track and takes the whole list's alignment with it.
 * Taller is welcome and costs nothing. */
.gap-row textarea { resize: vertical; }

/* The breakpoint is measured rather than picked, the way `.plan-pair`'s was.
 * The rail and the page padding take 406px and the row's own padding another
 * 40px, and the narrowest the three tracks are still comfortable at is about
 * 240 + 320 + 190 with 28px of gaps. That totals 1224px of viewport, so below
 * 1220px they stack instead, which is the one column a phone was always going
 * to get. */
@media (max-width: 1220px) {
  .gap-row { grid-template-columns: minmax(0, 1fr); }
}

/* --- The guided tour ------------------------------------------------------ */

/* Four panels, not one sheet, and they capture clicks.
 *
 * This used to be a single `.tour-veil` with `pointer-events: none`, on the
 * argument that a tour which traps you is worse than no tour. That argument
 * held while the tour was optional. It is now the way a new client is walked
 * through the product and cannot be skipped, so the page underneath has to be
 * genuinely out of reach: a dimmed page that is still fully clickable teaches
 * somebody to ignore the thing pointing at the control they need.
 *
 * The panels frame the highlighted control on four sides, so the gap between
 * them is a real hole. The client's click reaches the actual control through
 * it; every other click lands on a panel and is swallowed. An SVG mask or a
 * huge box-shadow would dim the same area but would still have to choose
 * between blocking everything and blocking nothing.
 *
 * Geometry is set from script, because it follows an element's rect. Only the
 * colour and the layer belong here. */
.tour-mask {
  position: fixed;
  background: #0a1220;
  opacity: 0.5;
  z-index: 90;
}

/* Written when dark was the only theme. Half-opacity navy over warm paper does
 * not read as "the rest of the page is dimmed", it reads as the lights going
 * out. Light gets a warm grey at the strength the modal backdrop uses, which is
 * the other thing in this product whose job is to push a page back without
 * hiding it. */
html:not([data-theme="dark"]) .tour-mask {
  background: #6b6255;
  opacity: 0.34;
}

/* A ring around whatever is being pointed at, rather than a hole cut in the
 * veil. A hole needs four boxes or an SVG mask and looks worse at every corner
 * radius the product uses. */
.tour-ring {
  position: fixed;
  z-index: 91;
  border: 2px solid var(--accent);
  border-radius: var(--r-3);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent),
    0 0 0 9999px transparent;
  /* The ring sits over the hole, so it must not intercept the click the hole
   * exists to let through. */
  pointer-events: none;
  transition: top var(--dur) var(--ease), left var(--dur) var(--ease),
    width var(--dur) var(--ease), height var(--dur) var(--ease);
}

/* Clicking the mask on a required step does nothing, and doing nothing is
 * indistinguishable from being broken. The ring answers instead, pointing at
 * the one thing that will move this on. */
.tour-ring-nudge {
  animation: tour-nudge 480ms var(--ease);
}

@keyframes tour-nudge {
  0%, 100% { transform: scale(1); }
  35% { transform: scale(1.045); }
  70% { transform: scale(0.995); }
}

@media (prefers-reduced-motion: reduce) {
  .tour-ring-nudge { animation: none; }
}

/* What the client is being waited for, in place of a Next button. */
.tour-prompt {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
}

.tour-prompt::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 99px;
  background: var(--accent);
  animation: tour-pulse 1.6s var(--ease) infinite;
}

@keyframes tour-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.25; }
}

@media (prefers-reduced-motion: reduce) {
  .tour-prompt::before { animation: none; }
}

.tour-box {
  position: fixed;
  z-index: 92;
  width: min(92vw, 340px);
  padding: 17px 18px 16px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-card);
  background: var(--surface-1);
  color: var(--ink);
  box-shadow: var(--shadow-elev);
  display: grid;
  gap: 9px;
}

/* Steps that are about a page rather than a control sit in the middle. */
.tour-centre {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.tour-count {
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 500;
}

.tour-title {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 500;
  letter-spacing: var(--ls-display);
  margin: 0;
}

.tour-body {
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-2);
  margin: 0;
}

.tour-body b { color: var(--ink); font-weight: 600; }

.tour-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  align-items: center;
  margin-top: 3px;
}

.tour-actions .btn-ghost { margin-right: auto; }

/* The thing being pointed at rises above the veil, so it is legible and still
 * clickable while the tour talks about it. */
.tour-running [data-tour] { position: relative; }

@media (max-width: 640px) {
  .tour-box { width: 94vw; left: 3vw !important; }
}

/* --- The setup toggle ----------------------------------------------------- */

/* A real switch, because "on or off" is the whole decision on this screen and
 * a button reading "Approve" makes it sound heavier than it is. */
.toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 11px 4px 4px;
  border: 1px solid var(--line);
  border-radius: 99px;
  background: var(--surface-2);
  cursor: pointer;
  font-size: 12px;
  color: var(--ink-3);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
    color var(--dur) var(--ease);
}

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

.toggle-knob {
  width: 22px;
  height: 22px;
  border-radius: 99px;
  background: var(--line-strong);
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
  flex: none;
}

.toggle-on {
  background: color-mix(in srgb, var(--good) 14%, transparent);
  border-color: color-mix(in srgb, var(--good) 42%, transparent);
  color: var(--good);
  font-weight: 500;
}

.toggle-on .toggle-knob { background: var(--good); }

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

/* A step that has not been reached. Shown rather than hidden, so the client can
 * see how much is left, but not a link. */
.wiz-step.locked {
  opacity: 0.42;
  cursor: not-allowed;
}

/* --- One plan, and the founding offer on it -------------------------------- */

.plan-solo {
  display: grid;
  max-width: 520px;
}

/* The plan and the confirmation, side by side.

   Only one panel is ever rendered beside the card: the confirmation before
   payment, or the active-plan summary after it. With neither, the grid holds
   one child and the card keeps its own 520px cap rather than stretching, which
   is why the first column is capped rather than a bare `1fr`. */
.plan-pair {
  display: grid;
  grid-template-columns: minmax(360px, 480px) minmax(400px, 1fr);
  gap: 15px;
  /* Stretch, not start.
   *
   * The two cards are sized by their own contents, and the contents are a
   * price table on one side and a billing policy on the other, so they landed
   * 3px apart and the bottom edge of the pair read as ragged rather than as
   * two cards. Equal height is what makes them look like a pair, and the
   * leftover space is absorbed above each card's action by `.plan-foot`, so it
   * reads as deliberate rather than as a gap. */
  align-items: stretch;
}

/* Flex rather than grid for the two cards on this page only, so the action can
 * be pushed to the foot. `margin-top: auto` on a grid item aligns it inside
 * its own row and does nothing for the container, which is the trap here.
 * Scoped to `.plan-pair` because `.plan` is also the public pricing card. */
.plan-pair > .plan-solo > .plan,
.plan-pair > .panel {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* The action at the foot of each card, so the two buttons sit on one line.
 *
 * An explicit class rather than `:last-child`, because the last thing in a
 * card is not always something that should be pushed down: an active plan has
 * no button and ends on a callout, and dropping that to the bottom would open
 * a hole in the middle of the card. */
.plan-pair .plan-foot { margin-top: auto; }

/* Stacked below 1240px, and the number is measured rather than picked.

   The portal rail and the page padding take about 330px, so at 1120px the
   second column had shrunk to its 400px floor and the billing policy was a
   ribbon of two-word lines. Two columns are only better than one while both are
   comfortable; below that the old stacked order is the better page. */
@media (max-width: 1240px) {
  .plan-pair { grid-template-columns: 1fr; }
  .plan-solo { max-width: 520px; }
}

/* The old price, struck through. Dimmer and smaller than the new one, or the
 * eye lands on the number nobody pays. */
.plan-was {
  font-family: var(--font-mono);
  font-size: 19px;
  color: var(--ink-3);
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
}

.founding {
  display: grid;
  gap: 5px;
  padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--good) 34%, transparent);
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--good) 9%, transparent);
}

.founding-head {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--good);
}

/* ======================================================================
 * Photography, and the sections built around it
 *
 * Every photograph in this product is warm: oak, cream, marble, olive, soft
 * daylight. That is the argument rather than the decoration. A med spa owner
 * deciding whether this belongs on their front desk is looking for something
 * that would not embarrass their reception, and a page of grey panels is not
 * it. The palette above was warmed to meet these images halfway; a cool page
 * behind a warm photograph reads as a mistake in both directions.
 *
 * Three rules hold across all of it.
 *
 * **An image never carries meaning on its own.** Every one either has a
 * caption or sits beside copy saying the same thing, so a blocked image, a
 * slow connection or a screen reader loses nothing. That is exactly why the
 * alt text is empty rather than descriptive: the sentence is already next to
 * it in real text, and hearing it twice is worse than hearing it once.
 *
 * **Width and height are in the markup.** The browser reserves the space
 * before the bytes arrive, so nothing under a photograph jumps as it lands.
 *
 * **Nothing is fetched from another host.** These are served from
 * static/media, like every other asset here.
 * ==================================================================== */

.shot {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-card);
  border: 1px solid var(--line);
  background: var(--surface-1);
  /* A screenshot of a product is a thing being held up, not a block of the
   * page. It had a border and no elevation, so on paper it read as a panel
   * rather than as a screen. */
  box-shadow: var(--shadow-float);
  line-height: 0;
}

.shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A hairline of the brand across the top of a photograph, which is the one
 * place the saturated gradient is allowed at this size: it is three pixels
 * tall, so it stays inside the rule that large surfaces never get it. */
.shot::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--brand-gradient-h);
  opacity: 0.9;
}

.shot-tall { aspect-ratio: 4 / 3; }
.shot-wide { aspect-ratio: 16 / 9; }

/* A caption sitting on the image itself, for the one or two places where the
 * photograph is the whole point and a line beneath it would read as a stray
 * sentence. The scrim is a gradient rather than a flat tint so the top of the
 * picture is untouched. */
.shot-cap {
  position: absolute;
  inset: auto 0 0 0;
  display: grid;
  gap: 3px;
  padding: 34px 18px 15px;
  line-height: 1.45;
  background: linear-gradient(to top, #14100adf 0%, #14100a99 45%, transparent 100%);
}

.shot-cap b {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: var(--ls-tight);
  color: #fbf8f2;
}

.shot-cap span { font-size: 12.5px; color: #e2dbcd; }

/* Photograph beside copy. Alternates by adding .band-flip, which swaps the
 * columns on a wide screen and does nothing on a narrow one, where the image
 * always leads. */
.band {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
  align-items: center;
}

.band-copy { display: grid; gap: 13px; align-content: center; }
.band-copy p { font-size: 14px; line-height: 1.65; color: var(--ink-2); max-width: 52ch; }

@media (min-width: 900px) {
  .band-flip .band-media { order: 2; }
}

@media (max-width: 899px) {
  .band { grid-template-columns: 1fr; gap: 22px; }
}

/* The three steps, which is the shape the marketing site uses for the same
 * explanation. The number is set in the display serif at a size that would be
 * shouting in the sans, and dimmed hard: it is a landmark for the eye moving
 * down the page, not a thing to read. */
.steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 26px;
}

@media (max-width: 860px) {
  .steps { grid-template-columns: 1fr; gap: 20px; }
}

.step { display: grid; gap: 7px; align-content: start; }

.step-no {
  font-family: var(--font-display);
  font-size: 30px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--ls-display);
  color: var(--accent);
  opacity: 0.5;
}

.step h3 { font-size: 15px; font-weight: 500; }
.step p { font-size: 13px; line-height: 1.6; color: var(--ink-3); }

/* Where a call ends up. Three cards, and the label on each is the outcome
 * rather than a feature name, because these are the three things that can
 * actually happen and a med spa owner wants to know which one they get. */
.outcomes {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}

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

.outcome {
  display: grid;
  gap: 6px;
  align-content: start;
  padding: 17px 17px 19px;
  border-radius: var(--r-3);
  border: 1px solid var(--line);
  background: var(--surface-2);
  position: relative;
  overflow: hidden;
}

.outcome::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--corner-spark);
  opacity: 0.55;
  pointer-events: none;
}

.outcome > * { position: relative; }
.outcome h3 { font-size: 14.5px; font-weight: 500; }
.outcome p { font-size: 12.5px; line-height: 1.6; color: var(--ink-3); }

/* The section label. The reference sites both set a small tracked word above
 * every heading, and it does more than decorate: it tells you what kind of
 * thing you are about to read before you read it. */
.section-head { display: grid; gap: 9px; }
.section-head h2 { font-size: clamp(22px, 3vw, 28px); line-height: 1.2; }
.section-head p { font-size: 14px; line-height: 1.65; color: var(--ink-2); max-width: 62ch; }

/* The hero with no film to show. One column, capped at a readable measure
 * rather than stretched across the full 1152px container, because a paragraph
 * running the whole width of a desktop window is not a paragraph anybody
 * finishes. */
.hero-solo {
  display: grid;
  max-width: 62ch;
}

/* Four facts under the buttons. Deliberately not cards, not pills and not
 * icons-with-headings: it is a list of short true statements, and the least
 * decorated version is the one that reads as confidence rather than as
 * marketing. */
.trust-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 9px 20px;
  margin-top: 6px;
  padding: 0;
  list-style: none;
}

.trust-strip li {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: var(--ink-2);
}

.trust-strip svg { color: var(--good); flex: none; }

/* The theme toggle is a 30px circle by design, which is right visually and
 * under the 44px touch guidance on a phone. The hit area is expanded rather
 * than the button, so the control looks identical and is reachable with a
 * thumb. The pseudo-element is behind nothing and captures nothing else,
 * because the bar has room around it. */
.pub-bar .theme-btn { position: relative; }

.pub-bar .theme-btn::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

/* A rail destination that exists but is not open yet.
 *
 * Shaped like a nav item so the order is legible, and plainly not a link, so
 * nobody clicks it and gets bounced. Cursor and colour do the work; there is no
 * pointer, no hover, and no href to follow. */
.nav-locked {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 11px;
  border-radius: var(--r-2);
  font-size: 13.5px;
  color: var(--ink-4);
  cursor: default;
  user-select: none;
}

.nav-locked .ico { opacity: 0.55; }

.nav-locked-note {
  display: block;
  padding: 2px 11px 8px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-3);
}

/*
  A count of work owed, on a nav tab. Alarm rather than accent because it is
  the one thing in this rail that means somebody should stop and do something:
  a customer with no Brin number has a line that answers nothing.

  It sits at the end of the row rather than floating over the icon, so it never
  covers the label at a narrow width.
*/
.nav .nav-badge {
  margin-left: auto;
  min-width: 18px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--alarm);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 18px;
  text-align: center;
}

/*
  The Brin number queue on the accounts page.

  The filter sits on its own line above the rows, with the same side padding as
  a `.list-row`, so the control and the rows it filters share a left edge.
*/
.numbers-filter {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 20px 14px;
  flex-wrap: wrap;
}
.numbers-filter .label { margin: 0; }
.numbers-filter select { width: auto; min-width: 230px; }

/*
  Name, then status, then the field. The field is a fixed width so five rows
  line up as a column rather than each one sizing to its own content, which is
  what made the first version read as a floating block.
*/
.number-set {
  display: flex;
  align-items: center;
  gap: 8px;
}
.number-set .input {
  width: 200px;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 900px) {
  /* The field owns the row below the name once there is no room beside it. */
  .number-set { width: 100%; }
  .number-set .input { width: auto; flex: 1; }
}

/*
  What the line will never answer. One topic per line, each marked so it reads
  as a refusal rather than a feature: a bullet list of things a product does not
  do looks identical to a bullet list of things it does.
*/
.demo-never {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 4px;
}

.demo-never li {
  display: grid;
  grid-template-columns: 12px 1fr;
  gap: 7px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-3);
}

.demo-never li::before {
  content: "";
  /* A short dash, aligned to the first line's optical centre. */
  width: 8px;
  height: 1.5px;
  margin-top: 8px;
  border-radius: 2px;
  background: var(--ink-4);
}

/*
  The conversation container is not a box, it is a place for boxes.

  `[data-thread]` is always in the markup, because `demo-call.js` sets its
  innerHTML per turn and cannot write into an element that is not there. As an
  ordinary grid child it therefore occupied a row on an untouched page: zero
  height, but a full 14px gap on each side of it, which is the unexplained hole
  between the suggestions and the ask box that made the panel look padded out
  for no reason.

  `display: contents` removes the wrapper from the grid without removing it from
  the DOM, so `.demo-thread` becomes the grid item once there is one and nothing
  is contributed while there is not. `:empty` would not do this: Jinja leaves
  the indentation inside the div as a text node, so the element is never empty.
*/
[data-thread] {
  display: contents;
}

/*
  The plan card, once it carries what the plan includes.

  The row was a flex line: name on the left, price on the right. It is a column
  now, because a list of ten things is not something that sits beside a price.
  The head keeps the old flex line inside it, so the name and the price still
  read as one pair.
*/
.plan-pick-row {
  flex-direction: column;
  align-items: stretch;
  gap: 13px;
  padding: 16px 17px 15px;
}

.plan-pick-head {
  display: flex;
  align-items: center;
  gap: 14px;
}

.plan-pick-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 6px;
}

.plan-pick-list li {
  display: grid;
  grid-template-columns: 15px 1fr;
  gap: 8px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink-2);
}

/*
  A tick, drawn rather than loaded, and in the accent rather than the good
  green: these are things the plan includes, not things that have succeeded.
*/
.plan-pick-list li::before {
  content: "";
  width: 13px;
  height: 13px;
  margin-top: 3px;
  border-radius: 99px;
  background: var(--accent-wash);
  border: 1px solid color-mix(in srgb, var(--accent) 34%, transparent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 6.2 4.9 8.2 9 4.1' fill='none' stroke='%234b57d1' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 11px 11px;
}

/* The row is a link, so it should say where it goes. */
.plan-pick-go {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
}

.plan-pick-go::after {
  content: "\2192";
  transition: transform var(--dur) var(--ease);
}

.plan-pick-row:hover .plan-pick-go::after {
  transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
  .plan-pick-go::after { transition: none; }
  .plan-pick-row:hover .plan-pick-go::after { transform: none; }
}

/*
  The chosen plan, once it carries what the plan includes.

  Same change as `.plan-pick-row`: a flex line becomes a column, with the old
  line kept inside a head so the name and the price still read as one pair. The
  list reuses `.plan-pick-list`, because a second copy of a tick would drift
  from the first the next time one of them is adjusted.
*/
.plan-chosen {
  flex-direction: column;
  align-items: stretch;
  gap: 13px;
}

.plan-chosen-head {
  display: flex;
  align-items: center;
  gap: 14px;
}

/* On the accent wash the plain wash behind each tick disappears, so the tick
 * gets the card's own surface to sit on instead. */
.plan-chosen .plan-pick-list li::before {
  background-color: var(--surface-1);
}

/* --- The price on the landing page ---------------------------------------- */

/* Centred, because it is the one section that is an offer rather than an
 * explanation, and centring is what separates it from the nine columns of copy
 * above it without needing a second background colour. */
.price-head {
  justify-items: center;
  text-align: center;
}

.price-head p { max-width: 58ch; }

.price-card {
  display: grid;
  justify-self: center;
  width: 100%;
  max-width: 560px;
  gap: 13px;
  padding: 30px 32px;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background:
    radial-gradient(120% 80% at 50% 0%, var(--accent-wash), transparent 68%),
    var(--surface-1);
  box-shadow: var(--shadow-elev);
}

/* A badge, so a pill. The rounding rule holds here: fully round is reserved for
 * status and filter chips, and what this says is which rate is in force. */
.price-badge {
  justify-self: start;
  padding: 6px 13px;
  border-radius: var(--r-pill);
  border: 1px solid color-mix(in srgb, var(--accent) 38%, transparent);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.price-figure {
  display: flex;
  align-items: baseline;
  gap: 9px;
  margin: 2px 0 0;
}

/* The display face rather than the mono one. This is the number the page is
 * built around, and Fraunces at 52px is the only place on the site where the
 * typeface is doing the persuading. */
.price-amount {
  font-family: var(--font-display);
  font-size: clamp(40px, 6vw, 52px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--ls-display);
  color: var(--ink);
}

.price-per { font-size: 14px; color: var(--ink-3); }

.price-note { margin: 0; font-size: 13px; color: var(--ink-3); }
.price-summary { margin: 0; font-size: 14px; color: var(--ink-2); }

.price-list {
  display: grid;
  gap: 9px;
  margin: 6px 0 2px;
  padding: 15px 0 0;
  border-top: 1px solid var(--line);
  list-style: none;
}

/* Left aligned inside a centred card. Ten ticks read as a list only while their
 * left edges agree; centred, each one starts somewhere different and the eye
 * has nothing to run down. */
.price-list li {
  display: grid;
  grid-template-columns: 13px 1fr;
  align-items: start;
  gap: 10px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-2);
}

.price-list svg { margin-top: 4px; color: var(--good); }

.price-actions { margin-top: 4px; }

.price-fine {
  margin: 0;
  padding-top: 13px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  line-height: 1.6;
  color: var(--ink-3);
}

.price-fine a { color: var(--accent); text-decoration: underline; }

@media (max-width: 560px) {
  .price-card { padding: 24px 20px; }
}

/* Who it is for, in two columns. The second is as load bearing as the first, so
 * it is not a footnote under the first: it is the same shape, beside it. */
.fit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 13px;
}

.fit {
  display: grid;
  gap: 11px;
  align-content: start;
  padding: 19px;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--surface-1);
}

.fit ul { display: grid; gap: 9px; margin: 0; padding: 0; list-style: none; }

/* A dash rather than a tick or a cross. A cross beside "not yet" reads as five
 * things the product fails at; these are four things it is not sold for. */
.fit li {
  position: relative;
  padding-left: 17px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-2);
}

.fit li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 9px;
  width: 8px;
  height: 1.5px;
  border-radius: 1px;
  background: var(--line-strong);
}

.fit:first-child li::before { background: var(--good); }

/* The paragraph about not being a clinician. Set at reading size rather than at
 * the 13px the rest of the page's supporting copy uses, because it is the one
 * place the product is asked to be believed rather than scanned. */
.honest p {
  margin: 0;
  font-size: 14px;
  line-height: 1.7;
  color: var(--ink-2);
  max-width: 68ch;
}

/* The refund promise, inside the price card. Given the good colour rather than
 * the accent: it is the reassurance on a card whose other emphasis is the
 * price, and two accents beside each other compete. */
.price-promise {
  display: grid;
  grid-template-columns: 15px 1fr;
  align-items: start;
  gap: 9px;
  margin: 2px 0 0;
  padding: 11px 13px;
  border: 1px solid color-mix(in srgb, var(--good) 30%, transparent);
  border-radius: var(--r-3);
  background: color-mix(in srgb, var(--good) 8%, transparent);
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink);
}

.price-promise svg { margin-top: 2px; color: var(--good); }

/* What the med spa receives, once it runs the full width.

   The three things a call can leave behind sit in a row here rather than
   stacking, which is what they did in a half-width column and is why the panel
   looked empty at the top and stranded at the bottom. */
.demo-received [data-received] .stack-sm {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 11px;
  align-items: start;
}

@media (max-width: 720px) {
  .demo-received [data-received] .stack-sm { grid-template-columns: 1fr; }
}

/* The agreement above the signup button.

   `.toggle` is a row with a control and a word; this one carries two sentences
   and two links, so the text has to wrap against the checkbox rather than
   beside it on one line. Aligned to the top for the same reason: with three
   lines of text, a centred checkbox floats in the middle of them. */
/* The agreement above the signup button.

   **Not `.toggle`.** That is an `inline-flex` pill built for a one-word switch,
   so it shrink-wraps to its content: with two sentences and two links in it, the
   label sized itself to 634px, the signup form's single auto grid column grew to
   match, every input stretched to 636px and hung out of the card, and the
   privacy link still ran off the side of the page because an inline-flex box
   does not wrap. Attributed by measuring the deployed page, where the same
   inputs are 368px.

   So it is its own block-level row: full width, ordinary corners, and text that
   wraps. `min-width: 0` on the text keeps a flex item from refusing to shrink
   below its own content. */
.terms-accept {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  min-width: 0;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  background: var(--surface-2);
  font-size: 12px;
  line-height: 1.55;
  color: var(--ink-2);
  cursor: pointer;
  /* Explicit, because the first version of this row borrowed `.toggle`, which
   * sets `nowrap` for a one-word switch, and two sentences then ran off the
   * side of the page. Nothing sets it now that the class is gone; saying so
   * here means this row does not depend on that staying true. */
  white-space: normal;
}

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

.terms-accept .check { margin-top: 2px; flex: none; }

/* The item that carries the text is the one that has to be allowed to wrap. */
.terms-accept > span { min-width: 0; }

.terms-accept a { color: var(--accent); text-decoration: underline; }

/* --- /watch, the outreach film ------------------------------------------- */

/* One heading, one film, one thing to do after it. The page is deliberately
 * narrower than the landing page: there is no second column and nothing to
 * scan, so `.pub-wide` at 960px is the reading measure rather than a layout. */
.watch { gap: 24px; padding-top: 40px; }

.watch-title {
  font-family: var(--font-display);
  font-size: clamp(28px, 4.4vw, 44px);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 6px 0 0;
}

.watch-lede {
  max-width: 58ch;
  margin: 4px 0 0;
  font-size: 16px;
  line-height: 1.65;
  color: var(--ink-2);
}

/* Black rather than a surface token, and that is not an oversight. The film is
 * letterboxed while it loads and during the first frames, and a warm card
 * behind black bars reads as a rendering fault. Same reasoning as the
 * `<video>` letterbox on the landing page, which is also deliberately not a
 * token. */
.watch-frame {
  position: relative;
  margin-top: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: #000;
  overflow: hidden;
  box-shadow: var(--shadow-elev);
}

.watch-video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
}

/* The overlay is `hidden` in the markup and revealed by `watch.js`. With no
 * script it never appears and the film's own controls are the control, which
 * is why this may cover the whole frame without trapping anybody. */
.watch-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  padding: 0;
  background: rgba(0, 0, 0, 0.10);
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}

.watch-play:hover { background: rgba(0, 0, 0, 0.20); }
.watch-play:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -4px;
}

/* `display: none` restated, because the rule above sets `display: flex` and an
 * author declaration beats the user agent's own `[hidden]`. Without it the
 * overlay renders before the script has run and over a page with no script at
 * all, which is exactly the fault `.qa[hidden]` and `.timefield-panel[hidden]`
 * were each written for. */
.watch-play[hidden] { display: none; }

.watch-pill {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  padding: 11px 22px 11px 13px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.96);
  color: #1a1a1c;
  font-size: 14px;
  font-weight: 500;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.28);
}

.watch-pill-icon {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 50%;
  background: var(--accent);
  color: var(--ink-on-accent);
  padding-left: 2px;
}

/* What to do after the film. Centred, because it is one decision rather than a
 * column of reading. */
.watch-card {
  margin-top: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--surface-1);
  padding: 30px 26px;
  text-align: center;
}

.watch-card-title {
  font-family: var(--font-display);
  font-size: clamp(21px, 2.8vw, 27px);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.014em;
  text-wrap: balance;
  margin: 0;
}

.watch-card-lede {
  max-width: 46ch;
  margin: 11px auto 0;
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
}

.watch-actions {
  justify-content: center;
  gap: 16px;
  margin-top: 22px;
}

.watch-secondary {
  font-size: 14px;
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-color: var(--line-strong);
  text-underline-offset: 4px;
  transition: color var(--dur) var(--ease),
              text-decoration-color var(--dur) var(--ease);
}

.watch-secondary:hover {
  color: var(--ink);
  text-decoration-color: var(--accent);
}

.watch-terms {
  margin: 20px 0 0;
  font-size: 13px;
  color: var(--ink-3);
}

@media (max-width: 560px) {
  .watch { padding-top: 26px; }
  .watch-card { padding: 24px 18px; }
  .watch-pill { padding: 9px 17px 9px 10px; font-size: 13px; }
  .watch-pill-icon { width: 30px; height: 30px; }
}

@media (prefers-reduced-motion: reduce) {
  .watch-play, .watch-secondary { transition: none; }
}

/* --- Arrival ------------------------------------------------------------- */

/*
 * Scroll-triggered reveals, driven by `reveal.js`.
 *
 * **Everything is keyed on `html.js-reveal`**, which the pre-paint script in
 * `base.html` sets only when the browser has IntersectionObserver and the
 * visitor has not asked for reduced motion. Without that class not one rule
 * below applies and nothing is ever hidden, so a blocked script, an old
 * browser or a preference for stillness leaves a page that simply does not
 * animate. A reveal whose resting state is invisible must never be the reason
 * a page is blank.
 *
 * The media query repeats the same condition on purpose. The class is decided
 * once at load; the query keeps up with somebody who turns the system setting
 * on while the page is open, which the class cannot.
 */
@media (prefers-reduced-motion: no-preference) {
  .js-reveal [data-reveal] {
    opacity: 0;
    /* The default arrival: a short lift. 10px, because the movement is meant
       to be felt rather than watched. */
    transform: translate3d(0, 10px, 0);
  }

  /* Promoted only while it still has work to do. `reveal.js` adds `is-settled`
     once the transition and its stagger have finished, and a layer promoted for
     ever costs memory on every element that ever animated. */
  .js-reveal [data-reveal]:not(.is-settled) {
    will-change: opacity, transform;
  }

  /* A little further, for a whole section arriving rather than a line of it. */
  .js-reveal [data-reveal="lift"] { transform: translate3d(0, 15px, 0); }

  /* Opacity alone, no movement. This is what the stat tiles inside the
     dashboard mockup use, and it is the right answer twice over: the brief asks
     for those to arrive without scale or bounce, and they are SVG children,
     where a CSS translate is resolved against the user coordinate system rather
     than against px and does not mean what it does elsewhere. */
  .js-reveal [data-reveal="fade"] { transform: none; }

  /* The film. It settles rather than lifts: a hair of scale and the shadow
     coming up under it, which reads as something being placed rather than
     something sliding in. */
  .js-reveal [data-reveal="settle"] {
    transform: scale(0.98);
    box-shadow: 0 0 0 rgba(0, 0, 0, 0);
  }

  .js-reveal [data-reveal].is-in {
    opacity: 1;
    transform: none;
    transition:
      opacity 500ms cubic-bezier(0.22, 0.61, 0.36, 1),
      transform 500ms cubic-bezier(0.22, 0.61, 0.36, 1),
      box-shadow 500ms cubic-bezier(0.22, 0.61, 0.36, 1);
    /* The stagger. `--stagger` is set per item in the template from the loop
       index, so a row of four arrives as one sweep rather than four events.
       50ms is deliberately under the threshold where a list reads as a
       sequence: it is the same arrival, slightly smeared. */
    transition-delay: calc(var(--stagger, 0) * 50ms);
  }

  /* `will-change` is dropped here rather than on `.is-in`, which would pull the
     layer out from under the transition that is still running. */
  .js-reveal [data-reveal].is-settled { will-change: auto; }

  /* The film keeps the elevation it is meant to rest at. Written out rather
     than inherited, because `transform: none` above cannot also restore a
     shadow. */
  .js-reveal [data-reveal="settle"].is-in { box-shadow: var(--shadow-float); }
}

/*
 * The header gains substance once the page moves under it.
 *
 * **The resting state is only quieter when there is a script to restore it.**
 * Without `.js-reveal` the bar keeps the tint and blur it has always had at
 * every scroll position, because a sticky bar that is transparent over scrolled
 * content is unreadable and nothing would be adding `is-scrolled`.
 *
 * `blur(0)` rather than `none`, because a filter function interpolates and a
 * keyword does not: `none` to `blur(12px)` snaps, `blur(0px)` to `blur(12px)`
 * is the frosting arriving. The border is always present and only changes
 * colour, so the bar never changes height and nothing below it moves.
 */
.js-reveal .pub-bar {
  background: transparent;
  backdrop-filter: blur(0px);
  -webkit-backdrop-filter: blur(0px);
  border-bottom: 1px solid transparent;
  transition:
    background 220ms var(--ease),
    backdrop-filter 220ms var(--ease),
    border-color 220ms var(--ease);
}

.js-reveal .pub-bar.is-scrolled {
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom-color: color-mix(in srgb, var(--line) 75%, transparent);
}

/* The frosting is not movement, so it still happens under reduced motion. It
   just stops being a transition and becomes a state. */
@media (prefers-reduced-motion: reduce) {
  .js-reveal .pub-bar { transition: none; }
}
