﻿/* ================================================================
   em.css — Electric Dusk brand styles for exclusively.me
   Extracted from landing page v2 (approved)
   ================================================================ */

:root {
  --deep-night: #1B1B2F;
  --violet: #5B4BD4;
  --violet-dark: #4A3CB0;
  --violet-light: #6B5CE7;
  --lilac: #A29BFE;
  /* Outstanding, not broken. The same value .flash-warn and .verify-badge
     already use as rgba() — those keep their own literals because they need
     alpha, so if this ever changes, change all three. */
  --amber: #FFB020;
  --amber-dark: #E89A0C;
  --ghost: #F8F7FF;
  --mint: #00E5A0;
  --mint-dark: #00C98C;
  --font-head: 'Space Grotesk', system-ui, sans-serif;
  --font-body: 'Inter', system-ui, sans-serif;

  /* One content width for every dashboard screen. There were four — 1100 for
     the editor, 680 for My Pages and Share, 600 for Settings — each set
     independently on its own class, so they drifted and the narrow ones read
     as floating under a full-bleed topbar.

     The editor needs the room (it is a 1fr 320px grid) and analytics will
     need more of it as real charts land, so the wide one won. Anything that
     should be narrower constrains itself INSIDE the container rather than
     shrinking it — otherwise the next page invents a fifth width. */
  --dash-max: 1100px;
  /* The one gap between analytics panels — rows, columns and the space under
     a split row all read from this, so the page has a single rhythm. */
  --analytics-gap: 16px;
}

/* ---- RESET ---- */
* { margin: 0; padding: 0; box-sizing: border-box; }
/* overflow-x on the root, not just body: on a phone, position:fixed is laid
   out against the LAYOUT viewport, so anything that widens the document
   carries the fixed nav off to the right with it. clip rather than hidden —
   hidden would make the root a scroll container and break position:sticky.
   The hidden line is the fallback for browsers without clip. */
html {
  scroll-behavior: smooth;
  overflow-x: hidden;
  overflow-x: clip;
}

body {
  font-family: var(--font-body);
  color: var(--deep-night);
  background: var(--ghost);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  overflow-x: clip;
}

/* ---- NAV ---- */
.em-nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  padding: 16px 40px;
  display: flex; align-items: center; justify-content: space-between;
  background: rgba(248, 247, 255, 0.85);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-bottom: 1px solid rgba(27, 27, 47, 0.06);
  transition: background 0.3s;
}
.em-nav.scrolled { background: rgba(248, 247, 255, 0.95); }
.nav-logo {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--font-head); font-weight: 700; font-size: 20px;
  color: var(--deep-night); text-decoration: none;
}
.nav-logo .dot { color: var(--violet); }
.nav-links {
  display: flex; gap: 32px; align-items: center;
  margin-left: auto;
}
.nav-links a {
  font-size: 14px; font-weight: 500; color: var(--deep-night);
  text-decoration: none; opacity: 0.6; transition: opacity 0.2s;
}
.nav-links a:hover { opacity: 1; }
/* The CTA is a sibling of .nav-links, not a child — on mobile the links wrap
   onto a second row and the CTA stays in the bar. So it restates the type
   styles it used to inherit from .nav-links a. */
.nav-cta {
  margin-left: 32px;
  font-family: var(--font-body); font-size: 14px;
  text-decoration: none; white-space: nowrap;
  background: var(--violet); color: white !important;
  padding: 10px 24px; border-radius: 10px;
  font-weight: 600; opacity: 1 !important;
  transition: background 0.2s, transform 0.15s;
}
.nav-cta:hover { background: var(--violet-dark); transform: translateY(-1px); }
.nav-links a:focus-visible,
.nav-cta:focus-visible,
.nav-logo:focus-visible {
  outline: 2px solid var(--violet); outline-offset: 3px;
  border-radius: 6px; opacity: 1;
}
/* The nav is fixed, so anchored sections need clearance or the heading lands
   underneath it. */
section[id] { scroll-margin-top: 92px; }

/* ---- HERO ---- */
.hero {
  padding: 130px 40px 80px;
  text-align: center;
  position: relative;
  /* ::before below is a fixed 800px glow centred on the hero. Under 800px
     wide it hangs off both edges and stretches the document — 595px wide on
     a 390px phone, which is what pushed the nav CTA off screen. Clip it
     here, at the source. overflow-x: clip keeps overflow-y visible, so the
     glow still bleeds up behind the nav as designed. */
  overflow-x: hidden;
  overflow-x: clip;
}
.hero::before {
  content: '';
  position: absolute; top: -200px; left: 50%; transform: translateX(-50%);
  width: 800px; height: 800px;
  background: radial-gradient(circle, rgba(91,75,212,0.08) 0%, transparent 70%);
  pointer-events: none;
}
.hero h1 {
  font-family: var(--font-head);
  font-size: clamp(38px, 5.5vw, 64px);
  font-weight: 800; line-height: 1.1;
  letter-spacing: -0.03em;
  max-width: 700px; margin: 0 auto 20px;
  position: relative;
}
.hero h1 .accent { color: var(--violet); }
/* .hero--wide: for a hero h1 whose longest forced line (a <br>-separated
   segment) doesn't fit in the standard 700px column at the clamp()'s upper
   font sizes -- it wraps mid-segment instead of at the intended <br>, which
   reads as broken rather than deliberate. Widening the column lets the
   longest line stay on one line up near the top of the viewport range; it
   still narrows back down like every other hero on smaller screens because
   this is a max-width, not a fixed width. Used by /for/businesses, whose
   middle line ("One QR code on your card.") is longer than this page's
   other lines and than every other page's hero lines; and by
   /for/social-media, whose first line ("Every platform gives you") wrapped
   "you" onto its own line ahead of the intended <br> without it. */
.hero--wide h1 { max-width: 920px; }
.hero-sub {
  font-size: 18px; line-height: 1.6;
  color: rgba(27,27,47,0.55);
  max-width: 480px; margin: 0 auto 36px;
}

/* ---- CLAIM FORM ---- */
.hero-form {
  display: flex; align-items: center; gap: 0;
  max-width: 480px; margin: 0 auto 16px;
  background: white;
  border: 2px solid rgba(91,75,212,0.15);
  border-radius: 14px;
  padding: 6px;
  transition: border-color 0.2s;
}
.hero-form:focus-within { border-color: var(--violet); }
.hero-form .prefix {
  font-size: 15px; font-weight: 600; color: var(--lilac);
  padding: 0 2px 0 14px; white-space: nowrap;
  font-family: var(--font-head);
}
.hero-form input {
  flex: 1; border: none; outline: none; background: none;
  font-size: 16px; font-family: var(--font-body);
  padding: 12px 8px; color: var(--deep-night);
  min-width: 0;
}
.hero-form input::placeholder { color: rgba(27,27,47,0.3); }
.hero-form button {
  background: var(--violet); color: white;
  border: none; border-radius: 10px; padding: 12px 28px;
  font-size: 15px; font-weight: 600; cursor: pointer;
  font-family: var(--font-body);
  transition: background 0.2s, transform 0.15s;
  white-space: nowrap;
}
.hero-form button:hover { background: var(--violet-dark); transform: translateY(-1px); }
.hero-note {
  font-size: 13px; color: rgba(27,27,47,0.4);
  margin-bottom: 56px;
}
.hero-note strong { color: var(--violet); font-weight: 600; }

/* ---- JOURNEY VISUAL (hero) ---- */
.journey {
  display: flex; align-items: center; justify-content: center;
  gap: 0; max-width: 820px; margin: 0 auto;
  position: relative;
}
.journey-step {
  display: flex; flex-direction: column; align-items: center;
  position: relative; z-index: 1;
}
.journey-card {
  width: 200px;
  background: white;
  border: 1px solid rgba(27,27,47,0.08);
  border-radius: 16px;
  padding: 20px 16px;
  text-align: center;
  transition: transform 0.3s, box-shadow 0.3s;
}
.journey-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 32px rgba(27,27,47,0.08);
}
.journey-num {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--violet); color: white;
  font-family: var(--font-head); font-weight: 700; font-size: 13px;
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 12px;
}
.journey-icon {
  width: 48px; height: 48px; border-radius: 12px;
  margin: 0 auto 10px;
  display: flex; align-items: center; justify-content: center;
  font-size: 24px;
}
.journey-icon.vi { background: rgba(91,75,212,0.1); }
.journey-icon.mi { background: rgba(0,229,160,0.12); }
.journey-card h3 {
  font-family: var(--font-head); font-weight: 700;
  font-size: 14px; margin-bottom: 4px;
}
.journey-card p {
  font-size: 12px; color: rgba(27,27,47,0.5); line-height: 1.4;
}
.journey-arrow {
  width: 60px; height: 2px; background: rgba(91,75,212,0.15);
  position: relative; flex-shrink: 0; margin-top: -20px;
}
.journey-arrow::after {
  content: ''; position: absolute; right: -1px; top: -4px;
  border: 5px solid transparent;
  border-left: 6px solid rgba(91,75,212,0.25);
}
.journey-time {
  font-size: 11px; color: var(--mint-dark); font-weight: 600;
  margin-top: 8px;
  font-family: var(--font-head);
}

/* ---- SOCIAL PROOF ---- */
.proof {
  padding: 48px 40px;
  border-top: 1px solid rgba(27,27,47,0.06);
  border-bottom: 1px solid rgba(27,27,47,0.06);
  background: white;
}
.proof-inner {
  max-width: 900px; margin: 0 auto;
  display: flex; align-items: center; justify-content: center;
  gap: 48px; flex-wrap: wrap;
}
.proof-stat { text-align: center; }
.proof-num {
  font-family: var(--font-head); font-weight: 800;
  font-size: 28px; color: var(--violet);
}
.proof-label {
  font-size: 13px; color: rgba(27,27,47,0.45);
  margin-top: 2px;
}

/* ---- NAME AVAILABILITY (HTMX responses) ---- */
.name-available {
  font-size: 14px; font-weight: 500; color: var(--mint-dark);
  margin-top: 8px;
}
.name-taken {
  font-size: 14px; font-weight: 500; color: #E74C3C;
  margin-top: 8px;
}
.name-invalid {
  font-size: 14px; font-weight: 500; color: rgba(27,27,47,0.4);
  margin-top: 8px;
}

/* ---- FIELD VALIDATION (HTMX responses for email, phone) ---- */
.field-valid {
  font-size: 14px; font-weight: 500; color: var(--mint-dark);
  margin-top: 4px; display: inline-block;
}
.field-invalid {
  font-size: 14px; font-weight: 500; color: #E74C3C;
  margin-top: 4px; display: inline-block;
}

/* ---- PHONE MOCKUP ---- */
.phone-wrap {
  display: flex; justify-content: center;
  perspective: 1000px;
}
.phone {
  width: 280px;
  background: var(--deep-night);
  border-radius: 36px;
  padding: 12px;
  box-shadow: 0 40px 80px rgba(27,27,47,0.18), 0 8px 24px rgba(27,27,47,0.1);
  transform: rotateX(4deg);
}
.phone-screen {
  background: linear-gradient(180deg, #2A2A48 0%, #1B1B2F 100%);
  border-radius: 26px;
  padding: 32px 20px 28px;
  min-height: 420px;
}
.phone-notch {
  width: 100px; height: 5px;
  background: rgba(255,255,255,0.15);
  border-radius: 10px;
  margin: 0 auto 28px;
}
.phone-avatar {
  width: 64px; height: 64px; border-radius: 50%;
  background: var(--violet);
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 14px;
  font-family: var(--font-head); font-weight: 700;
  font-size: 22px; color: white;
}
.phone-name {
  font-family: var(--font-head); font-weight: 700;
  font-size: 18px; color: white; text-align: center;
  margin-bottom: 4px;
}
.phone-bio {
  font-size: 13px; color: rgba(255,255,255,0.5);
  text-align: center; margin-bottom: 24px;
}
.phone-link {
  display: block;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 12px;
  padding: 14px 16px;
  margin-bottom: 10px;
  text-align: center;
  font-size: 14px; font-weight: 500;
  color: white; text-decoration: none;
  transition: background 0.2s;
}
.phone-link:hover { background: rgba(255,255,255,0.12); }
.phone-link.mint {
  background: var(--mint); color: var(--deep-night);
  border-color: transparent; font-weight: 600;
}
.phone-link.mint:hover { background: var(--mint-dark); }

/* ---- SECTION BASICS ---- */
section { padding: 100px 40px; }
.section-inner { max-width: 1080px; margin: 0 auto; }
.section-label {
  font-family: var(--font-head); font-size: 13px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--violet); margin-bottom: 14px;
}
.section-title {
  font-family: var(--font-head); font-size: clamp(28px, 4vw, 42px);
  font-weight: 800; line-height: 1.15; letter-spacing: -0.02em;
  max-width: 600px; margin-bottom: 16px;
}
.section-desc {
  font-size: 17px; line-height: 1.6;
  color: rgba(27,27,47,0.55);
  max-width: 500px; margin-bottom: 48px;
}

/* .section-title--wide / .section-desc--wide: for a section-title whose
   text doesn't fit the standard 600px column at the clamp()'s upper font
   size without an orphan word wrapping onto its own line. Widens the desc
   to match so it doesn't read as a narrower column than the heading above
   it. Used by /for/businesses's "Scan the van. Land on the page." section --
   longer than every other page's section-title. */
.section-title--wide { max-width: 760px; }
.section-desc--wide { max-width: 640px; }

/* ---- PERSONA STORIES ---- */
.personas { background: white; }
.persona-tabs {
  display: flex; gap: 6px; margin-bottom: 40px;
  flex-wrap: wrap;
}
.persona-tab {
  padding: 10px 20px;
  border-radius: 100px;
  border: 1.5px solid rgba(27,27,47,0.1);
  background: transparent;
  font-family: var(--font-body);
  font-size: 14px; font-weight: 500;
  color: rgba(27,27,47,0.5);
  cursor: pointer;
  transition: all 0.25s;
}
.persona-tab:hover { border-color: var(--violet); color: var(--violet); }
.persona-tab.active {
  background: var(--violet); color: white;
  border-color: var(--violet);
}
.persona-panels { position: relative; }
.persona-panel {
  display: none;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
  align-items: center;
  animation: fadeUp 0.4s ease;
}
.persona-panel.active { display: grid; }
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: translateY(0); }
}
.persona-pain {
  font-size: 22px; font-family: var(--font-head);
  font-weight: 700; line-height: 1.35;
  margin-bottom: 16px; color: var(--deep-night);
}
.persona-solution {
  font-size: 15px; line-height: 1.7;
  color: rgba(27,27,47,0.55);
  margin-bottom: 24px;
}
.persona-bullets {
  list-style: none; margin-bottom: 28px;
}
.persona-bullets li {
  font-size: 14px; padding: 7px 0;
  display: flex; align-items: flex-start; gap: 10px;
  color: rgba(27,27,47,0.65);
}
.persona-bullets li::before {
  content: '';
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--mint); flex-shrink: 0;
  margin-top: 7px;
}
.persona-cta {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 14px; font-weight: 600;
  color: var(--violet); text-decoration: none;
  transition: gap 0.2s;
}
.persona-cta:hover { gap: 12px; }

/* Persona phone mockup */
.persona-visual {
  display: flex; justify-content: center;
  position: relative;
}
.persona-phone {
  width: 260px;
  background: var(--deep-night);
  border-radius: 32px;
  padding: 10px;
  box-shadow: 0 32px 64px rgba(27,27,47,0.15);
}
.persona-screen {
  background: linear-gradient(180deg, #2A2A48 0%, #1B1B2F 100%);
  border-radius: 24px;
  padding: 24px 16px 20px;
  min-height: 360px;
}
.ps-notch {
  width: 80px; height: 4px;
  background: rgba(255,255,255,0.12);
  border-radius: 10px;
  margin: 0 auto 20px;
}
.ps-avatar {
  width: 52px; height: 52px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 10px;
  font-family: var(--font-head); font-weight: 700;
  font-size: 18px; color: white;
}
.ps-name {
  font-family: var(--font-head); font-weight: 700;
  font-size: 16px; color: white; text-align: center;
  margin-bottom: 3px;
}
.ps-bio {
  font-size: 11px; color: rgba(255,255,255,0.45);
  text-align: center; margin-bottom: 18px;
}
.ps-link {
  display: block;
  background: rgba(255,255,255,0.07);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 10px;
  padding: 11px 14px;
  margin-bottom: 8px;
  text-align: center;
  font-size: 13px; font-weight: 500;
  color: white; text-decoration: none;
}
.ps-link.mint {
  background: var(--mint); color: var(--deep-night);
  border-color: transparent; font-weight: 600;
}
.ps-link.schedule {
  border: 1px dashed rgba(162,155,254,0.4);
  color: var(--lilac); font-size: 11px;
  background: rgba(162,155,254,0.06);
}

/* ---- HOW IT WORKS (visual journey) ---- */
.how-section { background: var(--ghost); }
.how-flow {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0; position: relative;
}
.how-flow::before {
  content: '';
  position: absolute; top: 80px; left: 16.6%; right: 16.6%;
  height: 2px;
  background: linear-gradient(90deg, var(--lilac) 0%, var(--violet) 50%, var(--mint) 100%);
  opacity: 0.25;
}
.how-step {
  text-align: center; position: relative;
  padding: 0 20px;
}
.how-dot {
  width: 40px; height: 40px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 20px;
  font-family: var(--font-head); font-weight: 800; font-size: 16px;
  position: relative; z-index: 1;
}
.how-dot.d1 { background: var(--lilac); color: white; }
.how-dot.d2 { background: var(--violet); color: white; }
.how-dot.d3 { background: var(--mint); color: var(--deep-night); }
.how-visual {
  width: 200px; height: 140px;
  margin: 0 auto 20px;
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  position: relative; overflow: hidden;
}
.how-url-bar {
  background: white;
  border: 1.5px solid rgba(27,27,47,0.1);
  border-radius: 10px;
  padding: 10px 14px;
  display: flex; align-items: center; gap: 8px;
  width: 180px;
}
.how-url-bar .lock {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--mint); flex-shrink: 0;
}
.how-url-text {
  font-family: var(--font-head); font-size: 12px; font-weight: 600;
  color: var(--deep-night);
}
.how-url-text .typing {
  color: var(--violet);
  border-right: 2px solid var(--violet);
  animation: blink 1s step-end infinite;
  padding-right: 1px;
}
@keyframes blink { 50% { border-color: transparent; } }
.how-links-visual {
  display: flex; flex-direction: column; gap: 6px;
  width: 180px;
}
.how-link-item {
  background: white;
  border: 1px solid rgba(27,27,47,0.08);
  border-radius: 8px;
  padding: 8px 10px;
  display: flex; align-items: center; gap: 8px;
  font-size: 11px; font-weight: 500;
}
.how-link-dot {
  width: 8px; height: 8px; border-radius: 50%;
  flex-shrink: 0;
}
.how-link-item.dragging {
  border-color: var(--violet);
  box-shadow: 0 4px 12px rgba(91,75,212,0.15);
  transform: rotate(-1deg);
}
.how-share-visual {
  display: flex; gap: 12px; align-items: center;
}
.how-qr {
  width: 64px; height: 64px;
  background: white;
  border: 1px solid rgba(27,27,47,0.08);
  border-radius: 8px;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-template-rows: repeat(5, 1fr);
  gap: 2px; padding: 6px;
}
.how-qr span { border-radius: 1px; }
.how-qr span.f { background: var(--deep-night); }
.how-qr span.e { background: transparent; }
.how-share-targets {
  display: flex; flex-direction: column; gap: 4px;
}
.how-target {
  font-size: 10px; font-weight: 500;
  padding: 5px 10px; border-radius: 6px;
  background: white;
  border: 1px solid rgba(27,27,47,0.06);
  display: flex; align-items: center; gap: 5px;
}
.how-target .arrow { color: var(--mint); font-weight: 700; }
.how-step h3 {
  font-family: var(--font-head); font-weight: 700;
  font-size: 17px; margin-bottom: 6px;
}
.how-step p {
  font-size: 13px; color: rgba(27,27,47,0.5);
  line-height: 1.5;
}
.how-time {
  display: inline-block;
  font-size: 11px; font-weight: 700;
  font-family: var(--font-head);
  color: var(--mint-dark);
  background: rgba(0,229,160,0.1);
  padding: 3px 10px; border-radius: 100px;
  margin-top: 10px;
}

/* ---- USE CASES ---- */
.use-cases { background: var(--deep-night); color: white; }
.use-cases .section-label { color: var(--mint); }
.use-cases .section-title { color: white; }
.use-cases .section-desc { color: rgba(255,255,255,0.4); }
.case-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
.case-card {
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.07);
  border-radius: 16px;
  padding: 28px 22px;
  transition: background 0.2s, transform 0.2s;
  text-decoration: none;
}
.case-card:hover {
  background: rgba(255,255,255,0.07);
  transform: translateY(-3px);
}
.case-icon {
  width: 44px; height: 44px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 16px;
}
.case-card h3 {
  font-family: var(--font-head); font-weight: 700;
  font-size: 16px; margin-bottom: 6px; color: white;
}
.case-card p {
  font-size: 13px; line-height: 1.5;
  color: rgba(255,255,255,0.4);
  margin-bottom: 14px;
}
.case-link {
  font-size: 12px; font-weight: 600;
  color: var(--mint); display: flex; align-items: center; gap: 4px;
}

/* ---- PRICING ---- */
.pricing { background: white; text-align: center; }
.pricing .section-title { margin-left: auto; margin-right: auto; }
.pricing .section-desc { margin-left: auto; margin-right: auto; }
/* auto-fit, not repeat(2): the cards come from tier_marketing now, so the
   grid must not be the one place that still assumes exactly two. Two rows
   land in two equal columns at 720px, exactly as before. */
.price-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 20px; max-width: 720px; margin: 0 auto;
}

/* tier_marketing.palette (d_tier_palette) picks a card's accent, so a new
   tier is a row rather than a CSS edit. The defaults below reproduce what
   was hardcoded before: Free violet, Pro mint. */
.price-card { --pc-accent: var(--violet); --pc-accent-dark: var(--violet-dark); }
.price-card.pal-violet { --pc-accent: var(--violet); --pc-accent-dark: var(--violet-dark); }
.price-card.pal-mint   { --pc-accent: var(--mint);   --pc-accent-dark: var(--mint-dark); }
.price-card.pal-amber  { --pc-accent: var(--amber);  --pc-accent-dark: var(--amber-dark); }
.price-card.pal-lilac  { --pc-accent: var(--lilac);  --pc-accent-dark: var(--violet); }
.price-card {
  background: var(--ghost);
  border: 2px solid rgba(27,27,47,0.06);
  border-radius: 20px;
  padding: 36px 32px;
  text-align: left;
  transition: transform 0.2s;
}
.price-card:hover { transform: translateY(-4px); }
.price-card.featured {
  background: var(--deep-night); color: white;
  border-color: var(--violet);
}
.price-tier {
  font-family: var(--font-head); font-weight: 700;
  font-size: 14px; letter-spacing: 0.04em; text-transform: uppercase;
  margin-bottom: 8px;
}
.price-card.featured .price-tier { color: var(--pc-accent); }
.price-amount {
  font-family: var(--font-head); font-weight: 800;
  font-size: 42px; margin-bottom: 4px;
}
.price-amount span { font-size: 16px; font-weight: 500; opacity: 0.5; }
.price-desc {
  font-size: 14px; margin-bottom: 24px;
  opacity: 0.5;
}
.price-features { list-style: none; margin-bottom: 28px; }
.price-features li {
  font-size: 14px; padding: 8px 0;
  display: flex; align-items: center; gap: 10px;
}
.price-check {
  width: 18px; height: 18px; border-radius: 50%;
  background: rgba(0,229,160,0.15);
  flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
.price-check svg { width: 10px; height: 10px; }
.price-card.featured .price-features li { color: rgba(255,255,255,0.7); }
.price-btn {
  display: block; width: 100%; padding: 14px;
  border-radius: 12px; border: 2px solid rgba(27,27,47,0.1);
  background: white; color: var(--deep-night);
  font-size: 15px; font-weight: 600; cursor: pointer;
  font-family: var(--font-body);
  text-align: center; text-decoration: none;
  transition: all 0.2s;
}
.price-btn:hover { border-color: var(--violet); color: var(--violet); }
.price-card.featured .price-btn {
  background: var(--pc-accent); border-color: var(--pc-accent);
  color: var(--deep-night);
}
.price-card.featured .price-btn:hover {
  background: var(--pc-accent-dark); border-color: var(--pc-accent-dark);
}
/* THE ADD-ON STRIP, under the tier grid.

   Deliberately not a fourth card. It is the same 720px column as .price-grid
   so it lines up under it, but it reads as a row of small items rather than
   as an option in the same league as a plan -- which is the whole reason it
   is not in that grid. auto-fill, not auto-fit, so two blocks stay the width
   three would be rather than stretching to fill the row. */
.addon-strip {
  max-width: 720px; margin: 24px auto 0;
  padding: 18px 20px; border-radius: 16px;
  background: rgba(91,75,212,0.045);
  border: 1px solid rgba(91,75,212,0.12);
}
.addon-intro { display: flex; flex-direction: column; gap: 3px; margin-bottom: 14px; }
.addon-intro strong { font-family: var(--font-head); font-size: 15px; color: var(--deep-night); }
.addon-intro span { font-size: 13px; color: rgba(27,27,47,0.6); line-height: 1.5; }
.addon-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 12px;
}
.addon-item {
  background: #fff; border: 1px solid rgba(27,27,47,0.08);
  border-radius: 12px; padding: 12px 14px;
}
.addon-name {
  font-size: 13px; font-weight: 600; color: var(--deep-night);
  margin-bottom: 4px;
}
.addon-price {
  font-family: var(--font-head); font-size: 20px; font-weight: 700;
  color: var(--violet); line-height: 1.1;
}
/* The period, small beside the number -- the same treatment .price-amount
   gives it, because the two are read together on one page. */
.addon-price span { font-size: 12px; font-weight: 500; color: rgba(27,27,47,0.45); }
.addon-blurb { margin-top: 5px; font-size: 12px; color: rgba(27,27,47,0.55); line-height: 1.45; }
.addon-ask {
  margin: 14px 0 0; font-size: 13px; color: rgba(27,27,47,0.6); line-height: 1.5;
}
.addon-ask a { color: var(--violet); font-weight: 600; }

.price-note {
  text-align: center;
  margin-top: 20px;
  font-size: 14px;
  color: rgba(27,27,47,0.4);
}
.price-note strong { color: var(--violet); }

/* ---- FINAL CTA ---- */
.final-cta {
  background: var(--ghost);
  text-align: center;
  padding: 100px 40px;
}
.cta-box {
  max-width: 640px; margin: 0 auto;
  background: var(--violet);
  border-radius: 28px;
  padding: 64px 48px;
  position: relative; overflow: hidden;
}
.cta-box::before {
  content: '';
  position: absolute; top: -60px; right: -60px;
  width: 200px; height: 200px;
  background: radial-gradient(circle, rgba(162,155,254,0.3), transparent 70%);
}
.cta-box::after {
  content: '';
  position: absolute; bottom: -40px; left: -40px;
  width: 160px; height: 160px;
  background: radial-gradient(circle, rgba(0,229,160,0.2), transparent 70%);
}
.cta-box h2 {
  font-family: var(--font-head); font-weight: 800;
  font-size: clamp(28px, 4vw, 38px);
  color: white; margin-bottom: 14px;
  position: relative; z-index: 1;
}
.cta-box p {
  font-size: 16px; color: rgba(255,255,255,0.7);
  margin-bottom: 32px;
  position: relative; z-index: 1;
}
.cta-form {
  display: flex; align-items: center;
  max-width: 400px; margin: 0 auto;
  background: rgba(255,255,255,0.15);
  border: 1px solid rgba(255,255,255,0.2);
  border-radius: 12px; padding: 5px;
  position: relative; z-index: 1;
}
.cta-form .prefix {
  font-size: 13px; font-weight: 600; color: rgba(255,255,255,0.6);
  padding: 0 2px 0 12px; white-space: nowrap;
  font-family: var(--font-head);
}
.cta-form input {
  flex: 1; border: none; outline: none;
  background: none; color: white;
  font-size: 15px; font-family: var(--font-body);
  padding: 10px 8px; min-width: 0;
}
.cta-form input::placeholder { color: rgba(255,255,255,0.35); }
.cta-form button {
  background: var(--mint); color: var(--deep-night);
  border: none; border-radius: 8px; padding: 10px 24px;
  font-size: 14px; font-weight: 700; cursor: pointer;
  font-family: var(--font-body);
  transition: background 0.2s;
  white-space: nowrap;
}
.cta-form button:hover { background: var(--mint-dark); }

/* ---- FOOTER ---- */
.em-footer {
  background: var(--deep-night);
  color: rgba(255,255,255,0.4);
  padding: 48px 40px;
}
/* Two rows: .footer-top (logo + the "For..." persona group) above
   .footer-links (the generic site links), separated by a hairline so the
   footer reads as two rows rather than one long wrapped one. */
.footer-inner { max-width: 1080px; margin: 0 auto; }
.footer-top {
  display: flex; justify-content: space-between; align-items: center;
  flex-wrap: wrap; gap: 16px;
  padding-bottom: 20px;
}
.footer-logo {
  font-family: var(--font-head); font-weight: 700;
  font-size: 18px; color: white;
}
.footer-logo .dot { color: var(--lilac); }
.footer-group { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 20px; }
.footer-group-label {
  font-family: var(--font-head); font-size: 12px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: rgba(255,255,255,0.3);
}
.footer-group a, .footer-links a {
  font-size: 13px; color: rgba(255,255,255,0.4);
  text-decoration: none; transition: color 0.2s;
}
.footer-group a:hover, .footer-links a:hover { color: rgba(255,255,255,0.7); }
.footer-links {
  display: flex; gap: 12px 28px; flex-wrap: wrap;
  padding-top: 20px;
  border-top: 1px solid rgba(255,255,255,0.08);
}

/* ---- AUTH PAGES ---- */
.auth-container {
  min-height: 100vh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 40px 20px;
}
.auth-logo { margin-bottom: 32px; }
.auth-card {
  background: white;
  border: 1px solid rgba(27,27,47,0.08);
  border-radius: 20px;
  padding: 40px 36px;
  width: 100%;
  max-width: 420px;
  box-shadow: 0 4px 24px rgba(27,27,47,0.04);
}
.auth-card h1 {
  font-family: var(--font-head); font-weight: 800;
  font-size: 24px; margin-bottom: 8px;
  text-align: center;
}
.auth-card .subtitle {
  font-size: 14px; color: rgba(27,27,47,0.5);
  text-align: center; margin-bottom: 28px;
}

/* ---- FORMS ---- */
.form-group { margin-bottom: 18px; }
/* Direct child, deliberately. This rule is the FIELD CAPTION — the "Display
   name" sitting immediately above its input — and it was reaching every
   nested <label> too: the avatar's checkbox (stacking its box above its text)
   and the camera button (6px of bottom margin, 3px off the avatar's centre).

   The first attempt was label:not(.em-check), which made it worse: :not()
   takes the specificity of its argument, so that selector scored 0,2,1 and
   started outranking .avatar-actions .avatar-action-btn at 0,2,0. The child
   combinator adds no specificity at all and says what is actually meant. */
.form-group > label {
  display: block;
  font-size: 13px; font-weight: 600; color: var(--deep-night);
  margin-bottom: 6px;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group input[type="tel"],
.form-group input[type="password"],
.form-group input[type="url"],
/* number was missing until 5 Sep 2026, which is why the access code's
   "Remember for" field arrived as a raw browser control in the middle of a
   styled form. This list is opt-in by type, so every future input type is
   unstyled by default -- the failure is silent and looks like a missing rule
   rather than a missing selector. */
.form-group input[type="number"],
.form-group textarea {
  width: 100%; box-sizing: border-box;
  padding: 12px 14px;
  border: 2px solid rgba(27,27,47,0.1);
  border-radius: 10px;
  font-size: 15px; font-family: var(--font-body);
  color: var(--deep-night);
  background: var(--ghost);
  transition: border-color 0.2s;
}
.form-group textarea {
  resize: vertical; min-height: 80px;
}

/* A day count is at most three digits. Full width reads as though a long
   value is expected and leaves the unit stranded at the far side of the
   field from the number. */
.form-group input[type="number"] {
  width: 8ch;
  min-width: 8ch;
}
.form-group input:focus,
.form-group textarea:focus {
  outline: none;
  border-color: var(--violet);
}
/* Password reveal. The button sits inside the field rather than beside it so
   the input keeps its full width and the layout is unchanged when absent. */
.password-field { position: relative; }
.password-field input { padding-right: 48px !important; }
.password-toggle {
  position: absolute; top: 0; right: 0;
  width: 44px; height: 100%;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 0; margin: 0;
  color: rgba(27,27,47,0.45);
  cursor: pointer;
  border-radius: 0 10px 10px 0;
  transition: color 0.2s;
}
.password-toggle:hover { color: var(--violet); }
.password-toggle:focus-visible {
  outline: 2px solid var(--violet); outline-offset: -2px; color: var(--violet);
}
/* One icon at a time — the crossed-out eye means "currently visible". */
.password-toggle .pw-eye-off { display: none; }
.password-field.is-revealed .pw-eye { display: none; }
.password-field.is-revealed .pw-eye-off { display: block; }
.password-field.is-revealed .password-toggle { color: var(--violet); }

.input-with-prefix {
  display: flex; align-items: center;
  background: var(--ghost);
  border: 2px solid rgba(27,27,47,0.1);
  border-radius: 10px;
  transition: border-color 0.2s;
}
.input-with-prefix:focus-within { border-color: var(--violet); }
.input-prefix {
  font-size: 14px; font-weight: 600; color: var(--lilac);
  padding: 0 0 0 14px; white-space: nowrap;
  font-family: var(--font-head);
}
.input-with-prefix input {
  flex: 1; border: none !important; outline: none; background: none !important;
  font-size: 15px; font-family: var(--font-body);
  padding: 12px 14px 12px 4px; color: var(--deep-night);
  min-width: 0;
}
.btn-primary {
  display: block; width: 100%;
  padding: 14px;
  background: var(--violet); color: white;
  border: none; border-radius: 12px;
  font-size: 15px; font-weight: 600;
  font-family: var(--font-body);
  cursor: pointer;
  transition: background 0.2s, transform 0.15s;
}
.btn-primary:hover { background: var(--violet-dark); transform: translateY(-1px); }
/* .btn-primary was written for <button>, which centres its own text and has no
   underline. On an <a> -- the verified page, and anything else that links
   rather than submits -- the browser defaults reassert themselves and it comes
   out underlined and left-aligned. Declared here rather than as a separate
   anchor variant so a link styled as a button matches a real one everywhere,
   and so hover stays the shared behaviour (the background moves) rather than
   an underline appearing on one page and not the others. */
.btn-primary {
  text-decoration: none;
  text-align: center;
  box-sizing: border-box;
}
/* Space between a closing paragraph and the action it leads to. Only where a
   button follows prose directly -- the forms already get theirs from
   .form-group. */
.btn-primary--spaced { margin-top: 28px; }
/* Amber, for an action that resolves an outstanding state rather than moving
   the user forward — the resend on Settings while the address is unverified.
   Same amber as .flash-warn and the old Unverified badge, so the page reads as
   one system: nothing has gone wrong, there is simply something still to do.
   Violet would say "this is the next step here", which on a settings page it
   is not, and red would claim a fault that does not exist.

   Dark brown text, not white. White on amber fails contrast at this size —
   the one place where copying the violet button verbatim would have looked
   right and been unreadable. */
.btn-primary--warn {
  background: var(--amber);
  color: #4A2C00;
}
.btn-primary--warn:hover { background: var(--amber-dark); }

/* Honeypot. Off-screen rather than display:none or visibility:hidden — both
   of those are trivially detected, and some scripts skip them for that reason.
   Nothing in here is focusable or announced. */
.em-hp {
  position: absolute !important;
  left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

.user-avatar {
  border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 700;
  /* relative + hidden so the img fills the circle and is clipped by it */
  position: relative; overflow: hidden;
  background: var(--violet); color: #fff;
}
.user-avatar img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}

.referral-intro {
  margin: 0 0 20px; padding: 14px 18px;
  border-radius: 12px;
  background: rgba(91,75,212,0.06);
  border: 1px solid rgba(91,75,212,0.12);
  /* A row now that it carries the referrer's face beside their name. */
  display: flex; align-items: center; gap: 12px;
}
/* The :empty collapse is gone: the partial emits nothing at all on the plain
   form, so there is no empty div to hide and the reason no longer lives in a
   different file from the markup. */
.referral-intro .user-avatar { width: 44px; height: 44px; font-size: 15px; }
.referral-intro-body { min-width: 0; }
.referral-intro-who { margin: 0; font-size: 14px; color: var(--deep-night); }
.referral-intro-msg {
  margin: 6px 0 0; font-size: 14px; line-height: 1.5;
  color: rgba(27,27,47,0.65);
}

/* Collapsible section. Padding matches .settings-section, and the summary is
   the whole clickable row rather than just the text. */
.settings-disclosure { padding-top: 0; padding-bottom: 0; }
.settings-disclosure[open] { padding-bottom: 24px; }
.settings-summary {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 20px 0; cursor: pointer;
  list-style: none;
}
/* Both spellings: WebKit needs the pseudo-element, everything else the
   list-style above. Without them the native triangle sits beside our chevron. */
.settings-summary::-webkit-details-marker { display: none; }
.settings-summary::marker { content: ''; }
/* 15px and the open-state border deliberately mirror .settings-section h3, so
   an expanded disclosure reads as the same kind of block as its neighbours. */
.settings-summary-title {
  font-family: var(--font-head); font-size: 15px; font-weight: 600;
  color: var(--deep-night);
}
.settings-disclosure[open] .settings-summary {
  padding-bottom: 12px; margin-bottom: 16px;
  border-bottom: 1px solid rgba(27,27,47,0.06);
}
.settings-summary-action {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 14px; font-weight: 600; color: var(--violet);
}
.settings-chev { transition: transform 0.15s; }
.settings-disclosure[open] .settings-chev { transform: rotate(180deg); }
.settings-summary:focus-visible {
  outline: 2px solid var(--violet); outline-offset: 4px; border-radius: 8px;
}

/* The timezone form is one row; the password form is a stack. */
.settings-form--stack { display: block; max-width: 340px; }
.settings-form--stack .form-group { margin-bottom: 14px; }
.settings-form--stack input[type="password"] { width: 100%; box-sizing: border-box; }
.form-footer {
  text-align: center; margin-top: 20px;
  font-size: 14px; color: rgba(27,27,47,0.5);
}
.form-footer a { color: var(--violet); text-decoration: none; font-weight: 500; }
.form-footer a:hover { text-decoration: underline; }
/* Between the two "I cannot get in" links. Quieter than either of them, so
   the row reads as two choices rather than three things. */
.form-footer-sep { margin: 0 6px; color: rgba(27,27,47,0.3); }

/* ---- LEGAL PAGES (Privacy, Terms) ---- */
.legal-hero {
  padding: 130px 40px 40px;
  text-align: center;
}
.legal-hero h1 {
  font-family: var(--font-head);
  font-size: clamp(32px, 4vw, 48px);
  font-weight: 800; line-height: 1.1;
  letter-spacing: -0.03em;
}
.legal-hero h1 .accent { color: var(--violet); }
.legal-hero .updated {
  font-size: 14px; color: rgba(27,27,47,0.45);
  margin-top: 12px;
}
.legal-hero .intro {
  font-size: 17px; line-height: 1.7;
  color: rgba(27,27,47,0.6);
  max-width: 800px; margin: 20px auto 0;
}
.legal-body {
  max-width: 960px;
  margin: 0 auto;
  padding: 40px 40px 100px;
}
.legal-body h2 {
  font-family: var(--font-head);
  font-size: 22px; font-weight: 700;
  margin: 48px 0 16px;
  color: var(--deep-night);
}
.legal-body h2:first-child { margin-top: 0; }
.legal-body h3 {
  font-family: var(--font-head);
  font-size: 17px; font-weight: 600;
  margin: 28px 0 10px;
  color: var(--violet);
}
.legal-body p {
  font-size: 15px; line-height: 1.8;
  color: rgba(27,27,47,0.75);
  margin-bottom: 14px;
}
.legal-body ul, .legal-body ol {
  margin: 0 0 16px 20px;
  padding: 0;
}
.legal-body li {
  font-size: 15px; line-height: 1.8;
  color: rgba(27,27,47,0.75);
  margin-bottom: 6px;
}
.legal-body li::marker { color: var(--mint); }
.legal-body a {
  color: var(--violet);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.legal-body a:hover { color: #4A3CB0; }
.data-table {
  width: 100%;
  border-collapse: collapse;
  margin: 16px 0 24px;
  font-size: 14px;
}
.data-table th {
  font-family: var(--font-head);
  font-weight: 600; font-size: 13px;
  text-align: left; padding: 10px 14px;
  background: var(--deep-night);
  color: var(--ghost);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.data-table th:first-child { border-radius: 8px 0 0 0; }
.data-table th:last-child { border-radius: 0 8px 0 0; }
.data-table td {
  padding: 10px 14px;
  border-bottom: 1px solid rgba(27,27,47,0.06);
  color: rgba(27,27,47,0.75);
  line-height: 1.6;
}
.data-table tr:last-child td { border-bottom: none; }
.data-table tr:nth-child(even) td { background: rgba(91,75,212,0.03); }
.callout {
  background: rgba(91,75,212,0.06);
  border-left: 3px solid var(--violet);
  border-radius: 0 10px 10px 0;
  padding: 16px 20px;
  margin: 20px 0;
}
.callout p { margin: 0; font-size: 14px; color: rgba(27,27,47,0.7); }
.tier-comparison {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin: 20px 0 24px;
}
.tier-box {
  border: 1px solid rgba(27,27,47,0.08);
  border-radius: 12px;
  padding: 20px;
  background: white;
}
.tier-box.pro {
  border-color: var(--violet);
  background: rgba(91,75,212,0.03);
}
.tier-box h4 {
  font-family: var(--font-head);
  font-weight: 700; font-size: 16px;
  margin-bottom: 10px;
}
.tier-box.pro h4 { color: var(--violet); }
.tier-box ul { margin: 0 0 0 16px; }
.tier-box li { font-size: 13px; line-height: 1.7; }

/* ---- PUBLIC USER PAGE ----

   Themed with custom properties, not with per-theme rules on every component.

   The three built-ins set the variables; every component below reads them. A
   Pro custom theme is then an inline `style` on .em-page setting the same
   variables, which wins by the cascade without a single !important and
   without any user value ever reaching a selector or a property name.

   The reason it is variables and not a second set of rules: a page with NO
   custom theme emits no style attribute at all, so it renders exactly as it
   did before this feature existed. Nothing to diff, nothing to regress.

   Keep the variable list here and the list EM.Themes.StyleAttributeValue
   emits in step. A variable it emits that nothing here reads is dead, and a
   variable read here that it never emits silently falls back. */
.em-page {
  --em-radius: 14px;
  --em-head-font: var(--font-head);
  --em-body-font: inherit;
  /* The bio is dimmed rather than coloured by default. A theme that sets a
     real muted colour also sets this to 1 -- otherwise its chosen colour
     would be shown at 60% and read as the wrong colour. */
  --em-muted-opacity: 0.6;
  /* Base values for the accent role. Every built-in restates them; these
     exist so a page whose theme attribute is missing or unknown still gets a
     readable stand-out button rather than a transparent one. */
  --em-accent-link: var(--mint);
  --em-accent-link-fg: var(--deep-night);

  /* 100vh, AND NOT 100dvh. Tried the other way on 18 Sep 2026 and reverted
     within the hour, so the reasoning is worth keeping.

     On Chrome for Android 100vh is the LARGE viewport height -- what the page
     would be with the address bar hidden -- so while the bar is SHOWING the
     page is taller than the visible area and simply scrolls. It cannot leave
     a gap at the bottom.

     100dvh is the CURRENT height, which is SMALLER while the bar is showing.
     On a short page -- and a profile with two links is short -- the document
     then ends exactly where the bar begins, and the moment the viewport grows
     there is nothing under it but the near-white body background. Measured on
     a Pixel 10: the bottom went white, worse than before.

     Too big is harmless here; too small is the bug. */
  min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  padding: 40px 20px;
  background: var(--em-bg);
  color: var(--em-fg);
  font-family: var(--em-body-font);
}
.em-page[data-theme="dark"] {
  --em-bg: linear-gradient(180deg, #2A2A48 0%, var(--deep-night) 100%);
  --em-fg: #ffffff;
  --em-surface: rgba(255,255,255,0.08);
  --em-surface-hover: rgba(255,255,255,0.12);
  --em-link-border: rgba(255,255,255,0.1);
  --em-accent: var(--violet);
  --em-accent-fg: #ffffff;
  --em-accent-link: var(--mint);
  --em-accent-link-fg: var(--deep-night);
}
.em-page[data-theme="light"] {
  --em-bg: var(--ghost);
  --em-fg: var(--deep-night);
  --em-surface: #ffffff;
  --em-surface-hover: #ffffff;
  --em-link-border: rgba(27,27,47,0.08);
  --em-accent: var(--violet);
  --em-accent-fg: #ffffff;
  --em-accent-link: var(--mint);
  --em-accent-link-fg: var(--deep-night);
}
.em-page[data-theme="violet"] {
  --em-bg: linear-gradient(180deg, var(--violet) 0%, var(--violet-dark) 100%);
  --em-fg: #ffffff;
  --em-surface: rgba(255,255,255,0.12);
  --em-surface-hover: rgba(255,255,255,0.18);
  --em-link-border: rgba(255,255,255,0.15);
  --em-accent: var(--mint);
  --em-accent-fg: var(--deep-night);
  --em-accent-link: var(--mint);
  --em-accent-link-fg: var(--deep-night);
}
.em-page-inner {
  width: 100%; max-width: 420px;
  text-align: center;
}
.em-avatar {
  width: 80px; height: 80px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 16px;
  font-family: var(--em-head-font); font-weight: 700;
  font-size: 28px;
  color: var(--em-accent-fg);
  background: var(--em-accent);
  position: relative; overflow: hidden;
}
.em-name {
  font-family: var(--em-head-font); font-weight: 800;
  font-size: 24px; margin-bottom: 8px;
}
.em-bio {
  font-size: 15px;
  color: var(--em-muted, inherit);
  opacity: var(--em-muted-opacity);
  margin-bottom: 32px; line-height: 1.5;
}
.em-links { margin-bottom: 40px; }
.em-link {
  /* Flex, not block, so an icon can sit beside the text without either one
     leaving the centre. justify-content does the centring the old
     text-align did; text-align stays for the wrapped-title case. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px 20px;
  margin-bottom: 12px;
  border-radius: var(--em-radius);
  text-align: center;
  font-size: 15px; font-weight: 500;
  text-decoration: none;
  transition: transform 0.15s, background 0.2s;
  /* --em-link-bg is set only by an outline or soft button fill; otherwise the
     theme's surface colour is the button. */
  background: var(--em-link-bg, var(--em-surface));
  border: 1px solid var(--em-link-border);
  color: var(--em-fg);
}
.em-link:hover {
  transform: translateY(-2px);
  background: var(--em-link-bg, var(--em-surface-hover));
}
/* The light theme lifts its cards with a shadow rather than a fill change,
   which is the one thing that is genuinely per-theme rather than per-colour. */
.em-page[data-theme="light"] .em-link { box-shadow: 0 2px 8px rgba(27,27,47,0.04); }
.em-page[data-theme="light"] .em-link:hover { box-shadow: 0 4px 16px rgba(27,27,47,0.08); }

/* ---- Link roles --------------------------------------------------------

   A role says what a link is FOR; the theme says what that looks like. Every
   value below comes from a custom property, so a role works on every theme --
   built-in and custom -- without either knowing about the other. Adding a
   role is a rule here plus a value in d_link_style. No Pascal.

   Why these override with !important: .em-link sets background and border
   from --em-link-bg / --em-link-border, which a theme's button FILL drives
   page-wide. A role has to win over that, because "make this one stand out"
   is a per-link decision and an outline fill would otherwise erase it. */

/* Stand out. --em-accent-link is brand mint on the built-in themes, because
   the standout style has to stand out on all three and a theme's own accent
   need not -- a violet page with a violet accent has no accent. A paid theme
   that sets an accent colour overrides it; see EM.Themes.BuildStyle. */
.em-link--accent {
  background: var(--em-accent-link) !important;
  color: var(--em-accent-link-fg) !important;
  border-color: transparent !important;
  font-weight: 600;
}
.em-link--accent:hover { filter: brightness(0.94); }

/* A quiet announcement rather than a destination: dashed, dimmed, smaller.
   Named for how it reads, not for scheduling -- a pending link does not
   render at all, so this was never a schedule state. */
.em-link--note {
  background: transparent !important;
  border-style: dashed !important;
  border-color: var(--em-muted, currentColor) !important;
  color: var(--em-muted, inherit);
  opacity: var(--em-muted-opacity);
  font-size: 13px; font-weight: 400;
  padding: 11px 16px;
}
.em-link--note:hover { transform: none; background: transparent !important; }

/* Sized in em so it tracks the role's font-size: the note role is smaller and
   its icon should be too. flex-shrink because a long title must not squash
   the icon into an ellipse. */
.em-link-icon {
  width: 1.2em; height: 1.2em;
  flex: 0 0 auto;
}

/* ---- Block types -------------------------------------------------------

   A page is not only a stack of buttons. A row can be a heading, a paragraph
   or a pointer at another member's page -- see d_link_type. Same rule as the
   link roles above: every value comes from a custom property, so these work
   on the three built-in themes and on any custom one without either knowing
   about the other. No Pascal, and no theme change.

   Margins are asymmetric on purpose. A heading belongs to what FOLLOWS it, so
   it takes a large space above and a small one below; get that the wrong way
   round and a group heading appears to caption the group before it. The first
   one loses its top margin, or every page that opens with a heading starts
   with a gap. */
.em-block-header,
.em-block-subheader {
  font-family: var(--em-head-font);
  color: var(--em-fg);
  text-align: center;
  margin: 28px 0 10px;
  line-height: 1.25;
}
.em-block-header { font-size: 19px; font-weight: 700; }
.em-block-subheader { font-size: 16px; font-weight: 600; margin-top: 20px; }
.em-links > :first-child.em-block-header,
.em-links > :first-child.em-block-subheader { margin-top: 0; }

/* Body copy, not a button: muted like the bio, and reading width rather than
   centred, because a sentence of explanation centred over three lines is
   harder to read than the same sentence ranged left. */
.em-block-text {
  font-size: 14px;
  line-height: 1.5;
  color: var(--em-muted, inherit);
  opacity: var(--em-muted-opacity);
  margin: 0 0 16px;
  text-align: left;
}
/* The markdown a text block, a welcome message and an access-code prompt all
   emit. One rule set for the three of them: they are the same six constructs
   rendered by the same function, and three copies of this would drift the
   first time somebody adjusted a margin. */
.em-block-text p,
.em-bio p { margin: 0 0 10px; }
.em-block-text p:last-child,
.em-bio p:last-child { margin-bottom: 0; }
.em-block-text ul, .em-block-text ol,
.em-bio ul, .em-bio ol { margin: 0 0 10px; padding-left: 22px; }
.em-block-text li,
.em-bio li { margin-bottom: 4px; }
.em-block-text strong, .em-bio strong { font-weight: 700; }
.em-block-text em, .em-bio em { font-style: italic; }

/* A LIST INSIDE A CENTRED COLUMN. .em-bio is centred, and a centred bullet
   list puts every marker at a different distance from its text -- which is
   what the welcome message looked like the moment markdown was turned on for
   it. inline-block centres the list AS A BLOCK and leaves the items ranged
   left inside it, so the markers line up and the group still sits under the
   name. .em-block-text needs none of this: it is left-aligned already. */
.em-bio ul, .em-bio ol {
  display: inline-block;
  text-align: left;
  margin-left: 0; margin-right: 0;
}
.em-bio hr {
  border: 0;
  border-top: 1px solid var(--em-link-border);
  margin: 14px 0;
  opacity: 0.6;
}
/* A rule takes the button border colour rather than inventing one, so a
   divider matches the edges of the buttons above and below it on every
   theme -- including a custom one that changed them. */
.em-block-text hr {
  border: 0;
  border-top: 1px solid var(--em-link-border);
  margin: 16px 0;
  opacity: 0.6;
}

/* A profile block is a link wearing a face. The circle sits exactly where an
   icon sits, so a mixed list of links and profiles keeps one left edge for
   its marks -- which is what stops a drivers page looking like two lists that
   happen to be adjacent.

   flex: 0 0 auto for the same reason .em-link-icon has it: a long driver name
   must not squash the avatar into an ellipse. Sized in px rather than em
   because a face has a legible minimum and the note role's smaller text
   should not shrink it below that. */
.em-avatar--link {
  width: 32px; height: 32px;
  flex: 0 0 auto;
  font-size: 13px;
  margin: 0;
}
.em-link--profile { padding-top: 10px; padding-bottom: 10px; }
.em-branding {
  display: inline-block;
  font-family: var(--font-head); font-size: 13px; font-weight: 600;
  opacity: 0.3; text-decoration: none;
  transition: opacity 0.2s;
  color: var(--em-fg);
}
.em-branding:hover { opacity: 0.6; }

/* ---- DASHBOARD LAYOUT ---- */
.dash-topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 24px; height: 56px;
  background: white;
  border-bottom: 1px solid rgba(27,27,47,0.08);
  position: sticky; top: 0; z-index: 100;
}
.dash-topbar-left { display: flex; align-items: center; }
.dash-topbar-center { display: flex; align-items: center; gap: 4px; }
.dash-topbar-right { display: flex; align-items: center; gap: 14px; }
.dash-logo {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-head); font-weight: 700; font-size: 15px;
  color: var(--deep-night); text-decoration: none;
}
.dash-logo .dot { color: var(--violet); }
.dash-username { color: var(--violet); font-weight: 600; }
.dash-tab {
  padding: 6px 14px; border-radius: 8px;
  font-size: 13px; font-weight: 500;
  color: rgba(27,27,47,0.5); text-decoration: none;
  transition: color 0.2s, background 0.2s;
}
.dash-tab:hover { color: var(--deep-night); background: rgba(91,75,212,0.06); }
.dash-tab.active { color: var(--violet); background: rgba(91,75,212,0.1); }
/* A wrapper now, not the circle: .user-avatar is the circle, shared with the
   referral intro and both admin screens, and it carries the photo. Only the
   size is decided here. */
.dash-avatar { display: flex; }
.dash-avatar .user-avatar { width: 32px; height: 32px; font-size: 12px; }
.dash-logout {
  background: none; border: none; cursor: pointer;
  font-size: 13px; font-weight: 500; color: var(--deep-night);
  opacity: 0.6; padding: 6px 8px;
  font-family: var(--font-body);
}
.dash-logout:hover { opacity: 1; }
.dash-view-live {
  font-size: 13px; font-weight: 500; color: var(--violet);
  text-decoration: none; padding: 6px 12px;
  border: 1px solid rgba(91,75,212,0.2); border-radius: 8px;
  transition: background 0.2s;
}
.dash-view-live:hover { background: rgba(91,75,212,0.06); }

/* Tier badge */
.tier-badge {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.5px; padding: 3px 10px; border-radius: 20px;
  font-family: var(--font-head);
}
.tier-free {
  background: rgba(27,27,47,0.06); color: rgba(27,27,47,0.5);
}
.tier-pro {
  background: linear-gradient(135deg, var(--violet), #7C6CF0);
  color: white;
}

/* Dashboard body.

   The SINGLE owner of the gutter. Every screen used to add its own padding
   on top of this one, so the sections that did were inset further than the
   editor and the two families never lined up vertically. If a dashboard
   screen needs breathing room, it belongs here — not on the screen. */
.dash-body {
  background: var(--ghost); min-height: calc(100vh - 56px);
  padding: 24px;
}

/* Every top-level dashboard screen. One width, one place — see --dash-max.
   A new dashboard page gets its class added here and inherits the lot;
   it does not set a max-width of its own. */
.dash-onboarding,
.dash-editor,
.dash-editor-back,
.dash-pages,
.dash-analytics,
.dash-share,
.themes-page,
.settings-page {
  max-width: var(--dash-max);
  margin-left: auto;
  margin-right: auto;
}

/* Onboarding prompt */
.dash-onboarding {
  display: flex; align-items: center; gap: 14px;
  background: rgba(91,75,212,0.06);
  border: 1px solid rgba(91,75,212,0.12);
  border-radius: 12px; padding: 14px 20px;
  margin-bottom: 24px;
}
.onboarding-icon { font-size: 20px; }
.onboarding-text strong {
  display: block; font-size: 14px; font-weight: 600;
  color: var(--violet); margin-bottom: 2px;
}
.onboarding-text span {
  font-size: 13px; color: rgba(27,27,47,0.6);
}

/* Two-panel editor */
/* Owns the link add/edit inputs via their form= attribute so they are not
   part of #edit-form. Never rendered; display:none as well as [hidden] so
   no later rule can accidentally give it a box. */
#link-fields { display: none !important; }

.dash-editor {
  display: grid;
  /* THE EDITOR TAKES EVERYTHING LEFT OVER; the preview is a fixed 320. The
     editor's own floor is the 980px breakpoint further down, not a track
     minimum here -- see the note there for why the two are stated in that
     order.

     minmax(0, 1fr), NEVER a bare 1fr. `1fr` means minmax(AUTO, 1fr), and that
     auto floor is the column's min-content -- which #quick-add-track put at
     952px, so the two tracks asked for 952 + 24 + 320 = 1296 inside a 1100px
     container. A grid does not shrink to fit in that situation: it overflows,
     and as nothing here scrolls sideways the preview column was pushed
     clean out of the page and clipped.

     Measured 9 Sep 2026: broken at every width from 769px to about 1500px,
     and invisible above that only because a maximised window had room for
     the overflow to hang in. Which is exactly how it presented -- the panel
     vanishing "on resize" when nothing about the panel had changed; the
     viewport edge had arrived at it.

     The 768px block already said minmax(0, 1fr) and already carried a note
     explaining why. Same bug, same file, fixed once for the phone and left
     standing for the desktop. */
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 24px;
  align-items: start;
}
.dash-edit-panel {
  background: white; border-radius: 14px;
  border: 1px solid rgba(27,27,47,0.06);
  padding: 28px;
}
/* A panel heading needs air beneath it. Without this the theme editor's
   "New theme" sat directly on top of the first field caption and the two read
   as one lump. */
.dash-edit-panel > h2 { margin-bottom: 20px; }
.dash-actions {
  margin-top: 24px; display: flex; gap: 10px;
  /* Commit action at the end of the form, bottom-right of the card.
     Without this the 240px button sat against the left edge of a ~700px
     panel with nothing beside it. */
  justify-content: flex-end;
}
.dash-actions .btn-primary {
  flex: 0 1 240px;
}

.form-hint {
  font-size: 12px; color: rgba(27,27,47,0.4);
  margin-top: 6px;
}

/* Form section label */
.form-section-label {
  display: flex; align-items: center; justify-content: space-between;
  margin: 20px 0 10px; padding-bottom: 8px;
  border-bottom: 1px solid rgba(27,27,47,0.06);
}
.form-section-label span:first-child {
  font-size: 13px; font-weight: 600; color: var(--deep-night);
}
.link-count {
  font-size: 12px; color: rgba(27,27,47,0.4);
  font-family: var(--font-head); font-weight: 500;
}

/* Link editor cards */
.link-card {
  display: flex; align-items: center; gap: 10px;
  background: var(--ghost); border: 1px solid rgba(27,27,47,0.06);
  border-radius: 10px; padding: 10px 14px;
  margin-bottom: 8px;
}
.link-card .link-grip {
  color: rgba(27,27,47,0.2); cursor: grab; font-size: 14px;
}
.link-card .link-icon {
  font-size: 18px; width: 24px; text-align: center;
}
.link-card .link-info {
  flex: 1; min-width: 0;
}
.link-card .link-title {
  font-size: 13px; font-weight: 500; color: var(--deep-night);
  margin: 0;
}
.link-card .link-url {
  font-size: 11px; color: rgba(27,27,47,0.4);
  margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.link-card .link-actions {
  display: flex; gap: 4px;
  /* Without this the actions are a shrinkable flex item and a long link
     title squashes the buttons until the text runs over them. */
  flex-shrink: 0;
}
.link-card .link-actions button {
  flex: 0 0 34px;
  width: 34px; height: 34px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: none; cursor: pointer;
  color: rgba(27,27,47,0.4); padding: 0;
  transition: all 0.2s;
}
.link-card .link-actions button:hover { background: rgba(91,75,212,0.08); color: var(--violet); }
.link-card .link-actions button.link-delete { color: rgba(27,27,47,0.3); }
.link-card .link-actions button.link-delete:hover { background: rgba(231,76,60,0.08); color: #e74c3c; }

/* ── Preview: view as of ───────────────────────────────────────────────────
   Collapsed behind one checkbox. The preview is "now" almost always, and
   three permanent inputs above a phone mock would be clutter earning their
   keep about once a month. */
.preview-controls {
  margin: 0 0 10px;
  font-family: var(--font-body);
}
.preview-custom-check { font-size: 12px; min-height: 0; }
.preview-when {
  margin-top: 8px; padding: 10px;
  border-radius: 10px;
  background: rgba(91,75,212,0.05);
  border: 1px solid rgba(91,75,212,0.14);
}
.preview-when[hidden] { display: none; }
/* Date and time stay paired here for the same reason they do in the schedule
   tab — the preview panel is narrow, and a stranded time field reads as a
   separate control. */
.preview-when-row { display: flex; flex-wrap: nowrap; gap: 6px; }
.preview-when-row input[type="date"] { flex: 1 1 auto; min-width: 0; }
.preview-when input[type="date"],
.preview-when input[type="time"] {
  height: 32px; box-sizing: border-box; padding: 0 8px;
  border: 1px solid rgba(27,27,47,0.12); border-radius: 7px;
  background: white; font-family: var(--font-body); font-size: 12px;
  color: var(--deep-night);
}
.preview-when .preview-tz {
  width: 100%; max-width: none; margin-top: 6px;
  height: 32px; padding: 0 8px; font-size: 12px;
}
.preview-when .btn-link { margin-top: 8px; font-size: 12px; }

/* While an override is active the phone is not showing the live page, and it
   should not pretend to be. */
.preview-controls.is-overridden ~ .preview-phone-frame { opacity: 0.92; }
.preview-controls.is-overridden .preview-custom-check span { color: var(--violet); }

/* ── Link schedule: tabs, fields, badges ───────────────────────────────────
   The Schedule tab keeps two dates and seven checkboxes out of the way of the
   common case, which is a title and a URL and nothing else. */
.link-tabs {
  display: flex; gap: 4px; margin-bottom: 14px;
  border-bottom: 1px solid rgba(27,27,47,0.08);
}
.link-tab {
  background: none; border: none; cursor: pointer;
  padding: 6px 12px 8px; margin-bottom: -1px;
  font-family: var(--font-body); font-size: 13px; font-weight: 600;
  color: rgba(27,27,47,0.45);
  border-bottom: 2px solid transparent;
}
.link-tab:hover { color: var(--deep-night); }
.link-tab.is-active { color: var(--violet); border-bottom-color: var(--violet); }

.link-tab-panel { display: none; }
.link-tab-panel.is-active { display: block; }

/* ---- The Block type chooser --------------------------------------------

   [hidden] needs help here: .form-group is a flex container, and a display
   value set on the class beats the attribute's own UA rule. !important
   because this must win over every .form-group rule, present and future --
   a field the chosen type does not use showing anyway is a form that asks
   for a URL for a heading.

   The rows are hidden, never removed. They stay in the DOM and stay posted,
   so switching type and back does not lose what was typed -- and the server
   clears what the type does not use anyway (ApplyType), so a stale value
   cannot survive a save. */
[data-for-type][hidden] { display: none !important; }

.block-type select { width: 100%; }
.link-tab-panel textarea {
  width: 100%;
  font: inherit;
  resize: vertical;
  min-height: 88px;
}
/* Two labels in one group: the second needs air above it, or "... or somebody
   else's" reads as a caption for the dropdown rather than a second choice. */
.type-field label + label { display: block; margin-top: 12px; }

.sched-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 10px;
}
/* ── .em-check — the checkbox, wherever one appears ────────────────────────
   Native checkboxes honour accent-color too inconsistently across browsers to
   look like part of this product, so the input is hidden and a box is drawn
   in its place. Hidden by clipping, never display:none — it must stay
   focusable, keyboard-operable and visible to a screen reader.

   The box is a ::before on the LABEL, making it a flex child that
   align-items: center positions properly. An earlier version put it inside
   the text span, where it was an inline box sitting on the text baseline and
   needed a vertical-align fudge — which is why the label read as
   bottom-aligned and why it never quite lined up with the inputs beside it.
   If this needs a magic pixel offset again, it has been done wrong again.

   FormControlHeight keeps the box, the text and any input on the same row
   sharing one centre line. */
.em-check {
  position: relative;
  /* inline-GRID, not inline-flex. Two explicit tracks — an 18px box and the
     text — cannot be collapsed or re-stacked by whatever the parent is doing,
     which flex items can be and were: this label lives in a column flex
     container nested in a row flex container, and when the outer row ran
     short the box ended up on its own line above the text. A grid track of a
     fixed size is a fixed size. */
  display: inline-grid;
  grid-template-columns: 18px auto;
  align-items: center;
  gap: 9px;
  min-height: 38px;
  cursor: pointer; user-select: none;
  font-family: var(--font-body); font-size: 13px; font-weight: 500;
  line-height: 1; color: var(--deep-night);
}
.em-check input[type="checkbox"] {
  position: absolute; opacity: 0; width: 0; height: 0; margin: 0;
  pointer-events: none;
}
.em-check::before {
  content: '';
  /* Belt and braces on the sizing. A pseudo-element flex item can be
     collapsed by its container in ways an ordinary element is not, so the
     dimensions are stated three ways and border-box stops the 1.5px border
     eating into them. If this box ever renders as a thin bar again, look for
     a parent flex container squeezing it rather than for a missing rule. */
  display: block; box-sizing: border-box;
  width: 18px; height: 18px;
  align-self: center;
  border: 1.5px solid rgba(27,27,47,0.22); border-radius: 5px;
  background: white; transition: all 0.15s;
}
/* The label text: allowed to shrink, never to be pushed onto its own line. */
.em-check > span { min-width: 0; }
/* AND THE CAPTION RULE HAS TO LOSE TO IT. `.form-group > label` (0,1,1) is
   the field caption -- display:block, 6px under it -- and it outranks
   .em-check (0,1,0). A checkbox that is a DIRECT child of a .form-group
   therefore rendered as a block: the 18px ::before box on one line and the
   text on the next. That is the page editor's Security tab, and it is the
   same trap the child combinator above was added to fix -- narrowing the
   caption rule stopped it reaching NESTED labels and never stopped it
   reaching this one.

   0,2,1 rather than !important, and it restates only the two things the
   caption rule got wrong: the display, and a bottom margin meant for a
   caption sitting above an input. */
.form-group > label.em-check {
  display: inline-grid;
  margin-bottom: 0;
}
.em-check:hover::before { border-color: rgba(91,75,212,0.45); }
.em-check:has(input:checked)::before {
  background: var(--violet); border-color: var(--violet);
  /* Tick as a background image so the box needs no extra element. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  background-size: 12px; background-repeat: no-repeat; background-position: center;
}
.em-check:has(input:focus-visible)::before {
  box-shadow: 0 0 0 3px rgba(91,75,212,0.25);
}
.em-check:has(input:disabled) { opacity: 0.5; cursor: not-allowed; }
.em-check:has(input:disabled)::before { background: rgba(27,27,47,0.05); }

/* Schedule rows: a fixed label width so Starts and Ends line their inputs up
   with each other rather than with their own text. */
.sched-check { min-width: 104px; }
.sched-day { position: relative; }
.sched-day input[type="checkbox"] {
  position: absolute; opacity: 0; width: 0; height: 0; margin: 0;
}
/* The date and time travel together. nowrap here is the whole point: the row
   above may wrap, this pair may not, so on a narrow screen the tick box keeps
   the line and both inputs drop beneath it together. */
.sched-inputs {
  display: flex; flex-wrap: nowrap; align-items: center;
  gap: 8px; min-width: 0;
}
.sched-inputs input[type="date"] { flex: 1 1 auto; min-width: 0; }
.sched-inputs input[type="time"] { flex: 0 0 auto; }

/* The opening-hours row: two times and an ampersand between them. The times
   share the space equally, because unlike the date rows above neither one is
   the wider control -- letting them size to content left a ragged right edge
   against Starts and Ends directly above. */
.sched-between .sched-inputs input[type="time"] { flex: 1 1 0; min-width: 0; }
.sched-amp {
  flex: 0 0 auto;
  font-family: var(--font-body); font-size: 13px;
  color: rgba(27,27,47,0.5);
}

.sched-row input[type="date"],
.sched-row input[type="time"] {
  height: 38px; box-sizing: border-box; padding: 0 10px; border-radius: 8px;
  border: 1px solid rgba(27,27,47,0.12); background: var(--ghost);
  font-family: var(--font-body); font-size: 13px; color: var(--deep-night);
}
.sched-row input:disabled { opacity: 0.5; cursor: not-allowed; }

.sched-days { margin: 14px 0 10px; }
.sched-days-label {
  display: block; margin-bottom: 6px;
  font-family: var(--font-body); font-size: 13px; font-weight: 500;
  color: var(--deep-night);
}
/* Wraps to two rows on a narrow phone rather than scrolling sideways. */
.sched-day-list { display: flex; flex-wrap: wrap; gap: 6px; }
/* Day chips: the whole chip is the target, not a 13px tick box beside a
   label. Matters most on a phone, which is where this gets used. */
.sched-day {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 46px; padding: 8px 10px; border-radius: 9px;
  border: 1.5px solid rgba(27,27,47,0.12); background: var(--ghost);
  font-family: var(--font-body); font-size: 12px; font-weight: 600;
  color: rgba(27,27,47,0.55); cursor: pointer; user-select: none;
  transition: all 0.15s;
}
.sched-day:hover { border-color: rgba(91,75,212,0.35); color: var(--deep-night); }
.sched-day:has(input:checked) {
  border-color: var(--violet); background: var(--violet);
  color: white; box-shadow: 0 1px 4px rgba(91,75,212,0.3);
}
.sched-day:has(input:focus-visible) { box-shadow: 0 0 0 3px rgba(91,75,212,0.25); }
.sched-day:has(input:disabled) { opacity: 0.5; cursor: not-allowed; box-shadow: none; }

/* Inline schedule error. Sits with the fields it is about — replacing the
   whole card with a flash message is what this exists to stop. */
/* One rule, two names. .sched-error is the older, scheduling-specific name for
   what is simply the form error banner; .form-error-banner is what new screens
   use. Worth consolidating to one name when something else touches the
   scheduling markup -- not worth a churn commit on its own. */
.sched-error,
.form-error-banner {
  margin-top: 10px; padding: 9px 12px; border-radius: 8px;
  background: rgba(231,76,60,0.08); border: 1px solid rgba(231,76,60,0.25);
  font-family: var(--font-body); font-size: 12.5px; color: #a3271b;
}
.sched-error[hidden] { display: none; }

/* The upgrade card, inline in a tab rather than filling a section. */
.upgrade-card-inline { margin-bottom: 16px; padding: 14px 16px; }
.upgrade-card-inline .upgrade-description { margin: 8px 0 12px; font-size: 13px; }

.sched-hint {
  margin: 6px 0 0; font-family: var(--font-body);
  font-size: 12px; color: rgba(27,27,47,0.5);
}
.sched-upsell {
  margin-bottom: 14px; padding: 12px 14px; border-radius: 10px;
  background: rgba(91,75,212,0.06); border: 1px solid rgba(91,75,212,0.15);
  font-family: var(--font-body); font-size: 13px; line-height: 1.5;
  color: var(--deep-night);
}

/* The clock badge. Colour is never the only signal — every state prints its
   word, and the sentence underneath spells out the actual dates. */
.link-clock {
  display: inline-flex; align-items: center; gap: 4px;
  vertical-align: 1px;
}
.link-clock svg { flex-shrink: 0; }
.link-badge-live      { background: rgba(0,229,160,0.18);  color: #06724f; }
.link-badge-scheduled { background: rgba(245,166,35,0.18); color: #8a5a00; }
.link-badge-expired   { background: rgba(231,76,60,0.14);  color: #a3271b; }
.link-badge-offtier   { background: rgba(27,27,47,0.08);   color: rgba(27,27,47,0.55); }

.link-schedule-text {
  display: block; margin-top: 2px;
  font-family: var(--font-body); font-size: 11px;
  color: rgba(27,27,47,0.45); font-weight: 400;
}
.link-card-scheduled { border-left: 3px solid var(--lilac); }

.link-clear-schedule {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 12px; padding: 3px 8px; border-radius: 6px;
  background: none; border: 1px solid rgba(27,27,47,0.12); cursor: pointer;
  font-family: var(--font-body); font-size: 11px; font-weight: 600;
  color: rgba(27,27,47,0.55);
}
.link-clear-schedule:hover {
  border-color: var(--violet); color: var(--violet);
  background: rgba(91,75,212,0.06);
}

/* Paused links — the owner's manual off-switch.
   The card stays in place and stays fully operable; only its emphasis drops,
   so it reads as "switched off" rather than "broken" or "being dragged".
   The Paused badge carries the meaning on its own: the muting is colour, and
   colour is never the sole differentiator (brand accessibility rule 3). */
.link-card-paused {
  background: rgba(27,27,47,0.02);
  border-style: dashed;
  border-color: rgba(27,27,47,0.12);
}
.link-card-paused .link-title-text,
.link-card-paused .link-url { opacity: 0.55; }
.link-card-paused .link-icon { opacity: 0.4; }

.link-badge {
  display: inline-block; margin-left: 6px;
  padding: 1px 6px; border-radius: 4px;
  font-family: var(--font-body);
  font-size: 10px; font-weight: 600; letter-spacing: 0.02em;
  text-transform: uppercase; vertical-align: 1px;
}
.link-badge-paused {
  background: rgba(27,27,47,0.08);
  color: rgba(27,27,47,0.55);
}

/* The pause control sits left of Edit. Violet on hover like the others, but
   it gets a persistent tint while paused so the way back on is obvious. */
.link-card-paused .link-actions button.link-toggle {
  color: var(--violet);
  background: rgba(91,75,212,0.08);
}

/* Drag-and-drop states */
.link-card.dragging { opacity: 0.4; }
.link-card.drag-over {
  border-top: 2px solid var(--violet);
  margin-top: -1px;
}

/* Link editing card */
.link-card-editing {
  border-color: rgba(91,75,212,0.2);
}
.edit-link-form { width: 100%; }
.edit-link-form .form-group { margin-bottom: 10px; }
.edit-link-form .form-group:last-of-type { margin-bottom: 0; }

/* Add link button */
.btn-add-link {
  display: block; width: 100%;
  padding: 12px; margin-top: 4px;
  background: none; border: 2px dashed rgba(27,27,47,0.1);
  border-radius: 10px; cursor: pointer;
  font-size: 13px; font-weight: 500;
  color: rgba(27,27,47,0.4);
  font-family: var(--font-body);
  transition: border-color 0.2s, color 0.2s;
}
.btn-add-link:hover {
  border-color: var(--violet); color: var(--violet);
}

/* Add link inline form */
.add-link-form {
  background: var(--ghost); border: 2px solid rgba(91,75,212,0.15);
  border-radius: 10px; padding: 16px; margin-top: 4px;
}
.add-link-form .form-group { margin-bottom: 12px; }
.add-link-form .form-group:last-of-type { margin-bottom: 0; }
.add-link-form .add-link-actions,
.edit-link-form .add-link-actions,
.add-page-form .add-link-actions {
  display: flex; gap: 8px; margin-top: 14px;
}
.add-link-form .add-link-actions button,
.edit-link-form .add-link-actions button,
.add-page-form .add-link-actions button {
  padding: 8px 16px; font-size: 13px; border-radius: 8px;
  font-weight: 500; cursor: pointer; font-family: var(--font-body);
}
.add-link-form .btn-save-link,
.edit-link-form .btn-save-link,
.add-page-form .btn-save-link {
  background: var(--violet); color: white; border: none;
}
.add-link-form .btn-cancel-link,
.edit-link-form .btn-cancel-link,
.add-page-form .btn-cancel-link {
  background: none; border: 1px solid rgba(27,27,47,0.1); color: rgba(27,27,47,0.5);
}

/* Theme buttons */
.theme-buttons {
  display: flex; gap: 10px;
}
.theme-btn {
  flex: 1;
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
  background: var(--ghost);
  border: 2px solid rgba(27,27,47,0.08);
  border-radius: 12px;
  cursor: pointer;
  font-family: var(--font-body);
  transition: border-color 0.2s, background 0.2s;
}
.theme-btn:hover { border-color: rgba(91,75,212,0.3); }
.theme-btn.active {
  border-color: var(--violet);
  background: rgba(91,75,212,0.06);
}
.theme-btn-swatch {
  width: 32px; height: 32px; border-radius: 8px;
  flex-shrink: 0;
}
.theme-dark { background: var(--deep-night); }
.theme-light { background: #fff; border: 1px solid rgba(27,27,47,0.1); }
.theme-violet { background: var(--violet); }
.theme-btn-name {
  font-size: 13px; font-weight: 600; color: var(--deep-night);
  flex: 1;
}
.theme-btn-check {
  font-size: 14px; color: var(--violet); font-weight: 700;
  opacity: 0;
  transition: opacity 0.15s;
}
.theme-btn.active .theme-btn-check { opacity: 1; }

/* The row now holds the three built-ins PLUS one tile per saved theme, so it
   has to wrap where three never did. A three-up grid rather than flex:1, which
   would have stretched four tiles to a quarter each and five to a fifth — the
   built-ins must keep the same size whether or not the account has themes. */
.theme-buttons--wrap { flex-wrap: wrap; }
.theme-buttons--wrap .theme-btn {
  flex: 0 1 calc(33.333% - 7px);
  /* 130, not 150: at a 330px panel two 150s plus the gap came to 310 against
     298 of content width, so every tile took a row of its own. 130 keeps the
     phone two-up, which is what the three built-ins looked like before. */
  min-width: 130px;
}
/* A saved theme's name is the owner's, so it can be long where "Dark" is not.
   Ellipsis rather than wrap: a tile that grows taller than its neighbours
   breaks the row, and the full name is a click away on Manage themes. */
.theme-btn-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Sits under the row of tiles rather than beside a field. .settings-hint pulls
   itself UP by 4px to sit tight under a label, which is right everywhere else
   and wrong here -- it put the link on top of the tile above it. Written as
   both classes so it wins on specificity rather than on source order, since
   .settings-hint is declared further down this file. */
.settings-hint.theme-manage-link { margin-top: 20px; }

/* Quick add */
/* 8px below, made of 3px here plus the track's own 5px of bottom padding --
   see the note on .quick-add-track for why that padding exists. */
.quick-add {
  margin: 16px 0 3px;
}
.quick-add-label {
  font-size: 12px; font-weight: 600; color: rgba(27,27,47,0.4);
  text-transform: uppercase; letter-spacing: 0.04em;
  margin-bottom: 8px;
}
.quick-add-head {
  display: flex; align-items: center; justify-content: space-between;
  /* 3px + the track's 5px top padding = the 8px this has always been. */
  margin-bottom: 3px;
}
.quick-add-head .quick-add-label { margin-bottom: 0; }
.quick-add-nav { display: flex; gap: 4px; }
.quick-add-arrow {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0;
  border: 1.5px solid rgba(27,27,47,0.08);
  border-radius: 8px;
  background: #fff; cursor: pointer;
  color: rgba(27,27,47,0.55);
  transition: border-color 0.2s, color 0.2s;
}
.quick-add-arrow:hover { border-color: var(--violet); color: var(--violet); }
.quick-add-arrow svg { width: 15px; height: 15px; }
/* Never disabled: the track wraps, so there is no end to arrive at. An arrow
   that greys out would be telling the user something untrue. */

/* The track. Scroll-snap does the moving and the arrows only nudge
   scrollLeft, so this degrades to a plain scrollable strip with no
   JavaScript rather than to a dead row. */
.quick-add-track {
  display: flex; gap: 8px;
  overflow-x: auto;
  /* The strip is allowed to be narrower than the tiles it holds -- that is
     what the scrolling is FOR. Without this its min-content is all twelve
     tiles laid side by side (894px), and being a scroll container does not
     shrink that: the automatic minimum size only zeroes for flex and grid
     ITEMS, and this is a block in normal flow. So the figure propagated up
     through every ancestor and floored the .dash-editor column above.

     Belt and braces with the minmax(0, 1fr) there: either alone stops the
     preview being pushed off the page, and this one is the honest statement
     of the two, because it says the thing that is actually true about this
     box rather than defending its ancestors from it. */
  min-width: 0;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;

  /* ROOM FOR WHAT A TILE DRAWS OUTSIDE ITSELF, and this is not spacing.

     overflow-x: auto does not clip only the x axis. A non-visible value on
     one axis computes the other to auto, so this box clips VERTICALLY as
     well -- and .quick-add-btn:hover lifts its tile by 1px, which put the
     top of its violet border outside the box and sliced it off. The focus
     ring, with its 3px offset, was going the same way.

     Padding gives the overflow somewhere to live inside the clipping box.
     The 5px is TAKEN OUT of the margins either side of the track rather than
     cancelled with a negative margin here -- a negative margin would have
     collapsed against .quick-add-head's and .quick-add's own margins instead
     of pulling, which measured as a strip 10px taller and a gap 5px tighter.
     Explicit subtraction has no collapsing rules to reason about, and the
     three numbers still add up to the 8px gaps that were there before.

     Vertical only: horizontal padding would move the scroll-snap positions.

     Reported on desktop and not on mobile, which is the tell: a phone has no
     hover, so the lift never happened there. */
  padding: 5px 0;
}
.quick-add-track::-webkit-scrollbar { display: none; }
.quick-add-track:focus-visible {
  outline: 2px solid var(--violet); outline-offset: 3px; border-radius: 10px;
}
.quick-add-track > .quick-add-btn {
  /* Three and a bit visible, so the strip reads as continuing rather than as
     a row that happens to end. This is the PHONE size and it is already
     right -- a tile has to stay thumb-sized. */
  flex: 0 0 calc((100% - 24px) / 3.4);
  scroll-snap-align: start;
}
/* Desktop tiles were phone tiles stretched: a 22px icon and a one-word label
   in a box wide enough for three of them. Tightening the box shows five and a
   half instead of three and a bit, which is most of the set at a glance and
   fewer presses to reach LinkedIn at the end. Padding comes in with it --
   the white space was horizontal, so widening alone would just move it. */
@media (min-width: 768px) {
  .quick-add-track { gap: 6px; }
  .quick-add-track > .quick-add-btn {
    flex: 0 0 calc((100% - 30px) / 5.5);
    padding: 10px 4px;
    gap: 5px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .quick-add-track { scroll-behavior: auto; }
}
.quick-add-btn {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 14px 8px;
  background: var(--ghost);
  border: 1.5px solid rgba(27,27,47,0.06);
  border-radius: 10px;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: 11px; font-weight: 500;
  color: rgba(27,27,47,0.6);
  transition: border-color 0.2s, color 0.2s, transform 0.15s;
}
.quick-add-btn:hover {
  border-color: var(--violet); color: var(--violet);
  transform: translateY(-1px);
}
.quick-add-icon {
  width: 22px; height: 22px;
}

/* ---- Connections -------------------------------------------------------

   A directory row: face, who they are, where they can be found. The three
   parts are a grid rather than flex because the middle column has to stay
   put whether somebody has one page or six -- with flex, a long page list
   pushes the names around and no two rows line up.

   The avatar is .user-avatar, the circle the admin users table and the
   dashboard topbar already draw. It carries no size of its own by design
   (see partials/user-avatar.html), so the size belongs here. */
.conn-list { display: flex; flex-direction: column; gap: 8px; }
.conn-row {
  display: grid;
  grid-template-columns: 44px minmax(160px, 1fr) 2fr auto;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  background: var(--ghost);
  border: 1px solid rgba(27,27,47,0.06);
  border-radius: 12px;
}
.conn-row .user-avatar { width: 44px; height: 44px; font-size: 15px; }
.conn-row--find { grid-template-columns: 44px 1fr auto; }

/* The remove control sits in the row's fourth column, which the grid has
   always reserved as `auto` -- so the form is a grid ITEM and needs no
   display:contents. Quiet by default and red only on hover, the same
   grammar as the link card's delete: ending a connection is reversible and
   mutual, so it must not look like the row's purpose. */
/* display:contents so the form is layout-invisible: .delete-confirm-actions
   is a flex row, and a block-level form around one button would break the
   Remove and Cancel controls onto separate lines. Same trick, same reason,
   as .plan-change-buy. */
.conn-remove-form { display: contents; }
.conn-remove {
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; padding: 6px; margin: 0;
  border-radius: 8px; cursor: pointer; line-height: 0;
  color: rgba(27,27,47,0.3); transition: background .15s, color .15s;
}
.conn-remove:hover { background: rgba(231,76,60,0.08); color: #e74c3c; }
.conn-remove:focus-visible { outline: 2px solid var(--violet); outline-offset: 1px; }

.conn-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.conn-name {
  font-weight: 600; font-size: 15px;
  color: var(--deep-night); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.conn-name:hover { color: var(--violet); }
.conn-handle { font-size: 12px; color: rgba(27,27,47,0.45); }

/* THE PAGE LINKS ARE PLAIN, and that is the brief rather than an oversight.
   They are a list of somewhere-elses under somebody's name; underlining them
   or making them buttons would turn a directory row into a row of adverts.
   The hover is the whole affordance -- enough to say "this is clickable"
   when the pointer is on it, and silent otherwise. */
.conn-pages { display: flex; flex-wrap: wrap; gap: 6px 14px; min-width: 0; }
.conn-page {
  font-size: 13px;
  color: rgba(27,27,47,0.62);
  text-decoration: none;
}
.conn-page:hover { color: var(--violet); }
.conn-nopages { font-size: 13px; color: rgba(27,27,47,0.32); }

.conn-actions { display: flex; align-items: center; gap: 8px; }
.conn-pending {
  font-size: 12px; font-weight: 600;
  color: rgba(27,27,47,0.45);
  padding: 5px 12px;
  border: 1px dashed rgba(27,27,47,0.18);
  border-radius: 999px;
}
.btn-accept, .btn-decline, .btn-connect {
  font-family: var(--font-body);
  font-size: 13px; font-weight: 600;
  padding: 7px 14px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.btn-accept, .btn-connect {
  background: var(--violet); color: #fff; border: 1px solid var(--violet);
}
.btn-accept:hover, .btn-connect:hover { filter: brightness(1.08); }
/* Decline is quiet. It is a legitimate answer, not a destructive one, so it
   does not get the red an irreversible action would earn. */
.btn-decline {
  background: transparent; color: rgba(27,27,47,0.55);
  border: 1px solid rgba(27,27,47,0.18);
}
.btn-decline:hover { border-color: rgba(27,27,47,0.35); color: var(--deep-night); }

/* The invite link, styled like the boxed control under every other section
   heading on this page (.conn-search input, most directly) rather than a
   card of its own -- see the comment in connection-requests.html on why the
   .settings-section wrapper came back out. */
.conn-invite-link {
  font-family: var(--font-body); font-size: 13px;
  color: rgba(27,27,47,0.65);
  background: rgba(27,27,47,0.03);
  border: 1px solid rgba(27,27,47,0.08);
  border-radius: 8px;
  padding: 8px 12px;
  margin: 0 0 12px;
  word-break: break-all;
}
.conn-invite-actions {
  display: flex; flex-wrap: wrap; gap: 10px;
}

.conn-search { margin: 0 0 14px; }
.conn-search input {
  width: 100%;
  font: inherit; font-size: 14px;
  padding: 10px 14px;
  border: 1px solid rgba(27,27,47,0.12);
  border-radius: 10px;
  background: #fff;
}
.conn-search input:focus { outline: 2px solid var(--violet); outline-offset: 1px; }

/* The section headings on the requests tab. The bottom margin matters as much
   as the top one: an empty section is a single grey line, and without it
   "Nothing waiting." sits hard against the next heading and reads as though
   it belongs to it. */
.conn-section { margin-top: 30px; margin-bottom: 10px; }
/* .pages-section-title's `margin: 0 0 12px` shorthand (below) resets
   margin-top to 0 on anything carrying both classes -- same specificity as
   the rule above, so whichever comes LAST in the file was winning, and it
   was this one. "Waiting on you" and "Sent" were sitting flush against the
   block above them instead of standing apart from it. This compound
   selector outranks both single-class rules on its own, so it wins no
   matter which order the file lists them in. */
.pages-section-title.conn-section { margin-top: 30px; }
.conn-empty { padding: 22px 4px; color: rgba(27,27,47,0.5); font-size: 14px; }
.conn-empty-hint { margin-top: 6px; font-size: 13px; }
.conn-empty-line { color: rgba(27,27,47,0.4); font-size: 13px; margin: 0 0 6px; }

/* A row is three stacked blocks on a phone: the grid's middle column has a
   160px floor, and two of those plus an avatar do not fit a 375px screen.

   THE REMOVE ICON KEEPS ITS OWN COLUMN rather than joining the stack. It was
   falling to a line of its own, because auto-placement is what happens to a
   child with no grid-area once the parent declares template areas -- and a
   16px icon alone on a row reads as a fourth piece of content. Top right,
   level with the name, is where a phone expects the control for the row it
   is sitting on.

   The column is `auto`, so on the find and request rows -- which share this
   rule and carry no remove button -- it collapses to nothing. */
@media (max-width: 640px) {
  .conn-row, .conn-row--find {
    grid-template-columns: 44px 1fr auto;
    grid-template-areas:
      "avatar  id      remove"
      "pages   pages   pages"
      "actions actions actions";
    row-gap: 10px;
  }
  .conn-row .user-avatar { grid-area: avatar; }
  .conn-id { grid-area: id; }
  .conn-pages { grid-area: pages; }
  .conn-remove { grid-area: remove; align-self: start; }
  .conn-actions { grid-area: actions; justify-content: flex-end; }
}

/* Pro feature hints */
.pro-feature-hint {
  font-size: 12px; color: rgba(27,27,47,0.4);
  margin-top: 10px; display: flex; align-items: center; gap: 6px;
}
.pro-lock {
  display: inline-block;
  font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.3px; padding: 2px 8px; border-radius: 4px;
  background: linear-gradient(135deg, var(--violet), #7C6CF0);
  color: white; font-family: var(--font-head);
}

/* Tier gating is server-side, so there is no rule here and there should not
   be one. The free-tier blocks live in partials/upgrade-card-free.html and
   partials/theme-hint-free.html, picked by IEMRenderer.LoadTierPartial: a
   tier with no file is sent no markup. Never gate a tier with display:none —
   the markup would still be in the page for anyone who looked.
   (.tier-gate-free is gone. It carried no declarations, and the one time it
   did — display:flex — it silently turned .upgrade-card into a three-column
   row at every screen size.) */

/* Preview panel — phone mockup */
.dash-preview-panel {
  position: sticky; top: 80px;
}
.preview-label {
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.5px; color: rgba(27,27,47,0.3);
  margin-bottom: 10px; text-align: center;
}
.preview-phone-frame {
  background: var(--deep-night);
  border-radius: 32px;
  padding: 12px;
  box-shadow: 0 20px 60px rgba(27,27,47,0.15), 0 4px 16px rgba(27,27,47,0.08);
}
/* "Open /name/slug" under the phone mock. The only route to the live page on
   a phone, where .dash-view-live is display:none -- and useful on a desktop
   too, because it names the page it opens where the top bar just says "live".

   Centred under the frame and quiet: the preview above it is the thing being
   looked at, and this is the way out of it. */
.preview-open {
  /* BLOCK-LEVEL flex plus width:fit-content plus auto side margins -- which
     is what centres this WITHOUT touching .dash-preview-panel. Making the
     panel a flex column would have been the obvious way and is the trap the
     note above this section records: the upgrade card is a child of that
     panel too, and the last time a container here quietly became a flex box
     it turned that card into a three-column row at every screen size. */
  display: flex; align-items: center; gap: 6px;
  width: fit-content; margin: 14px auto 0; padding: 8px 14px;
  font-size: 13px; font-weight: 500; color: var(--violet);
  text-decoration: none; border-radius: 8px;
  border: 1px solid rgba(91,75,212,0.2);
  transition: background .15s;
}
.preview-open:hover { background: rgba(91,75,212,0.06); }
.preview-open:focus-visible { outline: 2px solid var(--violet); outline-offset: 1px; }

.preview-phone-notch {
  width: 80px; height: 5px;
  background: rgba(255,255,255,0.12);
  border-radius: 10px;
  margin: 4px auto 0;
  position: relative; z-index: 1;
}
.preview-phone-frame .em-page {
  border-radius: 22px;
  padding: 28px 16px 20px;
  min-height: 400px;
  text-align: center;
  flex-direction: column;
  transition: background 0.3s, color 0.3s;
  /* The frame is about a third the size of a real page, so its buttons want a
     smaller default corner than the public page's 14px. Set as the VARIABLE,
     not as border-radius on .em-link: a theme's own radius arrives in the
     element's style attribute and outranks any rule, so this is the default
     the theme replaces rather than a value that replaces the theme. Setting
     the property directly is what made Square, Rounded and Pill do nothing
     here -- the same mistake as the avatar below, twice more. */
  --em-radius: 10px;
}
/* THE PAGE'S OWN AVATAR, and a DIRECT CHILD selector because it is not the
   only circle in the frame. As `.preview-phone-frame .em-avatar` this rule
   was 0,2,0 and reached the face a PROFILE BLOCK wears, where
   .em-avatar--link (0,1,0) could not answer it: a driver block previewed at
   56px, centred by `margin: 0 auto`, on a line of its own -- nothing like
   the page it was previewing, on the one surface the owner watches while
   editing. The header avatar is a direct child of .em-page and a block's
   never is, so the combinator says exactly what was meant. Same trap, same
   fix, as `.form-group > label` over `.em-check`. */
.preview-phone-frame .em-page > .em-avatar {
  width: 56px; height: 56px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 20px; font-weight: 700;
  /* Size only. Colour and font come from the theme variables like the public
     page's avatar does -- hardcoding them here outranked the theme and made
     the preview quietly disagree with the thing it is previewing. */
  color: var(--em-accent-fg);
  font-family: var(--em-head-font);
  margin: 0 auto 10px;
}
/* The circle a profile block wears, scaled to the frame the way every other
   value in it is -- 32px beside 15px text on the page becomes 24px beside
   12px text here. margin:0 because .em-avatar's `0 auto 16px` is for the one
   at the top of a page, and auto margins on a flex child push the title off
   centre. */
.preview-phone-frame .em-avatar--link {
  width: 24px; height: 24px;
  font-size: 10px;
  margin: 0;
}
/* Size and spacing only, in all three. The heading font came from
   --font-head here and from --em-head-font on the real page, so a serif theme
   previewed as the brand face; the bio's opacity outranked the
   --em-muted-opacity:1 that a chosen muted colour sets, showing it at 60% and
   reading as the wrong colour. Both now inherit from .em-page's rules. */
.preview-phone-frame .em-name {
  font-weight: 700;
  font-size: 16px; margin: 0 0 4px;
}
.preview-phone-frame .em-bio {
  font-size: 12px; margin: 0 0 18px; line-height: 1.5;
}
/* FLEX, NOT BLOCK, for the same reason .em-link is flex on the public page:
   an icon or an avatar is a block-level box and takes a line of its own
   beside anything else. As `display: block` this rule (0,2,0) overrode that
   and every mark in the preview stacked above its title. Only the box model
   comes back -- the frame keeps its own padding, size and spacing, and gap
   is 6px against the page's 8px because everything in here is scaled. */
/* A heading block, scaled like everything else in the frame. .em-block-header
   is 19px against .em-name's 24px on a real page -- but the frame drops the
   name to 16px and never touched the heading, so a heading block rendered
   LARGER than the name of the page it was on. Sized between the name and the
   12px links, which is the relationship the public page has; the margins come
   down with it, because 28px of air above a heading is a third of this
   frame. */
.preview-phone-frame .em-block-header {
  font-size: 14px; margin: 16px 0 6px;
}
.preview-phone-frame .em-block-subheader {
  font-size: 12.5px; margin: 12px 0 6px;
}
.preview-phone-frame .em-block-text { font-size: 11px; margin-bottom: 10px; }

.preview-phone-frame .em-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 11px 14px;
  margin-bottom: 8px;
  font-size: 12px; font-weight: 500;
  text-decoration: none; text-align: center;
  transition: transform 0.15s;
}
.preview-phone-frame .em-link:hover { transform: scale(1.02); }
.preview-phone-frame .em-branding {
  display: block; margin-top: 16px;
  font-size: 10px; opacity: 0.25;
  text-decoration: none;
  font-family: var(--font-head); font-weight: 600;
}

/* Preview theme colours: deliberately none.

   There WAS a full second copy of the three themes here, scoped to
   .preview-phone-frame, and it had already drifted -- violet links at 0.15/0.2
   against the public page's 0.12/0.15, dark text as ghost rather than white.
   Two copies of one rule are maintained in one and discovered in the other,
   which is the same argument that made the link preview re-fetch from the
   server instead of being patched in JS.

   The themes are custom properties on .em-page now, so the preview inherits
   the real ones by using the same markup -- and a Pro custom theme shows up in
   the preview for free, which the duplicate block could never have done. */

/* Avatar editor */
.avatar-editor {
  display: flex; align-items: center; gap: 20px;
  padding: 8px 0 4px;
}
.avatar-editor .em-avatar { flex-shrink: 0; }
.avatar-file-input {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.avatar-actions {
  display: flex; align-items: center; gap: 2px;
  flex-shrink: 0;
}
.avatar-actions .avatar-action-btn {
  width: 34px; height: 34px; border-radius: 8px;
  box-sizing: border-box;
  /* Zeroed because this is a <label>, and .form-group label adds a 6px
     bottom margin for field captions — which pushed the button 3px above
     the avatar's centre line. */
  margin: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: none; cursor: pointer;
  color: rgba(27,27,47,0.4); padding: 0; margin: 0;
  font: inherit; line-height: 1;
  transition: all 0.2s;
}
.avatar-actions .avatar-action-btn svg { display: block; }
.avatar-actions .avatar-action-btn:hover { background: rgba(91,75,212,0.08); color: var(--violet); }
.avatar-actions .avatar-clear-btn:hover { background: rgba(231,76,60,0.08); color: #e74c3c; }
.avatar-meta {
  display: flex; flex-direction: column; gap: 6px;
  /* Take the remaining row width. Without this the column is sized by its
     narrowest content and "Show on page" breaks away from its checkbox. */
  flex: 1 1 auto; min-width: 0;
  align-items: flex-start;
}
.avatar-source-label {
  font-size: 12px; color: rgba(27,27,47,0.5);
  font-family: var(--font-body); line-height: 1;
}
/* Composes .em-check — see the note there. Only the muted colour differs,
   because this one sits under an avatar rather than in a form. */
/* The page editor's avatar block.

   A grid rather than a flex row, because two different alignments are wanted
   at once: the camera button and the Show-on-page toggle centred against the
   AVATAR, and the caption centred UNDER it. A flex row centres everything
   against the tallest item, so adding the caption to the column dragged the
   controls down with it.

      row 1:  [circle] [camera] [toggle]     <- align-items: center
      row 2:  [caption]                      <- centred under the circle

   Settings uses the same partial classes without this modifier and keeps the
   plain flex row: its caption is a whole sentence and would wrap badly
   squeezed under a 72px circle. */
.avatar-editor--page {
  display: grid;
  grid-template-columns: auto auto 1fr;
  grid-template-areas:
    "circle  actions toggle"
    "caption .       .";
  align-items: center;
  column-gap: 14px; row-gap: 6px;
}
.avatar-editor--page > .em-avatar { grid-area: circle; }
.avatar-editor--page > .avatar-actions { grid-area: actions; }
.avatar-editor--page > .avatar-meta {
  grid-area: toggle;
  /* flex-direction is reset explicitly: the base .avatar-meta is a column,
     and inherited here it made align-items:center centre the toggle
     horizontally in its 1fr track instead of parking it beside the camera. */
  display: flex; flex-direction: row;
  align-items: center; justify-content: flex-start;
}
.avatar-editor--page > .avatar-source-label {
  grid-area: caption; justify-self: center; text-align: center;
}


/* Composes .em-check. The explicit row/nowrap is not redundant: this label
   sits in a column flex container inside a row flex container, and when the
   outer row runs short the label is the first thing to be squeezed — which
   is what put the box on one line and the text on the next. */
.avatar-toggle { color: rgba(27,27,47,0.7); min-height: 0; }
.avatar-toggle-label { user-select: none; white-space: nowrap; }

/* Public page avatar with image */
.em-avatar .avatar-img {
  width: 100%; height: 100%; object-fit: cover;
  border-radius: 50%; position: absolute; top: 0; left: 0;
  /* THE PHOTO PAINTS OVER THE INITIALS, IN CSS, AND THAT IS THE FIX.

     .em-avatar is display:flex, and a flex item honours z-index WITHOUT being
     positioned -- so .avatar-initials at z-index 1 beat this absolutely
     positioned image at z-index auto, and the initials sat on top of the
     face. Nothing showed it, because BuildAvatarHtml writes an inline
     display:none on the initials when there is an image and em.js's
     initAvatarImages() writes another at load: the layering was wrong all
     along and two other mechanisms were hiding it.

     Both of those mechanisms miss anything htmx swaps in AFTER load, which is
     exactly what the dashboard's live preview is -- so a profile block's
     avatar showed its initials over the photo there and nowhere else.
     Reported 5 Sep 2026 on /dashboard/pages/211/edit.

     Fixing the paint order rather than adding a third hiding mechanism: this
     works before any script runs (no flash of initials on first paint), works
     with JavaScript off, and works for markup that arrives later. The
     display:none paths still do what they always did; they are now a
     belt-and-braces rather than the only thing standing between a photo and
     the letters on top of it. */
  z-index: 2;
}
.em-avatar .avatar-initials { z-index: 1; }

/* Avatar size/context modifiers — the circle markup itself is shared
   (partials/avatar-circle.html), only these change per context. */
.em-avatar--editor { margin: 0; }
.em-avatar--card {
  width: 42px; height: 42px; font-size: 14px;
  margin: 0; flex-shrink: 0;
}
.em-avatar--card-sub { width: 36px; height: 36px; font-size: 12px; margin: 0; }

/* Settings hint */
/* ── Timezone control ──────────────────────────────────────────────────────
   A native <select> on purpose. There are ~600 IANA zones grouped by region,
   and every platform already renders that well with type-ahead built in — a
   custom combobox would be worse on a phone, which is where a trades person
   actually opens this. */
/* Contact card (Settings). NOT .settings-form: that is flex+wrap, right for
   one input beside a button, wrong for fourteen fields. Two columns because
   the fields are PAIRS (first/last, phone/type, postcode/country); auto-fit
   made three at 700px and split the pairs. */
/* Used in the page editor since before the contact tab and never defined,
   so these hints have been rendering as unstyled body text. Same size and
   colour as .settings-hint, without its negative top margin -- that one is
   tuned to sit under a section <h3>. */
.editor-hint {
  font-size: 13px; color: rgba(27,27,47,0.5);
  margin: 0 0 14px; font-family: var(--font-body);
}
/* The page's own card, COLLAPSED until the override is switched on.

   It was dimmed rather than collapsed, and fourteen greyed-out fields still
   occupy fourteen fields of height: the tick box sat at the top of the tab
   and 'Save changes' sat below all of them, so the two controls somebody
   needs together were never on screen together. Collapsing puts them one
   under the other.

   THE INPUTS STAY IN THE DOM AND KEEP POSTING. That is the same requirement
   the dimming was honouring and the reason this is not `display: none` on
   the fields themselves -- a panel removed from the form is how the access
   code got written over as empty. A collapsed grid row and visibility:hidden
   both leave a control successful for submission; only detaching it or
   disabling it would not.

   grid-template-rows 1fr -> 0fr is what makes the height animatable at all;
   an `auto` row cannot be transitioned. The inner wrapper is not decorative:
   0fr only clamps a child that is allowed to be shorter than its content,
   which is what min-height:0 and overflow:hidden say.

   visibility carries the second half of the job -- out of the tab order, so
   a keyboard cannot land in a field nobody can see. It flips instantly on
   the way open and waits out the transition on the way shut, which is what
   the two transition declarations differ in. */
.card-own-fields {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows .2s ease;
}
.card-own-fields[data-off] { grid-template-rows: 0fr; }
.card-own-fields > .card-own-fields-inner {
  min-height: 0; overflow: hidden;
  visibility: visible; transition: visibility 0s;
}
.card-own-fields[data-off] > .card-own-fields-inner {
  visibility: hidden; transition: visibility 0s linear .2s;
}
@media (prefers-reduced-motion: reduce) {
  .card-own-fields,
  .card-own-fields > .card-own-fields-inner { transition: none; }
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 16px;
  margin-top: 16px;
}
.card-grid-wide { grid-column: 1 / -1; }
/* Double class, deliberately: .settings-hint carries margin-top:-4px, which
   is right under a section <h3> and wrong under an input -- it pulled these
   up against the field above. Equal specificity loses on source order, so
   these need 0,2,0 to win. */
.settings-hint.card-field-hint { margin: 8px 0 0; font-size: 12px; }
.settings-hint.card-url-hint { margin: 4px 0 14px; word-break: break-all; }
.card-toggle { margin: 14px 0; }
@media (max-width: 560px) {
  .card-grid { grid-template-columns: 1fr; }
}

.settings-form {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
}
/* Inputs are styled by input[type=...] selectors, which a select never
   matches, so selects need a rule of their own. ONE rule, listing every place
   a select appears, rather than a private copy per screen — the theme builder
   had no rule at all and rendered raw browser dropdowns next to styled
   inputs. A new screen adds its selector here and inherits the lot. */
.settings-select,
.form-group select,
.range-bar select {
  padding: 10px 12px;
  font-family: var(--font-body); font-size: 14px; color: var(--deep-night);
  background: var(--ghost);
  border: 1px solid rgba(27,27,47,0.12); border-radius: 8px;
}
.settings-select { flex: 1 1 260px; min-width: 0; max-width: 420px; }
/* Fills its .form-group, so two side by side line up with each other and with
   the inputs above them. */
.form-group select { display: block; width: 100%; }

.settings-select:focus,
.form-group select:focus,
.range-bar select:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: 0 0 0 3px rgba(91,75,212,0.12);
}
.btn-compact { padding: 10px 18px; font-size: 14px; width: auto; }

/* "Your browser says you're in X" — shown only when the browser and the
   saved value disagree, so it is never noise. */
.settings-nudge {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 10px 14px;
  border-radius: 8px;
  background: rgba(91,75,212,0.06);
  border: 1px solid rgba(91,75,212,0.15);
  font-family: var(--font-body); font-size: 13px; color: var(--deep-night);
}
.settings-nudge[hidden] { display: none; }
.btn-link {
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: var(--font-body); font-size: 13px; font-weight: 600;
  color: var(--violet); text-decoration: underline;
}
.btn-link:hover { color: var(--deep-night); }

.settings-result { margin-top: 8px; font-family: var(--font-body); font-size: 13px; }
.settings-result:empty { margin-top: 0; }
.settings-ok    { color: #0a7d5a; }
.settings-error { color: #c0392b; }

/* Label present for screen readers, absent for everyone else. The visible
   <h3> and hint already say what the control is. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

.settings-hint {
  font-size: 13px; color: rgba(27,27,47,0.5);
  margin: -4px 0 12px; font-family: var(--font-body);
}

/* Upgrade card */
.upgrade-card {
  margin-top: 16px; padding: 18px;
  background: white; border-radius: 12px;
  border: 1px solid rgba(91,75,212,0.12);
}
.upgrade-card-header {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 10px;
}
.upgrade-card-header strong {
  font-size: 14px; color: var(--deep-night);
}
.upgrade-features {
  list-style: none; padding: 0; margin: 0 0 14px;
}
.upgrade-features li {
  font-size: 12px; color: rgba(27,27,47,0.6);
  padding: 3px 0 3px 18px;
  position: relative;
}
.upgrade-features li::before {
  content: "\2713"; position: absolute; left: 0;
  color: var(--mint); font-weight: 600;
}
.btn-upgrade {
  display: block; text-align: center;
  padding: 10px; border-radius: 10px;
  background: var(--violet); color: white;
  font-size: 13px; font-weight: 600;
  text-decoration: none;
  font-family: var(--font-body);
  transition: background 0.2s;
}
.btn-upgrade:hover { background: var(--violet-dark); }
.upgrade-description {
  font-size: 13px; color: rgba(27,27,47,0.6);
  line-height: 1.5; margin: 0 0 14px;
}

/* ---- MY PAGES ---- */
/* Width and gutter come from the shared block near .dash-body. */
.dash-pages {
  padding: 0;
}
.pages-header h2 {
  font-family: var(--font-head); font-size: 22px;
  font-weight: 700; color: var(--deep-night); margin: 0 0 4px;
}
.pages-subtitle {
  font-size: 14px; color: rgba(27,27,47,0.5); margin: 0 0 28px;
}
.pages-section { margin-bottom: 32px; }
.pages-section-header {
  display: flex; align-items: center; gap: 8px; margin-bottom: 12px;
}
.pages-section-title {
  font-family: var(--font-head); font-size: 15px;
  font-weight: 600; color: var(--deep-night); margin: 0 0 12px;
}
.pages-section-header .pages-section-title { margin-bottom: 0; }

/* THE ALLOWANCE COUNTER, pushed to the far end of its own header. Quiet by
   design: it is a reassurance most of the time and only becomes information
   when somebody is close to the number. */
.pages-count {
  margin-left: auto; font-size: 12px; color: rgba(27,27,47,0.5);
  white-space: nowrap;
}

/* OVER ALLOWANCE. A real state, not a broken one -- a comp lapsing reaches it
   with nobody having done anything -- so it is a notice rather than an error
   page, and it says what still works before it says what does not. */
.pages-over {
  margin: 0 0 12px; padding: 10px 13px; border-radius: 11px;
  background: rgba(192,57,43,0.08); border: 1px solid rgba(192,57,43,0.16);
  color: #96281b; font-size: 13px; line-height: 1.5;
}
.pages-over strong { color: #7d2015; }

/* The server's answer to a refused publish, written into the flash area a
   rejected "add sub-page" already uses -- one place on this screen where
   things that were refused report themselves, rather than two. */
/* THE MAXED STATE, in the slot the add button had. Not a disabled button:
   a disabled control invites a click that does nothing, and this has
   somewhere useful to send you instead. */
.pages-maxed {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 11px 14px; border-radius: 12px;
  background: rgba(91,75,212,0.05);
  border: 1px dashed rgba(91,75,212,0.28);
}
.pages-maxed-state {
  font-size: 13px; font-weight: 600; color: rgba(27,27,47,0.65);
}
.pages-maxed-cta {
  margin-left: auto; font-size: 13px; font-weight: 600;
  color: var(--violet); text-decoration: none;
}
.pages-maxed-cta:hover { text-decoration: underline; }

.pages-flash-error {
  margin: 0 0 12px; padding: 9px 12px; border-radius: 10px;
  background: rgba(192,57,43,0.08); color: #96281b;
  font-size: 13px; line-height: 1.45;
}

/* Page card */
.page-card {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-radius: 12px;
  background: white; border: 1px solid rgba(27,27,47,0.08);
  margin-bottom: 8px; transition: box-shadow 0.2s;
}
.page-card:hover {
  box-shadow: 0 2px 8px rgba(27,27,47,0.08);
}
.page-card-info {
  display: flex; align-items: center; gap: 14px; min-width: 0;
}
.page-card-details {
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.page-card-name {
  font-family: var(--font-head); font-size: 15px; font-weight: 600;
  color: var(--deep-night);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.page-card-url {
  font-size: 12px; color: var(--violet); font-weight: 500;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.page-card-meta {
  font-size: 11px; color: rgba(27,27,47,0.4);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.page-card-actions {
  display: flex; gap: 6px; flex-shrink: 0;
}
.page-action {
  width: 34px; height: 34px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  color: rgba(27,27,47,0.4); background: transparent;
  border: none; cursor: pointer;
  text-decoration: none; transition: all 0.2s;
}
.page-action:hover {
  background: rgba(91,75,212,0.08); color: var(--violet);
}
.btn-add-page {
  display: block; width: 100%; padding: 12px;
  margin-top: 8px; border-radius: 10px;
  border: 2px dashed rgba(27,27,47,0.12);
  background: transparent; color: var(--violet);
  font-family: var(--font-body); font-size: 14px; font-weight: 600;
  cursor: pointer; transition: all 0.2s;
}
.btn-add-page:hover {
  border-color: var(--violet); background: rgba(91,75,212,0.04);
}

/* Inactive sub-page card (Free tier) */
.page-card--inactive {
  opacity: 0.6; border: 1px dashed rgba(27,27,47,0.15);
}
.page-card--inactive .page-card-actions .page-action[title="Preview"] {
  pointer-events: none; opacity: 0.3;
}

/* Slug input */
.slug-input-wrapper {
  display: flex; align-items: center; gap: 0;
  background: #f0eff5; border-radius: 10px; overflow: hidden;
}
.slug-prefix {
  padding: 10px 2px 10px 14px; font-size: 13px;
  color: rgba(27,27,47,0.5); white-space: nowrap;
  font-family: var(--font-body);
}
.slug-input-wrapper input[type="text"] {
  border-radius: 0 10px 10px 0; border: none;
  background: transparent; flex: 1; min-width: 0;
}
.validation-error { color: #e74c3c; font-size: 12px; margin-top: 4px; display: block; }
.validation-ok { color: var(--mint); font-size: 12px; margin-top: 4px; display: block; }

/* Add page form */
.add-page-form {
  background: #f8f7ff; border-radius: 12px;
  padding: 20px; margin-top: 8px;
}

/* Delete confirmation */
.page-action--delete { color: rgba(27,27,47,0.3); }
.page-action--delete:hover { color: #e74c3c; }
.delete-confirm {
  text-align: center; padding: 16px 12px;
}
.delete-confirm-message {
  font-size: 14px; color: rgba(27,27,47,0.7);
  margin: 0 0 12px; line-height: 1.5;
}
.delete-confirm-actions {
  display: flex; gap: 8px; justify-content: center;
}
.btn-delete-confirm {
  padding: 8px 20px; font-size: 13px; border-radius: 8px;
  font-weight: 600; cursor: pointer; font-family: var(--font-body);
  background: #e74c3c; color: white; border: none;
}
.btn-delete-confirm:hover { background: #c0392b; }
.btn-delete-cancel {
  padding: 8px 20px; font-size: 13px; border-radius: 8px;
  font-weight: 500; cursor: pointer; font-family: var(--font-body);
  background: none; border: 1px solid rgba(27,27,47,0.1); color: rgba(27,27,47,0.5);
}
/* The connections cancel is an <a>, not a <button> -- nothing has happened,
   so it is a link back to the list rather than a control. These two lines are
   everything an anchor needs to sit in the same row as the button beside it;
   without them it loses the padding and wears an underline. */
a.btn-delete-cancel {
  display: inline-block; text-decoration: none; line-height: normal;
}

/* ---- ERROR PAGES ---- */
.error-page {
  text-align: center; padding: 60px 20px;
}
.error-icon {
  font-size: 64px; margin-bottom: 16px; line-height: 1;
}
.error-title {
  font-family: var(--font-head); font-weight: 700;
  font-size: 28px; color: var(--deep-night); margin: 0 0 12px;
}
.error-message {
  font-size: 16px; color: rgba(27,27,47,0.6);
  max-width: 420px; margin: 0 auto 28px; line-height: 1.6;
}
.error-actions {
  display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;
}
.error-actions .btn-primary {
  display: inline-block; padding: 12px 28px;
  background: var(--violet); color: #fff;
  border-radius: 10px; text-decoration: none;
  font-family: var(--font-body); font-weight: 600; font-size: 14px;
  transition: background 0.2s;
}
.error-actions .btn-primary:hover { background: #4a3bc3; }
/* The quiet counterpart to .btn-primary, and now a real component.

   It existed for months as `.error-actions .btn-secondary` only, so anything
   else that reached for the class got a raw browser button — which is exactly
   what the analytics Apply button and the theme editor's Cancel were, in the
   only two screens that had ever asked for one. Unscoped here; the
   .error-actions rule below keeps that screen's larger sizing. */
/* inline-FLEX, not inline-block: in .dash-actions it is a flex item beside a
   48px .btn-primary and stretches to match it, and an inline-block's text
   stays at the top of the box it was given. */
.btn-secondary {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 9px 18px;
  background: transparent; color: var(--violet);
  border: 1.5px solid var(--violet); border-radius: 10px;
  text-decoration: none; cursor: pointer;
  font-family: var(--font-body); font-weight: 600; font-size: 14px;
  line-height: 1.2;
  transition: background 0.2s, border-color 0.2s;
}
.btn-secondary:hover { background: rgba(91,75,212,0.06); }
.btn-secondary:focus-visible {
  outline: 2px solid var(--violet); outline-offset: 2px;
}

/* The error page's is bigger, and sits beside a full-size .btn-primary. */
.error-actions .btn-secondary {
  padding: 12px 28px; border-width: 2px;
}

/* Upgrade prompt on QR/public pages */
.upgrade-banner {
  background: rgba(91,75,212,0.06); border: 1px solid rgba(91,75,212,0.15);
  border-radius: 12px; padding: 16px 20px;
  text-align: center; margin: 20px auto; max-width: 480px;
  font-size: 14px; color: rgba(27,27,47,0.7);
}
.upgrade-banner a {
  color: var(--violet); font-weight: 600; text-decoration: none;
}
.upgrade-banner a:hover { text-decoration: underline; }

/* QR code responsive container */
.qr-container svg {
  width: 100%; height: auto; display: block;
}

/* ---- ANALYTICS ----

   Its own container class. It borrowed .dash-pages, which meant it inherited
   a width chosen for a single-column list of page cards, and anyone tuning
   My Pages silently moved analytics too. Width comes from the shared block
   near .dash-body; the gutter comes from .dash-body. */
.dash-analytics {
  padding: 0;
}
.analytics-cards {
  /* auto-fit with a floor, not a fixed pair of columns: at --dash-max there
     is room for four stat cards, and a two-column grid at 1100px leaves two
     very wide boxes with one number each. Collapses to two, then one, on the
     way down without needing a breakpoint of its own.

     minmax(220px, 1fr), never 1fr on its own — a grid item's default
     min-width is auto, so 'Total page views' plus 48px of padding made each
     column wider than its share and pushed the row off a 320px screen. */
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px; margin-bottom: 24px;
}
.analytics-stat-card {
  background: #fff; border: 1px solid rgba(27,27,47,0.08);
  border-radius: 12px; padding: 24px; text-align: center;
}
.analytics-stat-value {
  display: block; font-family: var(--font-head); font-weight: 700;
  font-size: 36px; color: var(--violet); line-height: 1.2;
}
.analytics-stat-label {
  display: block; font-size: 14px; color: rgba(27,27,47,0.5);
  margin-top: 4px;
}
/* One gap between panels, everywhere on the screen. .analytics-section had no
   margin of its own and .analytics-split had margin-bottom:16px, so the only
   space on the page was the one below a split row: Time of day sat directly on
   top of Where views came from, while the gap above it looked twice as big. */
.analytics-section {
  background: #fff; border: 1px solid rgba(27,27,47,0.08);
  border-radius: 12px; padding: 20px;
  margin-bottom: var(--analytics-gap);
}
.chart-container {
  /* stretch, not flex-end: each column then fills the full height and the
     whole column is the tap target. With flex-end a quiet day was a 4px bar,
     and 4px is not something anyone can hit with a thumb. */
  display: flex; align-items: stretch; gap: 12px;
  /* Room for a MaxBarPx (200px) bar, the value bubble above it, and the day
     label beneath. Bars are scaled server-side so they never exceed that,
     which makes this the chart's fixed height rather than just a floor. Keep
     in step with MaxBarPx in EM.Web.Dashboard.Analytics. */
  min-height: 264px; padding: 16px 0 0;
}
.chart-bar-group {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; justify-content: flex-end; gap: 6px;
  cursor: pointer; background: none; border: 0; padding: 0;
  -webkit-tap-highlight-color: transparent;
}
.chart-bar {
  position: relative;
  width: 100%; max-width: 48px; min-height: 4px;
  background: var(--violet); border-radius: 4px 4px 0 0;
  transition: height 0.3s, background 0.15s;
}
/* The value floats above its bar rather than sitting in the column flow, so a
   four-figure count is not constrained by a 30px-wide column on a phone. */
.chart-value {
  position: absolute; bottom: 100%; left: 50%;
  transform: translateX(-50%); margin-bottom: 6px;
  font-family: var(--font-head); font-size: 11px; font-weight: 600;
  color: white; background: var(--deep-night);
  padding: 3px 7px; border-radius: 6px; white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity 0.15s;
}
.chart-bar-group:focus-visible .chart-value,
.chart-bar-group.is-active .chart-value { opacity: 1; }
.chart-bar-group.is-active .chart-bar { background: var(--violet-light); }
/* Hover behind a media query. A touch browser leaves a tapped element in
   :hover until you tap somewhere else, so an unguarded :hover kept the value
   showing after the tap that was meant to dismiss it — the second tap looked
   dead. Pointer devices are unaffected. */
@media (hover: hover) and (pointer: fine) {
  .chart-bar-group:hover .chart-value { opacity: 1; }
  .chart-bar-group:hover .chart-bar { background: var(--violet-light); }
}
.chart-bar-group:focus-visible {
  outline: 2px solid var(--violet); outline-offset: 2px; border-radius: 8px;
}
.chart-label {
  font-size: 11px; color: rgba(27,27,47,0.4);
}
.analytics-empty {
  color: rgba(27,27,47,0.4); font-size: 14px;
  text-align: center; padding: 32px 0;
  width: 100%;
}

/* Character counter */
.char-count {
  text-align: right; font-size: 11px;
  color: rgba(27,27,47,0.3); margin-top: 4px;
}

/* ---- FLASH MESSAGES ---- */
.flash {
  padding: 14px 18px;
  border-radius: 12px;
  font-size: 14px; font-weight: 500;
  margin-bottom: 20px;
}
.flash:empty, .flash.hidden { display: none; }
.flash-success {
  background: rgba(0,229,160,0.1);
  border: 1px solid rgba(0,229,160,0.2);
  color: #00B880;
}
.flash-error {
  background: rgba(231,76,60,0.08);
  border: 1px solid rgba(231,76,60,0.15);
  color: #C0392B;
}
.flash-info {
  background: rgba(91,75,212,0.06);
  border: 1px solid rgba(91,75,212,0.12);
  color: var(--violet);
}

/* ---- WHAT JUST HAPPENED, ON THE SUBSCRIPTION SCREEN ----

   The one message on this page that a person has been WAITING for: they have
   just pressed pay and come back to find out whether it worked. It had no
   rule of its own at all, so it drew as body text at the top of a long page
   and was missed by the person who wrote the flow. [4 Sep 2026]

   Deliberately the same two colours as .flash-success and .flash-warn rather
   than a third palette: a customer who has seen a green box mean "done"
   everywhere else in the app should not have to learn a new one here. It is
   not .flash itself because this block holds a headline, a sentence and
   sometimes a button, and .flash is one line of text.

   AMBER, NOT RED, for the bad case -- see TEMPlanOutcomeView.StateClass.
   Nothing has gone wrong that costs them anything. */
.plan-outcome {
  padding: 18px 20px;
  border-radius: 12px;
  border: 1px solid transparent;
  margin-bottom: 24px;
}
.plan-outcome-head {
  margin: 0;
  font-family: 'Space Grotesk', sans-serif;
  font-size: 17px; font-weight: 600;
}
.plan-outcome-body {
  margin: 6px 0 0;
  font-size: 14px; line-height: 1.5;
  color: var(--ink);
}
.plan-outcome .btn-primary { margin-top: 14px; }

.plan-outcome--good {
  background: rgba(0,229,160,0.1);
  border-color: rgba(0,229,160,0.25);
}
.plan-outcome--good .plan-outcome-head { color: #00926A; }

/* The same amber .flash-warn and the Unverified badge use. */
.plan-outcome--warn {
  background: rgba(255,176,32,0.12);
  border-color: rgba(255,176,32,0.35);
}
.plan-outcome--warn .plan-outcome-head { color: #7a4b00; }

/* ---- SCROLL REVEAL ---- */
.reveal {
  opacity: 0; transform: translateY(24px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal.visible {
  opacity: 1; transform: translateY(0);
}

/* ---- SETTINGS ---- */
/* Width and gutter come from the shared block near .dash-body, so the page
   itself is --dash-max like every other dashboard screen. */
.settings-page {
  padding: 0;
}
/* Settings is one readable column, centred inside that container.

   Capped because .settings-row is space-between: at the full 1100px the
   label and its value end up at opposite edges of the screen. Centred — and
   applied to every direct child rather than to .settings-section alone —
   because capping only the cards left the heading at x=0 and the cards
   floating beside it. The column moves as one thing or it looks broken.

   margin-left/right only, so each child keeps its own vertical rhythm. */
.settings-page > * {
  max-width: 720px;
  margin-left: auto;
  margin-right: auto;
}
/* margin-bottom longhand, NOT `margin: 0 0 8px`. The shorthand also writes
   margin-left/right: 0, and `.settings-page h2` (0,1,1) outranks the
   centring rule above (0,1,0) — so the shorthand silently pinned the heading
   to the left edge while the cards centred correctly beneath it. Same trap
   as `.form-group label` over `.em-check`; see the CSS notes in
   webstencils-htmx-reference.md. Set only what the rule means. */
.settings-page h2 {
  font-family: var(--font-head);
  font-size: 22px; font-weight: 700;
  color: var(--deep-night);
  margin-bottom: 8px;
}
.settings-placeholder {
  color: rgba(27,27,47,0.5);
  font-size: 14px; margin-bottom: 32px;
}
.settings-section {
  background: white; border-radius: 12px;
  border: 1px solid rgba(27,27,47,0.08);
  padding: 20px 24px;
  /* Width and centring come from .settings-page > * above — the whole
     column is capped together so the heading and the cards stay aligned. */
}
.settings-section h3 {
  font-family: var(--font-head);
  font-size: 15px; font-weight: 600;
  color: var(--deep-night);
  margin: 0 0 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(27,27,47,0.06);
}
.settings-row {
  display: flex; justify-content: space-between;
  align-items: center;
  padding: 10px 0;
  font-size: 14px;
}
.settings-row + .settings-row {
  border-top: 1px solid rgba(27,27,47,0.04);
}
.settings-label {
  color: rgba(27,27,47,0.5);
}
.settings-value {
  color: var(--deep-night); font-weight: 500;
}
/* The verification row carries a badge and, when unverified, an action beside
   it -- so this one value is a flex line rather than a bare span. */
.settings-row .settings-value {
  display: inline-flex; align-items: center; gap: 10px;
  flex-wrap: wrap; justify-content: flex-end;
}

/* Verified / unverified state. Mint for done and amber for outstanding, the
   same pairing .flash-success and .flash-warn use: amber because nothing has
   gone wrong, there is simply something still to do, and red beside a real
   error would read as two faults. */
.verify-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600; line-height: 18px;
}
.verify-badge--yes {
  background: rgba(0,229,160,0.12);
  border: 1px solid rgba(0,229,160,0.28);
  color: #00875F;
}
/* The holding page for a claimed-but-unverified name. Sits on .em-page, so it
   inherits the dark public-page treatment rather than inventing a third look
   for a page most people will see once. */
.em-pending-mark {
  width: 64px; height: 64px; margin: 0 auto 24px;
  border-radius: 50%;
  background: rgba(162,155,254,0.14);
  border: 2px solid var(--lilac);
  position: relative;
}
.em-pending-mark::after {
  content: ''; position: absolute;
  top: 50%; left: 50%;
  width: 14px; height: 14px; margin: -7px 0 0 -7px;
  border-radius: 50%;
  background: var(--mint);
}
.em-pending-note {
  margin: 28px auto 0; max-width: 34ch;
  font-size: 14px; line-height: 1.6;
  color: rgba(248,247,255,0.55);
}
.em-pending-note a { color: var(--lilac); text-decoration: none; font-weight: 500; }
.em-pending-note a:hover { text-decoration: underline; }

/* The date beside the tick. Quiet on purpose: the badge is the answer, the
   date is the detail somebody asks for second. */
.settings-meta {
  font-size: 13px; font-weight: 400; color: rgba(27,27,47,0.5);
}

/* ---- RESPONSIVE ---- */
/* WHEN THE TWO COLUMNS STOP FITTING, THE PREVIEW GOES UNDERNEATH.

   The editor needs about 570px before its own rows start to look cramped, so
   two columns want 570 + 24 gap + 320 preview = 914, plus the 2x32 gutter
   .dash-body adds: 978. Below that the preview stacks and the editor gets the
   whole width.

   Stated as a breakpoint rather than as a minmax() floor on the track,
   because a floor the container cannot honour is not a floor -- the grid
   would overflow again rather than reflow, which is the bug this pair of
   rules exists to fix. The breakpoint reflows; a minimum only asserts.

   980 and the 570 above are ONE decision written in two places, and CSS
   gives no way to share a number with a media query. Change one, change the
   other: 980 - 64 - 24 - 320 = 572. */
@media (max-width: 980px) {
  .dash-editor { grid-template-columns: minmax(0, 1fr); gap: 16px; }
  /* Not sticky once it is underneath: a panel this tall would pin most of
     the screen to itself. Capped and centred so a full-width phone mockup on
     a tablet still reads as a phone. */
  .dash-preview-panel {
    position: static; top: auto;
    max-width: 320px; margin: 0 auto;
  }
}

@media (max-width: 768px) {
  /* Marketing nav: wrap the links onto a second row rather than hiding them.
     Same shape as .dash-topbar-center below. Log in must stay reachable at
     every breakpoint — hiding it stranded returning users on a phone. */
  .em-nav { padding: 12px 16px; flex-wrap: wrap; row-gap: 6px; column-gap: 10px; }
  /* Logo and CTA share row one — at 20px the wordmark plus the CTA is
     374px, wider than a 390px phone, and the CTA drops to its own row.
     These sizes keep both on one line down to 360px; below that the CTA
     takes a row of its own, which is still fully usable. */
  .em-nav .nav-logo { font-size: 15px; gap: 6px; }
  .em-nav .nav-logo svg { width: 24px; height: 24px; }
  .nav-links {
    order: 3; flex-basis: 100%; margin-left: 0;
    justify-content: center; flex-wrap: wrap;
    /* Scales with the screen so the four links stay on one row at 320px
       without looking cramped at 430px. */
    gap: 8px clamp(12px, 4vw, 24px);
    border-top: 1px solid rgba(27, 27, 47, 0.06);
    padding-top: 8px; margin-top: 2px;
  }
  .nav-links a { font-size: 13px; opacity: 0.75; padding: 2px 0; }
  .nav-cta { margin-left: 0; padding: 9px 13px; font-size: 12.5px; }
  section[id] { scroll-margin-top: 104px; }
  section { padding: 64px 20px; }
  /* Two-row nav is ~38px taller than the old one-row nav. */
  .hero { padding: 148px 20px 60px; }
  /* flex-direction:column doesn't stretch every child to the container's
     width the way a block layout would -- WebKit in particular leaves a
     text <input> at its own intrinsic size (historically ~170-190px from
     its default `size`) inside a column flex box rather than filling the
     cross axis, so it (and the prefix label next to it) can end up
     narrower than the white card around them and sitting off-centre
     within it. width: 100% + box-sizing: border-box (global reset) pins
     both to the card's own width so they can't drift. */
  .hero-form, .cta-form { flex-direction: column; }
  .hero-form .prefix, .cta-form .prefix { padding: 10px 14px 0; width: 100%; }
  .hero-form input, .cta-form input { padding: 8px 14px; width: 100%; }
  .hero-form button, .cta-form button { width: calc(100% - 8px); margin: 4px; }
  .journey { flex-direction: column; gap: 16px; }
  .journey-arrow { width: 2px; height: 30px; margin: 0; }
  .journey-arrow::after {
    right: auto; top: auto; bottom: -1px; left: -4px;
    border: 5px solid transparent;
    border-top: 6px solid rgba(91,75,212,0.25);
    border-left: 5px solid transparent;
  }
  .persona-panel { grid-template-columns: 1fr; }
  .how-flow { grid-template-columns: 1fr; gap: 40px; }
  .how-flow::before { display: none; }
  /* minmax(0, 1fr), not 1fr: a grid item's default min-width is auto, so a
     long word in a card made the column wider than its share and pushed the
     grid past the viewport. */
  .case-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .price-grid { grid-template-columns: 1fr; }
  .proof-inner { gap: 32px; }
  .footer-top { flex-direction: column; text-align: center; }
  .footer-group { justify-content: center; }
  .footer-links { justify-content: center; }
  /* Dashboard topbar responsive */
  .dash-topbar {
    flex-wrap: wrap; height: auto; padding: 10px 16px; gap: 8px;
  }
  .dash-topbar-left { flex: 1; min-width: 0; }
  .dash-topbar-center {
    order: 3; flex-basis: 100%; justify-content: center;
    border-top: 1px solid rgba(27,27,47,0.06);
    padding-top: 8px; margin-top: 2px;
  }
  /* The right cluster is fixed content — never let it be squeezed, and never
     let the logo grow into it. .dash-topbar-left already has min-width: 0,
     but .dash-logo did not, so the username ran straight under the tier
     badge: 262px of logo inside a 133px box. */
  .dash-topbar-right { gap: 8px; flex-shrink: 0; }
  .dash-wordmark { display: none; }
  .dash-view-live { display: none; }
  .dash-logout { padding: 4px 6px; font-size: 12px; }
  .dash-logo { font-size: 13px; gap: 6px; min-width: 0; }
  .dash-username {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    min-width: 0;
  }
  .dash-logo svg { width: 22px; height: 22px; }
  .dash-tab { padding: 6px 10px; font-size: 12px; }
  /* Dashboard layout responsive */
  /* THE EDITOR/PREVIEW COLLAPSE MOVED OUT of this block on 9 Sep 2026, to
     the 980px query beside .dash-editor. It was never a phone rule: the two
     columns stop fitting at about 980, so between 769 and 980 this block was
     not yet in force and the grid overflowed instead of reflowing. Collapsing
     at the width the columns actually stop fitting is the whole fix; 768 was
     a guess that happened to be far enough down to look right on a phone.

     What is left here is genuinely phone-only. */
  .dash-body { padding: 16px 12px; }
  .dash-edit-panel { padding: 18px 16px; }
  /* Full width here — a 240px button off to one side is a worse thumb
     target than one that spans the card. */
  .dash-actions .btn-primary { flex: 1 1 auto; max-width: none; }
  .dash-onboarding { padding: 12px 14px; gap: 10px; margin-bottom: 16px; }
  /* Link rows: grip, icon and text on the first line, the edit and delete
     buttons on their own line underneath. At this width there is not room
     for both and the title was running under the icons. */
  .link-card { flex-wrap: wrap; row-gap: 4px; }
  .link-card .link-actions {
    flex-basis: 100%; justify-content: flex-end;
    border-top: 1px solid rgba(27,27,47,0.06);
    padding-top: 4px; margin-top: 0;
  }
  /* Three theme buttons in a row need ~350px of min-content; the panel has
     under 300px here. Stack them — they are easier to tap that way too. */
  .theme-buttons { flex-direction: column; }
  /* My Pages responsive. Same shape as the link cards above: identity on
     the first line, actions on their own line underneath. Four 34px icons
     plus the avatar leave under 100px for the name and URL otherwise. */
  /* No .dash-pages padding here — .dash-body above owns the gutter on every
     breakpoint, and a second one made this screen inset further than the
     editor beside it. */
  .page-card { padding: 12px; flex-wrap: wrap; row-gap: 6px; }
  .page-card-info { flex: 1 1 100%; min-width: 0; }
  .page-card-actions {
    flex-basis: 100%; justify-content: flex-end;
    border-top: 1px solid rgba(27,27,47,0.06);
    padding-top: 4px;
  }
  .em-avatar--card { width: 36px; height: 36px; font-size: 12px; }
  .page-card-name { font-size: 14px; }
  .page-action { width: 30px; height: 30px; }
  /* Legal pages responsive */
  .legal-hero { padding: 138px 20px 30px; }
  .legal-body { padding: 30px 20px 80px; }
  .data-table { font-size: 13px; }
  .data-table th, .data-table td { padding: 8px 10px; }
  /* Seven columns in ~300px. Narrow the gaps rather than the columns —
     the column is the tap target. */
  .chart-container { gap: 6px; min-height: 236px; }
  .chart-label { font-size: 10px; }
  .tier-comparison { grid-template-columns: 1fr; }
}

/* ── Avatar cropper ───────────────────────────────────────────────────────
   Fixed overlay. body.cropper-open stops the page scrolling underneath —
   without it, dragging the photo on a phone scrolls the page instead. */
body.cropper-open { overflow: hidden; }

.cropper-backdrop {
  display: none;
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(27,27,47,0.72);
  align-items: center; justify-content: center;
  padding: 16px;
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
}
.cropper-backdrop.is-open { display: flex; }

.cropper-panel {
  background: var(--ghost);
  border-radius: 16px;
  padding: 20px;
  width: 100%; max-width: 360px;
  box-sizing: border-box;
  text-align: center;
}
.cropper-title {
  font-family: var(--font-display);
  font-size: 18px; margin: 0 0 14px;
  color: var(--deep-night);
}

/* Square stage; the circle is a mask drawn over it. touch-action:none is what
   stops the browser claiming the drag as a scroll gesture. */
.cropper-stage {
  position: relative;
  width: 100%; aspect-ratio: 1 / 1;
  margin: 0 auto;
  overflow: hidden;
  border-radius: 12px;
  background: #1B1B2F;
  touch-action: none;
  cursor: grab;
  user-select: none;
}
.cropper-stage:active { cursor: grabbing; }
.cropper-stage img {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  max-width: none;          /* the JS sets explicit px dimensions */
  pointer-events: none;     /* drags belong to the stage, not the image */
  -webkit-user-drag: none;
}
/* The dim-outside-the-circle effect, without a second element per edge. */
.cropper-mask {
  position: absolute; inset: 0;
  box-shadow: 0 0 0 9999px rgba(27,27,47,0.55);
  border-radius: 50%;
  pointer-events: none;
}

.cropper-hint {
  font-size: 12px; color: rgba(27,27,47,0.55);
  margin: 12px 0 6px;
}
.cropper-zoom { width: 100%; margin: 0 0 16px; }
.cropper-zoom:focus-visible { outline: 2px solid var(--violet); outline-offset: 3px; }

/* The make-default checkbox, between the zoom slider and the buttons.

   [hidden] needs the explicit display:none because .em-check sets display
   on the label, and a class rule beats the UA stylesheet's [hidden]. */
.cropper-default {
  margin: 14px 0 0; align-self: flex-start;
  color: rgba(27,27,47,0.7); font-size: 13px;
}
.cropper-default[hidden] { display: none; }
.cropper-default-label { user-select: none; }

.cropper-actions { display: flex; gap: 10px; }
/* 44px minimum: the mobile audit flagged sub-44px targets, and this one is
   reached mid-gesture with a thumb. */
.cropper-btn { flex: 1; min-height: 44px; box-sizing: border-box; }
.cropper-actions .btn-primary { width: auto; }

/* ── Rate limit page ──────────────────────────────────────────────────────
   Shown mid-login, so it borrows the auth card rather than the error page
   styling. The clock is the focal point: the one thing the visitor needs. */
.rate-limit-panel {
  margin: 24px 0 8px;
  padding: 24px 20px;
  border-radius: 14px;
  background: rgba(91,75,212,0.06);
  border: 1px solid rgba(91,75,212,0.15);
  text-align: center;
}
.rate-limit-clock {
  display: flex; align-items: baseline; justify-content: center; gap: 8px;
  margin-bottom: 10px;
}
.rate-limit-seconds {
  font-family: var(--font-display);
  font-size: 48px; font-weight: 700; line-height: 1;
  color: var(--violet);
  /* Tabular figures stop the layout jittering as digits change. */
  font-variant-numeric: tabular-nums;
}
.rate-limit-unit {
  font-size: 14px; color: rgba(27,27,47,0.55);
}
.rate-limit-note {
  font-size: 13px; color: rgba(27,27,47,0.6); margin: 0 0 16px;
}
.rate-limit-retry {
  display: inline-block; width: auto; min-width: 160px; min-height: 44px;
  box-sizing: border-box; text-decoration: none; text-align: center;
  line-height: 20px;
}
/* Disabled until the countdown finishes. aria-disabled rather than removing
   the href, so the link stays focusable and a screen reader announces why. */
.rate-limit-retry[aria-disabled="true"] {
  opacity: 0.45; pointer-events: none;
}

/* Attempts-remaining warning on the login form. Amber, not red: nothing has
   gone wrong yet, and red next to a genuine error would read as two faults. */
.flash-warn {
  background: rgba(255,176,32,0.12);
  border: 1px solid rgba(255,176,32,0.35);
  color: #7a4b00;
}

/* ─── Share page (QR + cards) ──────────────────────────────────────────────
   /dashboard/pages/{id}/share. Tabbed: Share Cards | QR Code.
   Light-card style matching .settings-section and the edit page.
   Size switching is JS — no page reload, no scroll jump. */
/* Width and gutter come from the shared block near .dash-body. */
.dash-share { padding: 0; }

/* Reuses .link-tabs / .link-tab / .link-tab-panel from the link editor —
   no custom tab classes needed. The share-specific wrapper just adds
   spacing above the panels. */
/* The QR controls use .form-group + <label>, the dashboard's own control
   pattern — no share-specific label styling, deliberately. A second set of
   caption rules is a second thing to keep in step with the first. */

.dash-share .link-tabs { margin-bottom: 0; }
.dash-share .link-tab-panel.is-active {
  background: white; border-radius: 0 0 12px 12px;
  border: 1px solid rgba(27,27,47,0.08); border-top: none;
  padding: 20px 24px;
}

.share-note {
  color: rgba(27,27,47,0.5); font-size: 14px; line-height: 1.6;
  margin: 0 0 16px; max-width: 62ch;
}
.share-note--quiet { color: rgba(27,27,47,0.38); font-size: 13px; margin-top: 14px; }

/* Download button — compact pill, used for SVG / PNG / JPEG downloads. */
.btn-download {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 14px; border-radius: 8px;
  border: 1px solid rgba(27,27,47,0.12);
  background: rgba(91,75,212,0.06);
  color: var(--violet); font-size: 13px; font-weight: 600;
  font-family: var(--font-body); text-decoration: none;
  transition: all .15s ease; cursor: pointer;
}
.btn-download:hover {
  background: rgba(91,75,212,0.14); border-color: var(--violet);
  color: var(--violet);
}
.btn-download svg { flex-shrink: 0; }

/* Page sub-nav — secondary row beneath the topbar */
.page-subnav {
  background: white;
  border-bottom: 1px solid rgba(27,27,47,0.08);
  padding: 0 24px;
  display: flex; align-items: center; gap: 8px;
  height: 36px;
  font-family: var(--font-body); font-size: 13px;
  position: sticky; top: 56px; z-index: 99;
}
.page-subnav-slug {
  font-weight: 600; color: var(--deep-night); font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 240px;
}
.page-subnav-sep {
  color: rgba(27,27,47,0.25); font-size: 12px; flex-shrink: 0;
}
.page-subnav-links { display: flex; gap: 2px; }
.page-subnav-links a {
  padding: 4px 12px; border-radius: 6px;
  text-decoration: none; font-weight: 500; font-size: 13px;
  color: rgba(27,27,47,0.45); transition: background .15s, color .15s;
}
.page-subnav-links a:hover { background: rgba(91,75,212,0.08); color: var(--violet); }
.page-subnav-links a.is-active {
  background: rgba(91,75,212,0.1); color: var(--violet); font-weight: 600;
}
/* THE PENDING DOT, in both places it appears: beside 'Connection requests'
   in the strip and beside 'My Connections' in the top menu. Mint, which is
   the palette's one "something good is here" colour -- a request is an
   invitation, not a warning, so nothing amber and certainly nothing red.

   A DOT AND NEVER A NUMBER. What it says is "something wants an answer",
   which is a yes or no; a count there would read as inventory and compete
   with the count the Connections link already carries. */
.subnav-dot, .dash-tab-dot {
  display: inline-block; width: 7px; height: 7px; margin-left: 6px;
  border-radius: 50%; background: var(--mint);
  /* Lifted off the baseline so it sits against the cap height rather than
     hanging under the word like a full stop. */
  vertical-align: 2px;
}
/* On the top menu it has a dark bar behind it and needs a touch more
   presence, and the tab's own padding already spaces it. */
.dash-tab-dot { width: 6px; height: 6px; }

@media (max-width: 768px) {
  .page-subnav { padding: 0 16px; top: auto; position: relative; }
}

.share-sizes { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
.share-size {
  display: inline-block; padding: 7px 14px; border-radius: 999px;
  border: 1px solid rgba(27,27,47,0.12); color: rgba(27,27,47,0.55);
  font-size: 13px; text-decoration: none; cursor: pointer;
  background: none; font-family: var(--font-body);
  transition: all .15s ease;
}
.share-size:hover { border-color: var(--violet); color: var(--deep-night); }
.share-size--on {
  background: var(--violet); border-color: var(--violet); color: #fff;
}

/* Light border plate behind the QR — keeps the code visually distinct
   from the white card it sits on. */
.share-preview {
  display: inline-block; padding: 20px; border-radius: 12px;
  background: #fff; border: 1px solid rgba(27,27,47,0.06);
}
.share-qr { display: block; max-width: 100%; height: auto; }

.share-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }

.share-cards {
  display: grid; gap: 18px; margin-top: 4px;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.share-card { margin: 0; }
.share-card img {
  width: 100%; height: auto; display: block; border-radius: 12px;
  border: 1px solid rgba(27,27,47,0.08);
  background: rgba(27,27,47,0.03);
}
.share-card figcaption {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 8px; font-size: 12px; line-height: 1.6;
  color: rgba(27,27,47,0.5);
}

@media (max-width: 600px) {
  .share-preview { padding: 14px; }
  .share-cards { grid-template-columns: 1fr 1fr; }
}

/* ─── Light-surface use-case cards ─────────────────────────────────────────
   .case-card was written for .use-cases, which is deep night — so its text is
   white and its surface is a 4% white wash. Dropped onto a light section that
   reads as white-on-white: the persona pages shipped that way for one build.

   This is the variant for light bands. Both selectors are doubled
   (.case-card.case-card--light) rather than relying on being later in the
   file: same-specificity rules resolve by source order, which is a rule that
   holds right up until someone reorganises the stylesheet. */
.case-card.case-card--light {
  background: #fff;
  border: 1px solid rgba(27,27,47,0.08);
  box-shadow: 0 1px 2px rgba(27,27,47,0.04);
}
.case-card.case-card--light:hover {
  background: #fff;
  border-color: rgba(91,75,212,0.28);
  box-shadow: 0 8px 24px rgba(27,27,47,0.07);
}
.case-card.case-card--light h3 { color: var(--deep-night); }
.case-card.case-card--light p  { color: rgba(27,27,47,0.6); }
.case-card.case-card--light p:last-child { margin-bottom: 0; }
.case-card.case-card--light strong { color: var(--violet); font-weight: 600; }

/* The base grid is a fixed four columns, which strands three cards in a
   four-wide row. This one fits what it is given. */
.case-grid.case-grid--auto {
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 18px;
}

/* Alternating band, so a long page is not one continuous slab. */
.how-section.band--white { background: #fff; }

@media (max-width: 720px) {
  .case-grid.case-grid--auto { grid-template-columns: 1fr; }
}

/* ---- BLOCKED ACCOUNT BANNER ----
   #C0392B is the same red .flash-error uses and the admin app's blocked pill,
   so "blocked" reads identically to the person and to the staff member who
   blocked them. Solid rather than tinted: a tinted panel reads as one more
   notice, and this one has to be the first thing seen. */
.dash-blocked {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 16px 24px;
  background: #C0392B; color: #fff;
  box-shadow: inset 0 -1px 0 rgba(0,0,0,0.15);
}
.dash-blocked-icon { flex-shrink: 0; margin-top: 2px; }
.dash-blocked-text { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
.dash-blocked-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.dash-blocked-title { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
.dash-blocked-cat {
  font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 3px 8px; border-radius: 6px;
  background: rgba(255,255,255,0.18); border: 1px solid rgba(255,255,255,0.32);
}
.dash-blocked-reason { font-size: 14px; margin: 0; overflow-wrap: anywhere; }
.dash-blocked-when { font-size: 13px; color: rgba(255,255,255,0.85); }
.dash-blocked-appeal {
  flex-shrink: 0; align-self: center;
  padding: 8px 16px; border-radius: 10px;
  background: rgba(255,255,255,0.16); border: 1px solid rgba(255,255,255,0.35);
  color: #fff; font-size: 13px; font-weight: 600; text-decoration: none;
  white-space: nowrap;
}
.dash-blocked-appeal:hover { background: rgba(255,255,255,0.26); }

@media (max-width: 720px) {
  /* The appeal button drops under the text rather than squeezing the reason
     into a column two words wide. */
  .dash-blocked { flex-wrap: wrap; padding: 14px 16px; }
  .dash-blocked-appeal { align-self: flex-start; margin-left: 36px; }
}


/* ---- THEMES (Pro) ----
   Screens: /dashboard/themes (list) and the theme editor, which reuses
   .dash-editor / .dash-edit-panel so it IS the page editor's layout rather
   than a copy of it. Only what is genuinely new lives here. */

/* The way out of a screen that is reached from one place. Above the heading
   rather than beside it, so it is the first thing in the reading order and
   does not compete with the primary action on the same line. */
.back-link {
  display: inline-block; margin-bottom: 14px;
  font-size: 13px; font-weight: 500;
  color: rgba(27,27,47,0.55); text-decoration: none;
}
.back-link:hover { color: var(--violet); }

.themes-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; margin-bottom: 20px;
}
.themes-head h2 { margin-bottom: 4px; }
/* .btn-primary is display:block; width:100%, which is right for a form's
   commit button and very wrong for a flex item — it stretched across the whole
   header and squeezed the heading and its description into a 170px column.
   .btn-compact is the existing answer to that (it already sets width:auto);
   this only stops the button being squeezed by a long heading beside it. */
.themes-head .btn-primary { flex-shrink: 0; }
/* The heading column takes the space the button does not. */
.themes-head > div { min-width: 0; }

.theme-grid {
  display: grid;
  /* auto-fill with a floor rather than a fixed column count: three cards at
     --dash-max, two on a tablet, one on a phone, and no rule per breakpoint.
     A fixed 3 would strand a single card in a third of the screen. */
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 16px;
}
.theme-card {
  background: white; border-radius: 14px;
  border: 1px solid rgba(27,27,47,0.06);
  overflow: hidden;
  display: flex; flex-direction: column;
}
.theme-card-swatches { display: flex; height: 56px; }
.theme-swatch-band { flex: 1; }
.theme-card-body { padding: 16px 18px 10px; flex: 1; }
.theme-card-name {
  font-family: var(--font-head); font-size: 16px; font-weight: 700;
  margin-bottom: 4px;
}
.theme-card-summary { font-size: 13px; color: rgba(27,27,47,0.6); }
.theme-card-usage {
  font-size: 12px; color: rgba(27,27,47,0.45); margin-top: 6px;
}
.theme-card-actions {
  display: flex; gap: 8px; align-items: center;
  padding: 12px 18px 16px;
}
.theme-delete-form { margin: 0; }

.themes-empty {
  background: white; border-radius: 14px;
  border: 1px solid rgba(27,27,47,0.06);
  padding: 32px; text-align: center;
}
.themes-empty p { margin-bottom: 10px; }
.themes-empty .settings-hint { margin: 0 auto 18px; max-width: 460px; }

/* Destructive, but quiet: deleting a theme cannot lose a page, so this is not
   a red button competing with Save for attention. */
/* Metrics copied from .btn-secondary deliberately: it sits directly beside one
   on every theme card, and 8px/13px against 9px/14px made the pair look
   misaligned rather than quiet. Only the colour and border weight differ. */
.btn-danger-quiet {
  display: inline-block;
  background: none; border: 1.5px solid rgba(231,76,60,0.35);
  color: #c0392b; border-radius: 10px;
  padding: 9px 18px; font-size: 14px; font-weight: 600;
  line-height: 1.2;
  cursor: pointer; font-family: var(--font-body);
}
.btn-danger-quiet:hover { background: rgba(231,76,60,0.06); }

/* A section label sits on a rule; the sentence explaining the section needs
   air under it before the controls start. */
.form-section-label + .settings-hint { margin-bottom: 14px; }

/* The six colours, two-up on a wide panel and one-up on a phone. A single
   420px column left the right half of a ~700px card empty and made the list
   read as a long thin strip — the "mess" this replaces. auto-fit with a floor
   means no breakpoint of its own. */
.theme-colour-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 2px 24px;
}

/* A colour: its "in use" checkbox on the left, the swatch on the right.
   space-between so the swatches line up in a column however long the captions
   get. .em-check owns the checkbox itself — see its own note in this file.
   The width now comes from the grid column, not a max-width on the row. */
.theme-colour-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 7px 0;
}
.theme-colour-row input[type="color"] {
  width: 52px; height: 32px; padding: 2px;
  border: 1px solid rgba(27,27,47,0.15); border-radius: 8px;
  background: white; cursor: pointer; flex-shrink: 0;
}

/* Two controls on one line, on the same two-column rhythm as the colour grid
   above so the whole form reads as one set of columns rather than three
   different widths stacked. */
.theme-field-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 14px 24px;
}
/* The theme editor's preview has no real page behind it, so it needs a floor
   of its own; the page editor's preview gets one from its content. */
.theme-preview-page { min-height: 420px; }

@media (max-width: 900px) {
  /* The way out of a screen that is reached from one place. Above the heading
   rather than beside it, so it is the first thing in the reading order and
   does not compete with the primary action on the same line. */
.back-link {
  display: inline-block; margin-bottom: 14px;
  font-size: 13px; font-weight: 500;
  color: rgba(27,27,47,0.55); text-decoration: none;
}
.back-link:hover { color: var(--violet); }

.themes-head { flex-direction: column; }
}

/* ---- PRO ANALYTICS ----
   Only what is genuinely new. The stat cards, section cards, headings and the
   whole chart interaction (hover, focus, tap-to-reveal, the touch guard) come
   from the Free analytics screen and .chart-container above; this adds a
   SECOND bar per column, the range controls, and the ranked lists. */

/* Range controls. Presets are links, so every state of this screen is a URL
   somebody can bookmark or send to themselves — which is also why there is no
   "apply" for them, only for the custom dates. */
/* Range, From, To and Apply on ONE line — the shape the page editor's schedule
   row already uses, with .form-group keeping its stacked label because the
   dashboard has exactly one control-labelling pattern.

   This replaced a row of eight chips. The chips wrapped to two lines at some
   widths, and a row of pills where one is filled reads as a set of buttons
   rather than as the current value of a control. */
.range-bar {
  display: flex; align-items: flex-end; flex-wrap: wrap;
  gap: 12px; margin-bottom: 20px;
}
.range-bar .form-group { margin: 0; }
.range-bar input[type="date"] {
  padding: 9px 10px; border-radius: 8px;
  border: 1px solid rgba(27,27,47,0.15);
  font-family: var(--font-body); font-size: 14px;
  background: white; color: var(--deep-night);
}
.range-bar select { min-width: 180px; }
/* From and To travel together. Without this the row has three independent
   children and a narrow screen drops To on to its own line, leaving From
   stranded beside the range picker — the same failure the link schedule row
   hit, fixed the same way (.sched-inputs). */
.range-dates {
  display: flex; flex-wrap: nowrap; align-items: flex-end;
  gap: 12px; min-width: 0;
}
.range-dates .form-group { min-width: 0; }
.range-dates input[type="date"] { width: 100%; min-width: 0; }
.range-bar select:focus-visible,
.range-bar input[type="date"]:focus-visible {
  outline: 2px solid var(--violet); outline-offset: 1px;
}

.analytics-section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.chart-legend {
  font-size: 12px; color: rgba(27,27,47,0.5);
  display: flex; align-items: center; gap: 6px;
}
.chart-key {
  display: inline-block; width: 9px; height: 9px; border-radius: 2px;
  flex-shrink: 0;
}
.chart-key--views { background: var(--violet); }
.chart-key--clicks { background: var(--mint); }

.analytics-highlights {
  display: flex; flex-wrap: wrap; gap: 8px 20px;
  font-size: 13px; color: rgba(27,27,47,0.6);
  margin: -8px 0 20px;
}

/* Two panels side by side, collapsing to one column. auto-fit with a floor
   rather than a fixed pair, for the same reason as the theme grid: a single
   panel should fill the row rather than sit in half of it. */
.analytics-split {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--analytics-gap); margin-bottom: var(--analytics-gap);
  /* start, not stretch: a four-row list beside a 392px chart was being padded
     out to match it, leaving 200px of empty card. Two cards of honest
     different heights read better than one with a hole in it. */
  align-items: start;
}

/* Wide charts scroll INSIDE their own box. Twenty-four hourly columns do not
   fit a phone, and the alternative — letting the page scroll sideways — is the
   thing that must never happen.

   min-width: 0 is belt and braces, and the honest note is that it fixed
   nothing measurable. A grid item defaults to min-width: auto -- never shrink
   below your content -- so the dense chart's 520px floor could in principle
   push .analytics-split wider than the screen. It does not, because an element
   with overflow other than visible already has an automatic minimum size of
   zero, and .chart-scroll has overflow-x: auto. Kept because it costs nothing
   and states the intent; NOT kept because a bug was seen.

   Measured at a real 390px viewport: page scrollWidth 390, no horizontal
   overflow, chart scrolling inside its own box. */
/* auto, not scroll: a chart that fits must show no bar at all. The seven-column
   weekday chart was showing a permanent scrollbar because it sat in the same
   wrapper as the 24-column hourly one; only the dense variant has a min-width,
   so only the dense variant ever scrolls. */
.chart-scroll { overflow-x: auto; min-width: 0; }
/* Inside a split the grid's own gap is the spacing; a margin as well would
   make the row below a split further away than any other. */
.analytics-split > .analytics-section { min-width: 0; margin-bottom: 0; }

/* The Pro charts are taller than the Free one: a trend is a shape, and 200px
   of bar across a month reads as a flat line whatever the numbers say. Keep in
   step with ProMaxBarPx in EM.Web.Dashboard.Analytics — this is that plus room
   for the value pill above and the label below. */
/* Every label reserves the same height, so every bar sits on the same
   baseline. 'd mmm' captions wrap to two lines — except the ones that do not,
   and a single-line label made its column 14px shorter than its 29 neighbours.
   Measured: one bar baseline at 828 against 814 for the rest, which reads as a
   chart with a wonky floor. Only the daily chart needs this; the hour and
   weekday captions are one word. */
.chart-container--wrap .chart-label { min-height: 2.6em; display: block; }

.chart-container--tall {
  /* ProMaxBarPx (260) + the label beneath + headroom for the pill above. */
  min-height: 372px;
  /* 6px, not the base 12px. A month is thirty columns, and at 12px the gaps
     alone are 348px — which was the difference between fitting a 1100px panel
     and not. Measured: bars and gaps came to exactly the container width, so
     there was no slack for the pill padding below. */
  gap: 6px;
}
/* Full width now, so 24 columns fit a desktop without scrolling; the min-width
   is what makes them still readable on a phone, inside .chart-scroll. */
.chart-container--dense { min-width: 640px; gap: 4px; }
.chart-container--dense .chart-label { font-size: 10px; }

/* Two bars per column, sharing one scale so a height means the same thing
   wherever it appears. They sit in a row inside the group, which keeps the
   group itself the tap target — the reason .chart-bar-group is stretch rather
   than flex-end in the first place. */
/* height: AUTO, and this is the whole of the value-pill bug.

   .chart-value is positioned bottom: 100% of its nearest positioned ancestor.
   With height: 100% here that ancestor was the full-height column, so the pill
   floated at the top of the entire chart — a large black slab hanging over the
   heading, hundreds of pixels from the bar it described. Auto makes this box
   hug the taller of its two bars, so bottom: 100% is "just above the bar",
   which is what every other chart in the app already does.

   The bars carry server-computed pixel heights, so this box has a real height
   to hug; it is not relying on content. */
.chart-pair {
  position: relative;
  display: flex; align-items: flex-end; justify-content: center;
  gap: 3px; width: 100%; height: auto;
}
.chart-bar--views { background: var(--violet); }
.chart-bar--clicks { background: var(--mint); }
.chart-bar-group.is-active .chart-bar--clicks { background: var(--mint-dark); }
@media (hover: hover) and (pointer: fine) {
  .chart-bar-group:hover .chart-bar--clicks { background: var(--mint-dark); }
}
/* The pill lists both numbers, so it needs to be readable rather than tiny —
   same call the admin app's stacked pill makes. It still floats above the
   column and is still revealed by the shared hover/focus/tap rules. */
.chart-value--pair {
  text-align: left; padding: 7px 9px; line-height: 1.5; min-width: 108px;
}
/* The pill is absolutely positioned but still counts towards the scroll box,
   so a ~100px pill centred over a 12px column hung off both ends of the chart
   and showed a permanent scrollbar under charts that plainly fitted.

   That was first fixed with 54px of padding each side, which worked and cost
   54px of chart on every card — the excessive white margins. The pill now
   opens inward at the ends instead (.pill-left / .pill-right, decided in
   Pascal because the column count is not something CSS knows), so the chart
   gets the full width of its card back.

   The top padding stays: it is headroom for the pill above a full-height bar,
   which otherwise lands on the section heading. */
/* 84px of headroom, measured rather than chosen.

   .chart-scroll sets overflow-x: auto, and a box with a non-visible overflow on
   one axis computes auto on the other — so it CLIPS anything rising above it.
   The value pill is 72px tall and floats 6px above its bar, so on a full-height
   bar it needs 78px inside the scroll box or its top is cut off. It was, by
   20px, right under the section heading.

   ProMaxBarPx came down from 300 to 260 at the same time, so reserving this
   room did not make the card taller. */
.chart-container--tall { padding: 84px 6px 0; }
.chart-bar-group.pill-left .chart-value--pair {
  left: 0; right: auto; transform: none;
}
.chart-bar-group.pill-right .chart-value--pair {
  left: auto; right: 0; transform: none;
}
/* Which bar you are pointing at, above the numbers. Without it the pill answers
   "how many" but not "when", and on a 30-column chart the label under the bar
   is not necessarily the one your finger is over. */
.chart-vhead {
  display: block; font-weight: 700; margin-bottom: 4px;
  padding-bottom: 4px; border-bottom: 1px solid rgba(255,255,255,0.18);
}
.chart-vrow { display: flex; align-items: center; gap: 6px; }

/* The pill paints ABOVE every bar, including the ones after it.

   Each .chart-bar-group is a flex item painting in document order, so a pill
   over column 4 was being covered by columns 5 onward — worst on the tall Pro
   charts, where a 100px pill overlaps several neighbours. Lifting the whole
   active or hovered GROUP rather than just the pill keeps the pill and the bars
   it describes in one stacking context, so that bar stays highlighted under it. */
.chart-bar-group.is-active,
.chart-bar-group:focus-visible { position: relative; z-index: 3; }
@media (hover: hover) and (pointer: fine) {
  .chart-bar-group:hover { position: relative; z-index: 3; }
}
.chart-value { z-index: 4; }

/* Ranked lists: a proportion bar behind the row rather than beside it, so the
   shape is readable at a glance and the text is still the thing you read. */
.rank-list { list-style: none; margin: 0; padding: 0; }
.rank-row {
  position: relative; display: flex; align-items: center; gap: 12px;
  padding: 11px 12px; border-radius: 8px;
  /* isolate: the fill is absolutely positioned behind the content, and without
     a stacking context of its own it can paint over the row above. */
  isolation: isolate;
}
.rank-row + .rank-row { border-top: 1px solid rgba(27,27,47,0.05); }
.rank-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  background: rgba(91,75,212,0.07); border-radius: 8px;
  z-index: -1;
}
.rank-body { flex: 1 1 auto; min-width: 0; }
.rank-title {
  display: block; font-size: 14px; font-weight: 600; color: var(--deep-night);
  /* A long title truncates rather than wrapping the row to three lines and
     pushing the count out of alignment with the rows above it. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rank-sub {
  display: block; font-size: 12px; color: rgba(27,27,47,0.45);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rank-count { text-align: right; flex-shrink: 0; }
.rank-count strong {
  display: block; font-family: var(--font-head); font-size: 16px;
}
.rank-share { font-size: 11px; color: rgba(27,27,47,0.4); }

@media (max-width: 620px) {
  .range-bar { flex-direction: column; align-items: stretch; }
  .range-custom { flex-wrap: wrap; }
}

/* ── Link Style tab: role and icon pickers ─────────────────────────────────

   Radios drawn as tiles. Radios rather than a <select> because the point is
   seeing the thing before choosing it, and because a select on a phone opens
   a modal over the live preview -- which is the one surface that answers
   "what will this look like".

   The input itself is hidden from sight but NOT from the accessibility tree
   or the keyboard: opacity+position, never display:none, so arrow keys still
   move through the group and :focus-visible can draw a ring. */
.style-fields { display: flex; flex-direction: column; gap: 18px; }
.style-group { display: flex; flex-direction: column; gap: 8px; }
.style-label {
  font-size: 12px; font-weight: 600; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--ink-soft, #6b6b83);
}
.style-tiles { display: flex; flex-wrap: wrap; gap: 8px; }
.style-tile {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 10px 12px;
  border: 1px solid var(--line, rgba(27,27,47,0.12));
  border-radius: 10px;
  cursor: pointer;
  background: #fff;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.style-tile input {
  position: absolute; opacity: 0;
  width: 100%; height: 100%; top: 0; left: 0; margin: 0;
  cursor: pointer;
}
.style-tile:hover { border-color: var(--violet); }
.style-tile:has(input:checked) {
  border-color: var(--violet);
  box-shadow: 0 0 0 2px rgba(91,75,212,0.18);
}
.style-tile:has(input:focus-visible) { outline: 2px solid var(--violet); outline-offset: 2px; }
.style-tile:has(input:disabled) { opacity: 0.45; cursor: not-allowed; }
.style-tile:has(input:disabled) input { cursor: not-allowed; }
.style-tile-name { font-size: 12px; font-weight: 500; }
.style-tile-hint { font-size: 11px; color: var(--ink-soft, #6b6b83); }

/* The role tiles preview themselves: each swatch is drawn the way that role
   draws a button, so the choice is visible rather than described. */
.style-tiles-roles .style-tile { flex: 1 1 150px; }
.style-role-swatch {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 30px; border-radius: 8px;
  font-size: 12px; font-weight: 500;
  border: 1px solid rgba(27,27,47,0.12);
  background: rgba(27,27,47,0.04);
}
.style-role--accent .style-role-swatch {
  background: var(--mint); color: var(--deep-night);
  border-color: transparent; font-weight: 600;
}
.style-role--note .style-role-swatch {
  background: transparent; border-style: dashed;
  border-color: var(--lilac); color: var(--lilac);
  font-size: 11px;
}

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

/* Two rows, scrolling sideways. grid-auto-flow: column fills DOWN then
   across, so the visual order is column-major -- which is what makes a
   horizontal scroll read correctly. A flex-wrap row would fill across and
   then down, and scrolling it sideways would jump alphabetically by twos. */
.style-tiles-icons {
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, auto);
  grid-auto-columns: 62px;
  gap: 6px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-behavior: smooth;
  scrollbar-width: none;
  padding-bottom: 2px;
}
.style-tiles-icons::-webkit-scrollbar { display: none; }
.style-tiles-icons:focus-visible {
  outline: 2px solid var(--violet); outline-offset: 3px; border-radius: 10px;
}
.style-tiles-icons .style-tile {
  width: auto; padding: 8px 4px;
  scroll-snap-align: start;
}
@media (prefers-reduced-motion: reduce) {
  .style-tiles-icons { scroll-behavior: auto; }
}
.style-icon-glyph { width: 20px; height: 20px; }
.style-icon-none {
  display: flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; font-size: 10px; color: var(--ink-soft, #6b6b83);
}
.style-tiles-icons .style-tile-name { font-size: 10px; }

/* ── Pages list: the Show/Hide switch ──────────────────────────────────────
   Far right of the card's actions, after the icons, because it is a
   different kind of control: the icons navigate, this one changes state. */
.page-card-toggle {
  display: flex; align-items: center; gap: 6px;
  margin-left: 8px; padding-left: 10px;
  border-left: 1px solid rgba(27,27,47,0.08);
  white-space: nowrap;
  cursor: pointer;
}
.page-card-toggle-label {
  font-size: 12px; font-weight: 500;
  color: rgba(27,27,47,0.55);
}
.page-card-toggle:hover .page-card-toggle-label { color: var(--violet); }
/* Locked: publishing this one needs Pro. Still readable -- a control you
   cannot use should look unavailable, not invisible. */
.page-card-toggle:has(input:disabled) { cursor: not-allowed; opacity: 0.5; }
.page-card-toggle:has(input:disabled):hover .page-card-toggle-label {
  color: rgba(27,27,47,0.55);
}
@media (max-width: 600px) {
  /* The label goes, the box stays. The title attribute still carries the
     hint, and at this width the row needs the room more than the word. */
  .page-card-toggle-label { display: none; }
  .page-card-toggle { margin-left: 4px; padding-left: 8px; }
}

/* Inline field hint. Sits under the input it is about, next to the cross,
   because a message that names the fix is only useful while the field is
   still in front of you. */
.field-hint {
  display: block;
  margin-top: 4px;
  font-size: 12px; line-height: 1.4;
  color: var(--ink-soft, #6b6b83);
}

/* ---- The SEO tab's search preview ----
   Added 18 Sep 2026 with FR-3.7.6. Deliberately RESEMBLES a search result
   rather than a form field: the owner is being shown an outcome, not asked
   for input, and the two want to look different or the preview reads as a
   third box to fill in.

   Not a pixel copy of anyone's results page -- close enough to be legible as
   "this is what a search listing looks like", and no further. */
.seo-preview {
  margin: 0 0 20px;
  padding: 14px 16px;
  background: var(--ghost);
  border: 1px solid rgba(27,27,47,0.08);
  border-radius: 10px;
}
.seo-preview-label {
  display: block;
  margin: 0 0 8px;
  font-family: var(--font-body);
  font-size: 11px; font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft, #6b6b83);
}
/* The title wraps rather than truncating. A preview that cut the text off
   would be showing its own limit rather than the search engine's, and the
   hint under the field already says where that falls. */
.seo-preview-title {
  margin: 0 0 4px;
  font-family: var(--font-head);
  font-size: 17px; line-height: 1.3; font-weight: 600;
  color: var(--violet);
  overflow-wrap: anywhere;
}
.seo-preview-desc {
  margin: 0;
  font-family: var(--font-body);
  font-size: 13px; line-height: 1.5;
  color: var(--ink-soft, #6b6b83);
  overflow-wrap: anywhere;
}

/* ---- The plan: Settings section, and the two screens behind it ----
   Added 2 Sep 2026. The Settings section shipped referencing .plan-row,
   .plan-badge, .plan-detail, .plan-upgrade and .settings-note, none of which
   were ever declared -- so the section rendered as unstyled runs of text.
   These are those rules, plus what the two plan screens need. */

.plan-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 10px;
}
.plan-badge {
  display: inline-block; padding: 3px 11px; border-radius: 999px;
  font-family: var(--font-body); font-size: 12px; font-weight: 600;
  letter-spacing: .02em;
  background: rgba(27,27,47,0.06); color: rgba(27,27,47,0.6);
}
/* Pro is the only badge that earns colour. A Free badge in violet would be
   selling; a Free badge in grey is a fact. */
.plan-badge--pro { background: rgba(91,75,212,0.12); color: var(--violet); }
/* ENDS. The same amber as .plan-change-pill--ending and .flash-warn, because
   it is the same fact one level down: this is running out on a date, nothing
   has gone wrong, and nothing is lost yet. A customer should not have to
   learn a second vocabulary for a block than for the subscription. */
.plan-badge--ending {
  background: rgba(255,176,32,0.14); color: #7a5f16;
}
/* The date cell that goes with it. Not bold -- the pill already carries the
   emphasis, and two shouts beside each other read as an error state. */
.plan-line-when--ending { color: #7a5f16; }
/* Under the total, about the total: what it will be next month. */
.plan-lines-note {
  margin: 10px 2px 0; font-size: 13px; line-height: 1.55;
  color: rgba(27,27,47,0.68);
}
.plan-detail { font-size: 13px; color: rgba(27,27,47,0.6); }

.settings-note {
  margin: 8px 0 0; font-size: 13px; line-height: 1.55;
  color: rgba(27,27,47,0.68);
}
.settings-note a { color: var(--violet); }
.settings-note-warn {
  display: block; margin-top: 4px;
  font-size: 12px; line-height: 1.45; color: #8a6d1f;
}

.plan-upgrade {
  margin-top: 14px; padding: 14px 16px; border-radius: 10px;
  background: rgba(91,75,212,0.05);
  border: 1px solid rgba(91,75,212,0.12);
}
.plan-upgrade p { margin: 0 0 10px; font-size: 13px; line-height: 1.5; }

.plan-links { margin: 14px 0 0; font-size: 13px; }
.plan-links a { color: var(--violet); text-decoration: none; font-weight: 500; }
.plan-links a:hover { text-decoration: underline; }
.plan-links-sep { color: rgba(27,27,47,0.25); margin: 0 8px; }

.plan-page h2 { margin-bottom: 18px; }
.plan-page .settings-section + .settings-section { margin-top: 18px; }

/* The history table. Three columns, and the amount right-aligned and
   tabular so a column of money lines up on the decimal point -- the whole
   reason someone opens this screen is to compare it with a bank statement. */
.plan-history { width: 100%; border-collapse: collapse; font-size: 13px; }
.plan-history th {
  text-align: left; font-weight: 600; font-size: 12px;
  letter-spacing: .04em; text-transform: uppercase;
  color: rgba(27,27,47,0.42);
  padding: 0 0 8px; border-bottom: 1px solid rgba(27,27,47,0.1);
}
.plan-history td {
  padding: 10px 0; border-bottom: 1px solid rgba(27,27,47,0.06);
  vertical-align: top; color: var(--deep-night);
}
.plan-history tr:last-child td { border-bottom: none; }
.plan-history-when { white-space: nowrap; color: rgba(27,27,47,0.6); }
.plan-history-num {
  text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
th.plan-history-num { text-align: right; }
/* A word, not a minus sign: '-£5.00' among positives reads as a typo. */
.plan-history-refund {
  display: inline-block; margin-left: 8px;
  padding: 1px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600;
  background: rgba(27,27,47,0.06); color: rgba(27,27,47,0.55);
}

@media (max-width: 560px) {
  .plan-history th:first-child, .plan-history td:first-child { padding-right: 10px; }
  .plan-history-when { font-size: 12px; }
}

/* ---- The subscription list ----
   A GRID, not a table. The header, every line and the total share one
   `grid-template-columns` declared on .plan-lines, so the columns cannot
   drift apart the way they do when each row sets its own widths. Added
   2 Sep 2026 with the Subscription screen. */
.plan-lines {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 84px 84px 78px 104px;
  align-items: center;
  column-gap: 10px;
}
.plan-line { display: contents; }
.plan-line > * { padding: 15px 0; }

.plan-line--head > * {
  padding: 0 0 10px;
  border-bottom: 1px solid rgba(27,27,47,0.08);
  font-size: 10px; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: rgba(27,27,47,0.4);
}
/* The first cell of the header is the card's title, not a column label. */
.plan-line--head .plan-line-name {
  font-family: var(--font-head);
  font-size: 15px; font-weight: 600;
  letter-spacing: normal; text-transform: none;
  color: var(--deep-night);
}
.plan-line + .plan-line > * { border-top: 1px solid rgba(27,27,47,0.06); }
.plan-line--head + .plan-line > * { border-top: none; }

.plan-line-name { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.plan-line-title {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 14px; font-weight: 600; color: var(--deep-night);
}
.plan-line-detail { font-size: 12px; line-height: 1.5; color: rgba(27,27,47,0.55); }
/* The separator belongs to the note, so it must not outlive it. UnitNote is
   blank on a single-unit line -- where '1 x PS5.75' would only repeat the
   amount beside it -- and the template still draws the span, so the ::before
   painted a middot with nothing after it: "Includes 3 sub pages ·". Guarding
   the content rather than the span keeps the decision where the punctuation
   is. */
.plan-line-unit:not(:empty)::before { content: " · "; }

.plan-qty {
  font-size: 12px; font-weight: 600; color: rgba(27,27,47,0.5);
  padding: 2px 7px; border-radius: 6px; background: rgba(27,27,47,0.05);
}
.plan-line-when {
  font-size: 13px; color: rgba(27,27,47,0.6);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.plan-line-when--next { color: var(--deep-night); font-weight: 500; }
.plan-line-amount {
  font-size: 14px; font-weight: 600; color: var(--deep-night);
  text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums;
}
.plan-line-nil { color: rgba(27,27,47,0.3); font-weight: 400; }
.plan-line-act { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }

.plan-line--total > * {
  border-top: 1px solid rgba(27,27,47,0.08);
  padding-top: 14px;
}
.plan-line--total .plan-line-name {
  font-size: 13px; font-weight: 400; color: rgba(27,27,47,0.5);
}
.plan-line--total .plan-line-amount {
  font-family: var(--font-head); font-size: 17px; font-weight: 700;
}

/* The quantity stepper. One control for "one more block" and "one fewer",
   because they are the same decision in two directions. */
.plan-step { display: inline-flex; align-items: center; }
.plan-step-btn {
  width: 30px; height: 30px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: white; cursor: pointer; color: var(--deep-night);
  border: 1px solid rgba(27,27,47,0.16);
  border-radius: 8px 0 0 8px;
  transition: background .15s, border-color .15s;
}
.plan-step-btn--add { border-radius: 0 8px 8px 0; color: var(--violet); }
.plan-step-btn:hover { background: rgba(91,75,212,0.06); border-color: var(--violet); }
.plan-step-btn:focus-visible { outline: 2px solid var(--violet); outline-offset: 1px; }
.plan-step-n {
  min-width: 34px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  border-top: 1px solid rgba(27,27,47,0.16);
  border-bottom: 1px solid rgba(27,27,47,0.16);
  font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums;
}

/* Grey, never red, and never the page's primary action. Nothing has gone
   wrong when somebody cancels. */
.plan-cancel {
  font-size: 13px; color: rgba(27,27,47,0.45);
  text-decoration: underline; text-underline-offset: 2px;
}
.plan-cancel:hover { color: var(--deep-night); }

/* ── THE CHANGE-PLAN PANEL ───────────────────────────────────────────────
   Spec 5.3c, drawn in Docs/design/change-plan-mockup.html.

   The opener is violet where .plan-cancel was grey: Change is a decision
   offered, not a loss being confirmed, and the word replaced one that named
   only the way out. Still a link and still not a button -- on Pro this page
   has no primary action, and a loud control here would invent one. */
/* THE ONLY ROUTE TO A PLAN CHANGE, and it was a 13px underlined text link in
   the last cell of a five-column row, next to a detail line, two dates and a
   price. The owner opened the Subscription screen on a fresh Pro account and
   reported there was 'no option to downgrade' -- and the request log agrees
   with him: not one GET carried ?change=1, so the panel was never opened. The
   link rendered; nobody could see it.

   A bordered control in the same cell, same word. 5.3c.1's choice of 'Change'
   over 'Cancel' stands -- that decision was about what the word promises, not
   about how quiet it should be. */
.plan-change-open {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 7px 14px; border: 1.5px solid rgba(91,75,212,0.5);
  border-radius: 9px; font-size: 13px; font-weight: 600;
  color: var(--violet); text-decoration: none; white-space: nowrap;
}
.plan-change-open:hover {
  border-color: var(--violet); background: rgba(91,75,212,0.06);
}

/* ---- Sub-page add-ons: chooser and priced confirmation ------------------
   Named addon-*, and grepped first: nothing else in this file owns that
   prefix. The lesson from .plan-cancel one screen over. */
.addon-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px; margin: 18px 0;
}
.addon-card {
  display: flex; flex-direction: column; gap: 6px; align-items: flex-start;
  padding: 18px 20px; border-radius: 12px;
  background: #fff; border: 1px solid rgba(27,27,47,0.1);
}
.addon-card-name { font-size: 14px; font-weight: 600; color: var(--deep-night); }
.addon-card-price {
  font-family: var(--font-head); font-size: 20px; font-weight: 700;
  color: var(--deep-night);
}
.addon-card-adds { font-size: 12.5px; color: rgba(27,27,47,0.55); }
.addon-card-held {
  font-size: 11px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--violet);
}
.addon-card .btn-primary { margin-top: 8px; width: auto; display: inline-flex; padding: 9px 18px; }

/* The second section: what the subscription becomes, read like an invoice. */
.addon-summary h3 {
  margin: 0 0 14px; font-family: var(--font-head);
  font-size: 15px; font-weight: 600; color: var(--deep-night);
}
.addon-summary-lines { display: flex; flex-direction: column; }
.addon-summary-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; padding: 10px 0; font-size: 14px; color: var(--deep-night);
  border-bottom: 1px solid rgba(27,27,47,0.06);
}
.addon-summary-amount { font-variant-numeric: tabular-nums; font-weight: 600; }
.addon-summary-row--total {
  border-bottom: none; padding-top: 14px; font-weight: 600;
}
.addon-summary-row--total .addon-summary-amount {
  font-family: var(--font-head); font-size: 17px; font-weight: 700;
}
.addon-lead { margin: 0 0 16px; font-size: 15px; color: rgba(27,27,47,0.7); }
/* THE MONEY, and the amount taken today is the biggest thing on the card. */
.addon-money {
  border: 1px solid rgba(91,75,212,0.22); border-radius: 10px;
  overflow: hidden; margin-bottom: 20px;
}
.addon-money-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 14px 18px;
}
.addon-money-row--today { background: rgba(91,75,212,0.05); }
/* THE DOWN PATH'S BOX, in the slot .addon-money occupies going up. Same
   shape so the page does not reflow between the two directions, and the same
   quiet violet rather than a warning colour: removing a block is a decision,
   not a problem, and amber here would argue with a customer who has already
   made up their mind. */
.addon-keeps {
  border: 1px solid rgba(91,75,212,0.22); border-radius: 10px;
  background: rgba(91,75,212,0.05);
  padding: 14px 18px; margin-bottom: 20px;
  font-size: 14px; line-height: 1.55;
}
.addon-money-row + .addon-money-row { border-top: 1px solid rgba(91,75,212,0.16); }
.addon-money-what { display: flex; flex-direction: column; gap: 3px; font-size: 14px; }
.addon-money-note { font-size: 12px; color: rgba(27,27,47,0.55); }
.addon-money-figure {
  font-family: var(--font-head); font-size: 26px; font-weight: 700;
  color: var(--deep-night); font-variant-numeric: tabular-nums;
}
.addon-money-figure--small { font-size: 17px; }
/* The refusal names the number to act on, not the rule. Amber: nothing has
   gone wrong, and the pages are still there. */
.addon-refusal {
  padding: 14px 16px; margin-bottom: 20px; border-radius: 10px;
  background: rgba(255,176,32,0.08); border: 1px solid rgba(255,176,32,0.3);
  font-size: 14px; line-height: 1.5; color: var(--deep-night);
}
.addon-actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.addon-go { display: contents; }
.addon-actions .btn-primary { width: auto; display: inline-flex; padding: 12px 22px; }
.btn-quiet-link { font-size: 14px; color: rgba(27,27,47,0.55); }
.btn-quiet-link:hover { color: var(--deep-night); }
.addon-foot { margin-top: 14px; }

/* ---- Cancel Pro: the stop-and-challenge screen (5.3c.6a) ----------------
   NAMED cancel-screen*, NOT plan-cancel*. `.plan-cancel` is already taken --
   it is the change panel's quiet close link, 30 lines above, and it carries
   text-decoration: underline. A container reusing that name inherited the
   underline and PAINTED IT THROUGH EVERY DESCENDANT, button text included:
   text-decoration propagates to children even though their own computed
   text-decoration-line stays 'none', which is why getComputedStyle on the
   paragraph said nothing was wrong.

   The reference doc already carried this lesson from `.ema-swatch`: grep the
   stylesheet for a class name before inventing it. Written 4 Sep 2026 by
   somebody who did not. */
.cancel-screen { display: flex; flex-direction: column; gap: 18px; }
.cancel-screen-lead {
  margin: 0; font-size: 15px; line-height: 1.55; color: var(--deep-night);
}
/* Amber, never red. Nothing has gone wrong and nothing is lost yet -- red
   here would be the screen telling them they have made a mistake. */
.cancel-screen-stops {
  padding: 16px 18px; border-radius: 10px;
  background: rgba(255,176,32,0.08); border: 1px solid rgba(255,176,32,0.3);
}
.cancel-screen-head {
  display: block; margin-bottom: 10px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: #8A6D1F;
}
.cancel-screen-stops ul, .cancel-screen-keeps {
  margin: 0; padding-left: 18px; font-size: 14px; line-height: 1.8;
  color: var(--deep-night);
}
.cancel-screen-keeps { list-style: none; padding-left: 0; }
.cancel-screen-keeps li::before {
  content: "\2713\00a0\00a0"; color: #00875F; font-weight: 700;
}
/* Keep Pro is the only highlighted action; Cancel Pro is a real button so it
   is unmistakably the other choice, but it does not compete for the eye. */
.cancel-screen-actions {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.cancel-screen-go { display: contents; }
/* .btn-primary is display:block;width:100% -- written for a form's only
   button, where filling the width is right. Here it sits BESIDE the quiet
   one, and a full-width Keep Pro pushed Cancel Pro onto its own line, which
   turns a deliberate pair of choices into a stack with a winner. */
.cancel-screen-actions .btn-primary { display: inline-flex; width: auto; padding: 12px 22px; }
.btn-quiet {
  appearance: none; -webkit-appearance: none;
  padding: 12px 22px; border-radius: 12px; cursor: pointer;
  background: transparent; border: 1.5px solid rgba(27,27,47,0.18);
  color: rgba(27,27,47,0.6);
  font-family: inherit; font-size: 15px; font-weight: 600; line-height: 1.2;
}
.btn-quiet:hover { border-color: rgba(27,27,47,0.35); color: var(--deep-night); }
.cancel-screen-foot { margin-top: 4px; }

/* Slides open IN PLACE. The person stays on the screen that shows what they
   already have, which is the context that makes the choice mean anything. */
.plan-change {
  margin-top: 4px; padding: 20px; border-radius: 12px;
  background: var(--ghost); border: 1px solid rgba(91,75,212,0.18);
}
.plan-change-tiers {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
}
.plan-change-tier {
  background: #ffffff; border: 1px solid rgba(27,27,47,0.1);
  border-radius: 10px; padding: 16px 18px;
}
/* Pro's card is the visual upsell, in both directions -- it is the one
   carrying the benefits the decision is actually about. */
.plan-change-tier[data-tier="pro"] {
  border: 1.5px solid var(--violet);
  box-shadow: 0 2px 10px rgba(91,75,212,0.12);
}
.plan-change-tier-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 4px;
}
.plan-change-tier-name {
  font-family: var(--font-head); font-size: 16px; font-weight: 700;
}
.plan-change-pill {
  font-size: 10px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--violet);
  border: 1px solid rgba(91,75,212,0.4); border-radius: 20px;
  padding: 2px 9px; white-space: nowrap;
}
/* CANCELLED. Amber, not red -- nothing has gone wrong and nothing is lost
   yet; it is the state they asked for. Violet here would be the badge
   agreeing that nothing has changed. */
.plan-change-pill--ending {
  color: #7a5f16; border-color: rgba(255,176,32,0.7);
  background: rgba(255,176,32,0.14);
}
.plan-change-price {
  font-family: var(--font-head); font-size: 22px; font-weight: 700;
  margin-bottom: 12px;
}
.plan-change-per {
  font-size: 13px; font-weight: 500; color: rgba(27,27,47,0.5);
}
.plan-change-features {
  margin: 0; padding-left: 16px; font-size: 12.5px; line-height: 1.75;
  color: rgba(27,27,47,0.7);
}
.plan-change-foot {
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid rgba(27,27,47,0.08);
}
.plan-change-runs {
  margin: 0 0 10px; font-size: 13px; line-height: 1.6;
  color: rgba(27,27,47,0.75);
}

/* AMBER, NEVER RED. Nothing has gone wrong, nothing is deleted, and every
   page named here can be switched back on. Red would be a claim about
   damage that this change does not do. */
.plan-change-warn {
  padding: 14px 16px; margin-bottom: 12px; border-radius: 10px;
  background: rgba(255,176,32,0.12); border: 1px solid rgba(255,176,32,0.4);
}
.plan-change-warn-head {
  margin: 0 0 6px; font-size: 13.5px; font-weight: 600;
  color: var(--deep-night);
}
.plan-change-warn-body {
  margin: 0; font-size: 12.5px; line-height: 1.6; color: rgba(27,27,47,0.7);
}
/* The tax line, under the two tier cards and above everything about the
   change itself. Quietest thing in the panel: it is a standing fact, not part
   of the decision. */
.plan-change-tax {
  margin: 0 0 14px; font-size: 11.5px; line-height: 1.55;
  color: rgba(27,27,47,0.5);
}
.plan-change-after {
  margin: 0 0 6px; font-size: 12.5px; font-weight: 600;
  color: rgba(27,27,47,0.75);
}
.plan-change-costs {
  margin: 0 0 12px; padding-left: 16px; font-size: 12.5px; line-height: 1.7;
  color: rgba(27,27,47,0.65);
}
/* A bounded fear is the difference between an informed decision and a
   panicked one, so what is KEPT gets the mint and its own box. */
.plan-change-kept {
  margin: 0 0 14px; padding: 10px 12px; border-radius: 8px;
  font-size: 12.5px; line-height: 1.6; color: rgba(27,27,47,0.7);
  background: rgba(0,229,160,0.1);
}
/* The rate they are about to spend (5.3c.7). Amber, matching the
   switched-off block above it: a bounded fact, never a red banner. It had no
   rule at all until 3 Sep 2026 and rendered as unstyled body text. */
.plan-change-rate {
  margin: 0 0 12px; padding: 10px 12px; border-radius: 8px;
  font-size: 12.5px; line-height: 1.6; color: #7a5f16;
  background: rgba(255,176,32,0.12);
}
/* 5.3c.6, and the last line before the press. Carries slightly more weight
   than anything above it because it is the only irreversible fact on the
   panel -- but still no red: nothing has gone wrong. */
.plan-change-final {
  margin: 0 0 14px; padding-left: 10px;
  border-left: 3px solid rgba(255,176,32,0.7);
  font-size: 12.5px; line-height: 1.6; font-weight: 600;
  color: var(--deep-night);
}
.plan-change-actions {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
}
/* The way down stays quiet. Not red, not a button, and not the page's
   primary action -- but still unmistakably a control. */
/* THIS IS A <button> NOW, NOT AN <a>. It was styled as a link and quietly
   became a form control when the cancel route landed -- and a button inherits
   none of an anchor's defaults, so it drew as a grey UA-chrome box in the
   system font with underlined violet text inside it. Everything above the
   colour is undoing that. */
.plan-change-down {
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; padding: 0; margin: 0;
  font-family: inherit; line-height: inherit; cursor: pointer;
  font-size: 13px; color: var(--violet);
  text-decoration: underline; text-underline-offset: 2px;
}
/* The forms wrapping the two actions are layout-invisible: .plan-change-actions
   is a flex row, and a block-level <form> around one button breaks it onto a
   line of its own. */
.plan-change-buy, .plan-change-downform { display: contents; }
/* THE CURRENCY PICKER (4.2b), beside the button it pays with. Sized down to
   sit under the primary action rather than compete with it: this is a
   qualifier on a decision already made, not a second decision. It inherits
   the flex row because .plan-change-buy is display:contents. */
.plan-change-currency {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 12px; font-size: 12.5px; color: rgba(27,27,47,0.65);
}
.plan-change-currency select {
  appearance: none; -webkit-appearance: none;
  font-family: inherit; font-size: 12.5px; line-height: 1.4;
  color: var(--deep-night); cursor: pointer;
  padding: 7px 26px 7px 10px; border-radius: 8px;
  border: 1px solid rgba(91,75,212,0.25); background-color: var(--ghost);
  /* The chevron is a data: URI rather than a file: one request fewer, and
     the colour is fixed here with the rest of the control. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='%235B4BD4' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 9px center;
  background-size: 10px;
}
.plan-change-currency select:focus-visible {
  outline: 2px solid var(--violet); outline-offset: 1px;
}
/* LOCKED. Quiet, and where the control would have been -- it is a standing
   fact about the subscription, not a warning about anything. */
.plan-change-currency-fixed {
  margin: 0 0 12px; font-size: 12.5px; line-height: 1.6;
  color: rgba(27,27,47,0.6);
}
/* Already cancelled: a statement standing where the control was. */
.plan-change-ending {
  margin: 0; font-size: 13px; line-height: 1.5; font-weight: 600;
  color: var(--deep-night);
}
.plan-change-down:hover { color: var(--violet-dark); }
.plan-change-cancel {
  font-size: 13px; color: rgba(27,27,47,0.5);
  text-decoration: none;
}
.plan-change-cancel:hover { color: var(--deep-night); }

@media (max-width: 640px) {
  .plan-change { padding: 16px; }
  .plan-change-tiers { grid-template-columns: 1fr; }
}

.plan-meter-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; margin-bottom: 14px;
}
.plan-meter-head h3 {
  margin: 0; padding: 0; border: none;
  font-family: var(--font-head); font-size: 15px; font-weight: 600;
}
.plan-meter-count { font-size: 13px; color: rgba(27,27,47,0.5); }
.plan-meter-count strong { color: var(--deep-night); font-weight: 600; }
.plan-meter {
  height: 8px; border-radius: 4px; overflow: hidden;
  background: rgba(91,75,212,0.16); margin-bottom: 14px;
}
.plan-meter > span { display: block; height: 100%; border-radius: 4px; background: var(--violet); }
.plan-meter-foot { display: flex; align-items: center; justify-content: flex-end; }

/* Below the grid's natural width the five columns stop fitting. Two rows per
   line: the name and its detail, then the dates, price and control. */
@media (max-width: 620px) {
  .plan-lines { display: block; }
  .plan-line { display: block; padding: 14px 0; }
  .plan-line > * { padding: 0; border: none; }
  .plan-line + .plan-line { border-top: 1px solid rgba(27,27,47,0.06); }
  .plan-line--head { display: none; }
  .plan-line-when { display: inline-block; margin: 6px 10px 0 0; font-size: 12px; }
  .plan-line-when--next::before { content: "renews "; color: rgba(27,27,47,0.4); }
  /* Must track plan.NextLabel: the desktop heading is the only thing that
     labels this column, and it is display:none here. */
  .plan-line-when--ending::before { content: "ends "; color: rgba(27,27,47,0.4); }
  .plan-line-amount { display: inline-block; text-align: left; margin-top: 6px; }
  .plan-line-act { justify-content: flex-start; margin-top: 10px; }
  .plan-line--total { display: flex; justify-content: space-between; align-items: baseline; }
}

/* ---- Page editor tabs ------------------------------------------------
   Added 5 Sep 2026 with the access code.

   BOTH PANELS ARE IN THE DOM AT ALL TIMES and this only hides one. That is
   not a styling choice: the editor is a single form posting everything to
   /save at once, so a panel that was fetched on demand would have no inputs
   when somebody saved from the other tab, and the save would write empty
   over the access code. Hiding is the whole mechanism -- do not "improve"
   this into a lazy load. */
.editor-tabs {
  display: flex;
  gap: 4px;
  margin: 24px 0 20px;
  border-bottom: 1px solid var(--line, #e6e4f0);
}

.editor-tab {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 10px 16px;
  font: inherit;
  font-weight: 600;
  font-size: 14px;
  color: var(--ink-soft, #6b6b83);
  cursor: pointer;
}

.editor-tab:hover { color: var(--ink, #1B1B2F); }

.editor-tab.active {
  color: var(--violet, #5B4BD4);
  border-bottom-color: var(--violet, #5B4BD4);
}

.editor-tab:focus-visible {
  outline: 2px solid var(--violet, #5B4BD4);
  outline-offset: 2px;
}

/* [hidden] needs saying out loud: .editor-tab-panel has no display of its
   own, but a future one would beat the attribute's default and show both
   panels at once. */
.editor-tab-panel[hidden] { display: none; }

/* A hint that is a warning rather than an explanation. Amber, never red:
   nothing here has gone wrong, the reader is being told a consequence. */
.field-hint--warn {
  /* NOT var(--amber): #FFB020 is a fill colour and fails contrast as 12px
     text on white. This is the same hue taken dark enough to read, which is
     the accessibility rule product-and-brand.md sets for text on a light
     ground. */
  color: #8A6100;
}

/* ---- The access-code gate (5 Sep 2026) -------------------------------
   Lives inside .em-page, so it inherits the page's own theme variables --
   the gate is the first thing a visitor sees of this person and an unstyled
   prompt in front of a themed page reads as an interception. */
.em-gate-message {
  margin-bottom: 20px;
}

.em-gate-form { width: 100%; }

.em-gate-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  max-width: 320px;
  margin: 0 auto;
  text-align: left;
}

.em-gate-label {
  font-size: 13px;
  font-weight: 600;
  opacity: .85;
}

.em-gate-input {
  width: 100%;
  padding: 12px 14px;
  font: inherit;
  font-size: 16px;          /* 16px stops iOS zooming the page on focus */
  letter-spacing: .04em;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, .25);
  background: rgba(255, 255, 255, .08);
  color: inherit;
}

.em-gate-input:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

.em-gate-btn {
  padding: 12px 16px;
  font: inherit;
  font-weight: 600;
  border: 0;
  border-radius: 10px;
  cursor: pointer;
  background: var(--em-accent, #5B4BD4);
  color: #fff;
}

.em-gate-help {
  margin: 2px 0 0;
  font-size: 12px;
  line-height: 1.45;
  opacity: .7;
}

/* Amber, never red. Nothing has gone wrong and nobody is in trouble -- a
   mistyped code is the most ordinary thing that can happen here, and the
   tone rules forbid alarm in a message like this. */
.em-gate-error {
  margin: 0;
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 13px;
  background: rgba(255, 176, 32, .15);
  border: 1px solid rgba(255, 176, 32, .45);
}

/* A unit label beside a short input. The suffix is markup rather than a CSS
   ::after so it is real text -- a screen reader reads "days" as part of the
   field, and a pseudo-element would not be there to read. */
.field-with-suffix {
  display: flex;
  align-items: center;
  gap: 8px;
}

.field-suffix {
  font-size: 14px;
  color: var(--ink-soft, #6b6b83);
}

/* A block that explains a state the person did not choose and can act on.
   Amber, never red: nothing has gone wrong and nobody is in trouble -- a
   subscription lapsed and a page went quiet, which is a consequence, not a
   fault. The tone rules forbid alarm in a message about somebody's account. */
.notice {
  margin-top: 16px;
  padding: 14px 16px;
  border-radius: 10px;
  border: 1px solid rgba(255, 176, 32, .45);
  background: rgba(255, 176, 32, .10);
}

.notice strong { display: block; margin-bottom: 6px; }

.notice p {
  margin: 0 0 10px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-soft, #6b6b83);
}

.notice .btn-secondary { margin-top: 2px; }


/* ─── AMBASSADOR EARNINGS ────────────────────────────────────────────────
   /dashboard/ambassador. Three cards and two lists, and the whole screen is
   about one distinction: money that is theirs, and money that is theirs and
   not yet theirs to have.

   THE HOLD IS NOT DRAWN AS A WARNING. It is amber-free on purpose -- ninety
   days is the arrangement, not a problem, and a page that puts a caution
   colour on somebody's own earnings reads as something having gone wrong. */
.dash-amb { max-width: var(--dash-max); margin: 0 auto; }

.amb-head {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 20px;
}
.amb-head-note { font-size: 14px; color: rgba(27,27,47,0.55); }

.amb-status {
  display: inline-block; padding: 4px 12px; border-radius: 999px;
  font-size: 12px; font-weight: 600; letter-spacing: .02em;
}
.amb-status--on   { background: rgba(0,229,160,.16); color: #0a7a57; }
.amb-status--hold { background: rgba(255,176,32,.16); color: #8a5b00; }
.amb-status--off  { background: rgba(27,27,47,.08); color: rgba(27,27,47,.6); }

/* auto-fit rather than three fixed columns: at phone width the cards stack
   without a second rule, and a stacked card keeps its full-width number. */
.amb-cards {
  display: grid; gap: var(--analytics-gap);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  margin-bottom: var(--analytics-gap);
}
.amb-card {
  background: #fff; border: 1px solid rgba(27,27,47,0.08);
  border-radius: 12px; padding: 20px;
}
.amb-card--good { border-color: rgba(0,229,160,.35); }
.amb-card-value {
  display: block; font-family: var(--font-head); font-weight: 700;
  font-size: 32px; line-height: 1.2; color: var(--violet);
}
.amb-card--good .amb-card-value { color: #0a7a57; }
.amb-card-label {
  display: block; font-size: 14px; font-weight: 600; margin-top: 4px;
}
.amb-card-note {
  display: block; font-size: 12px; line-height: 1.5; margin-top: 8px;
  color: rgba(27,27,47,0.5);
}

.amb-section {
  background: #fff; border: 1px solid rgba(27,27,47,0.08);
  border-radius: 12px; padding: 20px;
  margin-bottom: var(--analytics-gap);
}
/* .preview-open centres itself with auto side margins, so the link section
   needs nothing but room -- and the heading stays left where every other
   section title on the dashboard is. */
.amb-section--link .preview-open { margin-top: 10px; }
.amb-section--link .amb-empty { text-align: center; }

/* The consent toggle uses .em-check and the settings result line, both of
   which already exist -- there is deliberately no bespoke control here. What
   the section does need is room between the box, its hint and the line that
   confirms the save. */
.amb-section .em-check { margin-top: 4px; }
.amb-section .field-hint { margin: 10px 0 0; }
.amb-section .settings-result:not(:empty) { margin-top: 10px; }
.amb-empty { font-size: 14px; color: rgba(27,27,47,0.55); margin: 8px 0 0; }

.amb-list { list-style: none; margin: 8px 0 0; padding: 0; }

/* A GRID, not a flex row: the pill and the amount hold their columns down the
   list, so the money lines up and the two sub-lines wrap underneath instead
   of pushing the amount off the right-hand edge on a phone. */
.amb-row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 12px;
  align-items: baseline;
  padding: 12px 0;
  border-top: 1px solid rgba(27,27,47,0.07);
}
.amb-row:first-child { border-top: 0; }
.amb-row-main {
  font-family: var(--font-head); font-weight: 700; font-size: 18px;
  text-align: right;
}
.amb-row-sub {
  grid-column: 1 / -1;
  font-size: 13px; line-height: 1.5; color: rgba(27,27,47,0.55);
}

.amb-pill {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  font-size: 11px; font-weight: 600; white-space: nowrap;
  background: rgba(27,27,47,.08); color: rgba(27,27,47,.6);
}
.amb-pill--ok     { background: rgba(0,229,160,.16);  color: #0a7a57; }
.amb-pill--hold   { background: rgba(91,75,212,.12);  color: var(--violet); }
.amb-pill--warn   { background: rgba(255,176,32,.16); color: #8a5b00; }
.amb-pill--bad    { background: rgba(255,107,107,.16); color: #a02020; }
.amb-pill--muted  { background: rgba(27,27,47,.08);   color: rgba(27,27,47,.6); }

/* The rubber-band at the end of a scroll is the other way that strip appears:
   overscrolling past the bottom of the document reveals the canvas, which is
   the body's near-white, not the page's own colour. Scoped with :has() to a
   page that actually contains a profile -- the marketing site and the
   dashboard keep the normal bounce, and pull-to-refresh with it. */
html:has(.em-page), body:has(> .em-page) { overscroll-behavior-y: none; }


/* ---- COOKIE CONSENT BANNER (17 Sep 2026) ----------------------------------
   Deep Night, so it reads as chrome rather than as part of whatever page it
   is sitting over -- it appears on marketing, auth and dashboard screens,
   which have three different backgrounds between them.

   THE TWO BUTTONS ARE DELIBERATELY THE SAME WEIGHT. Mint on Deep Night for
   Allow (10.22:1, and never white on mint -- see the brand accessibility
   rules), Ghost outline for No thanks. Same padding, same radius, same font
   size. Making the refusal quieter than the acceptance is the dark pattern
   PECR guidance names, and it is one CSS edit away at all times, so this
   comment is the guard.

   position: fixed with a translate-in: the banner is not modal and must not
   trap focus or block the page. Somebody who ignores it keeps browsing, and
   nothing behind it is disabled.
--------------------------------------------------------------------------- */
.em-consent {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 200;
  background: var(--deep-night);
  color: var(--ghost);
  border-top: 1px solid rgba(162, 155, 254, 0.35);
  box-shadow: 0 -8px 32px rgba(27, 27, 47, 0.28);
  transform: translateY(100%);
  transition: transform 0.28s ease;
}
.em-consent.is-open { transform: translateY(0); }
.em-consent[hidden] { display: none; }

.em-consent-inner {
  max-width: 1100px; margin: 0 auto;
  padding: 18px 24px;
  display: flex; align-items: center; gap: 24px;
  flex-wrap: wrap;
}
.em-consent-text { flex: 1 1 340px; }
.em-consent-text p { font-size: 14px; line-height: 1.55; color: rgba(248, 247, 255, 0.82); }
.em-consent-title {
  font-family: var(--font-head); font-weight: 700; font-size: 16px;
  color: var(--ghost); margin-bottom: 4px;
}
.em-consent-text a { color: var(--lilac); text-decoration: underline; }
.em-consent-text a:hover { color: var(--ghost); }

.em-consent-actions { display: flex; gap: 12px; flex: 0 0 auto; }
.em-consent-btn {
  font-family: var(--font-body); font-size: 14px; font-weight: 600;
  padding: 11px 22px; border-radius: 10px;
  border: 1px solid transparent; cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}
.em-consent-btn:focus-visible {
  outline: 2px solid var(--lilac); outline-offset: 2px;
}
.em-consent-yes { background: var(--mint); color: var(--deep-night); }
.em-consent-yes:hover { background: var(--mint-dark); }
.em-consent-no {
  background: transparent; color: var(--ghost);
  border-color: rgba(248, 247, 255, 0.45);
}
.em-consent-no:hover { border-color: var(--ghost); background: rgba(248, 247, 255, 0.08); }

@media (max-width: 640px) {
  .em-consent-inner { padding: 16px; gap: 14px; }
  .em-consent-actions { width: 100%; }
  .em-consent-btn { flex: 1 1 0; }
}

@media (prefers-reduced-motion: reduce) {
  .em-consent { transition: none; }
}

/* ── The Features page's demo chrome ───────────────────────────────────────
   Templates/pages/features.html shows real dashboard screens using the real
   dashboard classes -- page-card, quick-add, link-card, style-tile,
   theme-btn, chart-bar-group, em-gate-* -- so that the marketing page cannot
   drift from the product it is describing. What is NOT already in this file
   is the frame those screens sit in, and that is all this block is: panels,
   captions, and two grids.

   Nothing here restyles a dashboard class. If a rule below starts reaching
   into one, it has stopped being chrome and the page has started forking the
   product's appearance, which is the whole thing this arrangement avoids. */

/* A white band on any section, not only a .how-section. The only rule for
   this class was `.how-section.band--white`, so a section that carried
   band--white alone -- which is what the Features page's alternating bands
   do -- got the class and no background, and the page rendered as one
   continuous wash. Bare class, 0,1,0: the scoped rule still wins where it
   applies, and nothing that reads band--white is surprised by what it
   means. */
.band--white { background: white; }

.demo-flow {
  display: flex; align-items: flex-start; justify-content: center;
  flex-wrap: wrap; max-width: 900px; margin: 0 auto;
}
.demo-panel { width: 340px; max-width: 100%; position: relative; margin: 0 auto 8px; }
.demo-panel-inner {
  background: white;
  border: 1px solid rgba(27,27,47,0.08);
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(27,27,47,0.04);
}
.demo-panel-inner--pad { padding: 20px; }
.demo-panel-inner--centre { display: flex; justify-content: center; padding: 20px 0; }
.demo-panel-body { padding: 16px; }
.demo-topbar {
  padding: 14px 16px;
  border-bottom: 1px solid rgba(27,27,47,0.06);
  font-family: var(--font-head); font-weight: 700; font-size: 13px;
  color: var(--deep-night);
}
.demo-topbar strong { color: var(--violet); }
.demo-caption {
  text-align: center; font-size: 13px; color: rgba(27,27,47,0.55);
  margin: 14px 0 0; line-height: 1.5;
}
.demo-caption--left { text-align: left; margin-top: 2px; }
/* The "look here" ring on the QR icon in step 1. !important because
   .page-action:hover sets the same two properties and a visitor whose
   pointer crosses the card would otherwise cancel the callout. */
.demo-highlight {
  background: rgba(91,75,212,0.1) !important;
  color: var(--violet) !important;
  box-shadow: 0 0 0 3px rgba(91,75,212,0.18);
}
/* The page rows' initials circle. A local class rather than .em-avatar:
   that one is the PAGE's avatar, sized and centred for the top of a profile,
   and borrowing it here is how the preview frame came to shrink a profile
   block's face -- see .preview-phone-frame .em-page > .em-avatar. */
.demo-avatar {
  width: 36px; height: 36px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-head); font-weight: 700; font-size: 13px;
  flex-shrink: 0;
}
.demo-avatar--violet { background: var(--violet); color: white; }
.demo-avatar--ink    { background: var(--deep-night); color: white; }
.demo-avatar--grey   { background: rgba(27,27,47,0.08); color: var(--deep-night); }
.lock-glyph {
  width: 13px; height: 13px; margin-left: 6px;
  vertical-align: -1px; opacity: 0.45;
}

/* Editor and theming showcases: a control column and a phone beside it. */
.editor-demo-grid,
.theming-demo-grid {
  display: grid; grid-template-columns: 1.35fr 1fr;
  gap: 28px; align-items: start; max-width: 920px; margin: 0 auto;
}
/* min-width: 0, and it is doing real work. A grid item's automatic minimum
   size is its CONTENT, so an item holding a horizontally scrolling strip
   does not scroll -- it grows, and takes the track with it. The icon picker
   is 26 tiles two rows deep and blew this column out to 1367px before this
   line existed. Same fix, same reason, as min-height:0 on .card-own-fields. */
.editor-demo-panel,
.theming-demo-panel { min-width: 0; }
/* THE COLUMN STRETCHES; THE FRAME IS WHAT GETS CAPPED AND CENTRED.

   .preview-phone-frame has no width of its own -- on the dashboard it fills
   .dash-preview-panel, an ordinary block, and that is where its proportions
   come from. Two ways of arranging this column both took that away:

     display:flex + align-items:center  -> the frame shrinks to its content
     max-width + margin:0 auto ON THE GRID ITEM -> an auto inline margin
       absorbs the free space in the track, which turns off stretch and
       makes the item shrink-to-fit as well

   Both produced a phone about half the width the dashboard draws. So the
   grid item is left alone to fill its track, and the cap and the centring
   move inside it, where margin:auto means what it looks like it means. The
   note above .preview-open records the same trap from the other direction. */
.editor-demo-preview .preview-phone-frame,
.theming-demo-preview .preview-phone-frame {
  max-width: 300px;
  margin: 0 auto;
}

/* Three panels, no connecting arrows: .journey-arrow's offset is tuned to
   two panels of equal height and these three are a page list, a short form
   and a phone frame. */
.subpages-flow {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 28px 24px; max-width: 1040px; margin: 0 auto; align-items: start;
}
.subpages-flow .demo-panel { width: 100%; }

/* The shared claim form's name check, read on the violet CTA panel.

   Nothing about the form changes -- same partial, same endpoint, same
   words. Only the two colours that were chosen against a near-white hero
   and do not survive the move: #E74C3C on the violet measures about 2.4:1,
   and .name-invalid's rgba(27,27,47,0.4) is near-black at 40% opacity, which
   on this panel is not a low-contrast message so much as an absent one.

   Scoped to .cta-box, so the hero is untouched. */
.cta-box .name-taken { color: #FF9E90; }
.cta-box .name-invalid { color: rgba(255,255,255,0.7); }

/* The line that keeps a price off this page. */
.tier-note {
  max-width: 600px; margin: 0 auto 8px;
  text-align: center; font-size: 14px; color: rgba(27,27,47,0.5);
}
.tier-note a { color: var(--violet); font-weight: 600; }

/* A Pro feature card. Composes .case-card and .case-card--light -- see the
   note on .upgrade-card: a second set of classes for the same card is how the
   avatar circle came to have two.

   DOUBLE CLASS, and not for tidiness. .case-card.case-card--light is 0,2,0
   and sets the background and border this has to replace; at 0,1,0 this rule
   simply lost, and the Pro cards rendered identically to the Free ones. */
.case-card.case-card--pro {
  background: rgba(91,75,212,0.04);
  border-color: rgba(91,75,212,0.18);
}

@media (max-width: 900px) {
  .editor-demo-grid,
  .theming-demo-grid { grid-template-columns: 1fr; }
  .subpages-flow { grid-template-columns: 1fr; }
  /* The arrow is a horizontal connector; stacked panels have nothing for it
     to connect, and its 74px top offset leaves a gap that reads as a bug. */
  .journey-arrow { display: none; }
}
