/* Breakpoint convention: mobile-first base rules; use min-width queries only to enhance wider layouts. */
.pre-wrap { white-space: pre-wrap; }

:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 16px;
  --space-4: 24px;
  --space-5: 32px;
  --space-6: 48px;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  color-scheme: light;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--brand-secondary);
  color: #17191c;
  font-family: var(--brand-font);
  font-size: 1rem;
}

img,
video,
iframe {
  display: block;
  max-width: 100%;
}

button,
input,
select,
textarea {
  color: inherit;
  font: inherit;
}

a {
  color: var(--brand-primary);
  text-underline-offset: var(--space-1);
}

h1,
h2,
h3,
h4,
h5 {
  line-height: 1.2;
  margin: 0 0 var(--space-3);
}

h1,
h2 {
  font-family: var(--brand-display-font);
}

h1 {
  font-size: clamp(2rem, 5vw, 3rem);
  margin-bottom: var(--space-3);
}

h2 { font-size: 1.5rem; }
h3 { font-size: 1.2rem; }

p,
ul,
pre {
  margin: 0 0 var(--space-3);
}

pre {
  max-width: 100%;
  overflow: auto;
  white-space: pre-wrap;
}

.site-header {
  min-height: 64px;
  background: #fff;
  border-bottom: 1px solid #e5e5e5;
}

.header-inner {
  min-height: 64px;
  max-width: 1200px;
  margin-inline: auto;
  padding-inline: var(--space-4);
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: #17191c;
  font-size: 1.125rem;
  font-weight: 600;
  text-decoration: none;
  /* PTM-48 phase 2: measured hit area was 119x22 -- vertical padding (not a
     font-size bump) brings it to the >=44px tap-target floor. .header-inner is
     64px tall and align-items:center, so the extra invisible padding just grows
     the link's own box and keeps the wordmark centered where it already sits. */
  padding-block: 11px;
}

.brand-mark {
  font-family: var(--brand-display-font);
  font-size: 1.375rem;
  font-weight: 700;
  /* Brand-book rule (PTM-76): on the white/chalk header the wordmark LETTERS are
     ink -- they inherit .brand's #17191c. The accent (--brand-primary) belongs
     ONLY to the trailing period, via .brand-period. The letters must NEVER be the
     accent colour ("accent is scarce, never wallpaper" / "RED IS A NUMBER, NOT
     WALLPAPER"). Do not set this back to var(--brand-primary). */
  color: inherit;
  line-height: 1;
}

/* PTM-74: brand book's own rule -- "the emerald full stop that is always part of
   the mark" -- the trailing period on any wordmark-style render of product_name
   (header brand-mark, hero H1, help-page H1) always carries the accent color, even
   where the surrounding text uses a different color (e.g. .hero h1's --hero-fg). */
.brand-period {
  color: var(--brand-primary);
}

.site-nav {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-4);
}

.site-nav a {
  color: #17191c;
  font-weight: 600;
}

.public-nav {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.newcomer-nav {
  padding-inline: var(--space-4);
}

.newcomer-nav .language-toggle {
  color: var(--hero-fg);
}

/* PTM-249: mobile account exit. It lives in the header rather than among portal
   destinations, remains a 44px touch target, and uses a restrained danger outline:
   findable as an exit without competing with the primary bottom navigation. */
.mobile-account-controls {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.mobile-mode-switch {
  margin: 0;
}

.mobile-mode-switch button {
  min-height: 44px;
  padding: 9px 8px;
  border: 0;
  background: transparent;
  color: var(--brand-primary);
  font-weight: 600;
  cursor: pointer;
}

.mobile-account-exit {
  min-height: 44px;
  margin-left: auto;
  padding: 9px 12px;
  display: inline-flex;
  align-items: center;
  border: 1px solid #b52f2f;
  border-radius: 7px;
  color: #a52222;
  font-weight: 700;
  text-decoration: none;
}

.mobile-account-controls .mobile-account-exit {
  margin-left: 0;
}

/* The newcomer keeps the same desktop nav as before, but its mobile exit is the
   separate header control above—not the last item in a row of destinations. */
.desktop-nav-signout {
  display: none;
}

.client-drawer-trigger {
  min-width: 44px;
  min-height: 44px;
  margin-left: auto;
  padding: 7px 9px;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  border: 0;
  background: transparent;
  color: #17191c;
  font-weight: 650;
  cursor: pointer;
}

.client-drawer-backdrop {
  position: fixed;
  z-index: 30;
  inset: 64px 0 calc(64px + env(safe-area-inset-bottom));
  background: rgb(0 0 0 / 42%);
}

.client-drawer {
  position: fixed;
  z-index: 31;
  top: 64px;
  right: 0;
  bottom: calc(64px + env(safe-area-inset-bottom));
  width: min(88vw, 360px);
  padding: var(--space-3);
  overflow-y: auto;
  background: #fff;
  box-shadow: -5px 0 20px rgb(0 0 0 / 18%);
}

.client-drawer[hidden],
.client-drawer-backdrop[hidden] { display: none; }
.drawer-open { overflow: hidden; }
.client-drawer-heading { display: flex; align-items: center; justify-content: space-between; }
.client-drawer-heading h2 { margin: 0; font-size: 1rem; }
.client-drawer-close { min-width: 44px; min-height: 44px; border: 0; background: transparent; font-size: 1.8rem; cursor: pointer; }
.client-drawer-nav { display: grid; margin-top: var(--space-2); }
.client-drawer-nav a,
.client-drawer-exit a {
  min-height: 48px;
  padding: 11px var(--space-2);
  display: flex;
  align-items: center;
  gap: 10px;
  border-radius: 7px;
  color: #333;
  font-weight: 600;
  text-decoration: none;
}
.client-drawer-nav a[aria-current="page"] { color: var(--brand-primary); background: color-mix(in srgb, var(--brand-primary) 9%, white); }
.client-drawer-account,
.client-drawer-exit { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid #ddd; }
.client-drawer-account { display: grid; gap: var(--space-2); }
.client-drawer-exit a { color: #a52222; }

@media (prefers-reduced-motion: no-preference) {
  .client-drawer { animation: client-drawer-in .18s ease-out; }
  @keyframes client-drawer-in { from { transform: translateX(100%); } }
}

.app-nav {
  position: fixed;
  z-index: 20;
  right: 0;
  bottom: 0;
  left: 0;
  background: #fff;
  border-top: 1px solid #d9d9d9;
  padding-bottom: env(safe-area-inset-bottom);
  box-shadow: 0 -4px 16px rgb(0 0 0 / 8%);
}

.nav-primary {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.nav-primary > a {
  min-height: 64px;
  padding: 7px 3px 5px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  color: #555;
  font-size: .72rem;
  font-weight: 650;
  line-height: 1.15;
  text-align: center;
  text-decoration: none;
}

.nav-primary > a[aria-current="page"] {
  color: var(--brand-primary);
  background: color-mix(in srgb, var(--brand-primary) 9%, white);
}

.nav-icon {
  width: 24px;
  height: 24px;
  flex: none;
}

.nav-secondary,
.nav-utility,
.nav-account {
  display: none;
}

.page-shell {
  padding-bottom: calc(88px + env(safe-area-inset-bottom));
}

.hub-list {
  display: grid;
  overflow: hidden;
  border: 1px solid #dedede;
  border-radius: 12px;
  background: #fff;
}

.hub-list a {
  min-height: 48px;
  padding: 12px var(--space-3);
  display: flex;
  align-items: center;
  border-bottom: 1px solid #ededed;
  color: #17191c;
  font-weight: 600;
  text-decoration: none;
}

.hub-list a:last-child {
  border-bottom: 0;
}

.section-tabs {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
  border-bottom: 1px solid #ccc;
}

.section-tabs a {
  min-height: 44px;
  padding: 10px var(--space-3);
  color: #555;
  font-weight: 650;
  text-decoration: none;
}

.section-tabs a[aria-current="page"] {
  color: var(--brand-primary);
  border-bottom: 3px solid var(--brand-primary);
}

.timeline {
  display: grid;
  gap: var(--space-2);
  padding: 0;
  list-style: none;
}

.timeline-item a {
  min-height: 64px;
  padding: var(--space-3);
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px var(--space-3);
  border-left: 4px solid #bbb;
  border-radius: 8px;
  background: #fff;
  color: #17191c;
  text-decoration: none;
}

.timeline-item--current a { border-color: var(--brand-primary); }
.timeline-item--completed a { border-color: #38855b; }
.timeline-item .status { grid-column: 1 / -1; color: #666; }
.history-week { padding-block: var(--space-3); border-top: 1px solid #ddd; }
.history-set { display: inline-block; margin: 2px var(--space-2) 2px 0; }

@media (min-width: 1024px) {
  .client-drawer-trigger,
  .client-drawer,
  .client-drawer-backdrop { display: none; }
  .mobile-account-controls {
    display: none;
  }

  .mobile-account-exit {
    display: none;
  }

  .desktop-nav-signout {
    min-height: 44px;
    padding: 10px 12px;
    display: flex;
    align-items: center;
    border: 1px solid #b52f2f;
    border-radius: 7px;
    color: #a52222;
    font-weight: 700;
    text-decoration: none;
  }

  /* PTM-126 tweak: full-width top bar with the wordmark centred, sidebar sits below it */
  body:has(.app-nav) .header-inner {
    max-width: none;
    justify-content: center;
  }

  body:has(.app-nav) .page-shell,
  body:has(.app-nav) > .flash {
    margin-left: 260px;
  }

  .app-nav {
    top: 64px;
    right: auto;
    width: 260px;
    padding: var(--space-4) var(--space-3);
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    border-top: 0;
    border-right: 1px solid #d9d9d9;
    box-shadow: 2px 0 12px rgb(0 0 0 / 5%);
  }

  .nav-account {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
    padding-bottom: var(--space-3);
    border-bottom: 1px solid #e5e5e5;
  }

  .nav-primary {
    display: flex;
    flex-direction: column;
  }

  .nav-primary > a {
    min-height: 44px;
    padding: 9px 12px;
    flex-direction: row;
    justify-content: flex-start;
    gap: 10px;
    border-radius: 7px;
    font-size: .95rem;
    text-align: left;
  }

  .nav-icon {
    width: 21px;
    height: 21px;
  }

  .nav-primary > a[href$="/hub"],
  .nav-primary > a[href$="/personal"] {
    display: none;
  }

  .nav-secondary {
    display: flex;
    flex-direction: column;
    margin-top: var(--space-4);
    padding-top: var(--space-3);
    border-top: 1px solid #e5e5e5;
  }

  .nav-group-label {
    margin: 0 12px var(--space-2);
    color: #777;
    font-size: .75rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
  }

  .nav-secondary a {
    min-height: 44px;
    padding: 10px 12px;
    display: flex;
    align-items: center;
    border-radius: 7px;
    color: #333;
    text-decoration: none;
  }

  .nav-utility {
    display: grid;
    gap: var(--space-3);
    margin-top: auto;
    padding-top: var(--space-4);
    border-top: 1px solid #e5e5e5;
  }

  .nav-signout {
    min-height: 44px;
    padding: 10px 12px;
    display: flex;
    align-items: center;
    border: 1px solid #b52f2f;
    border-radius: 7px;
    color: #a52222;
    font-weight: 700;
    text-decoration: none;
  }

  .page-shell {
    padding-bottom: var(--space-6);
  }
}

/* Account-type switch (coach <-> client mode); shown only to dual-role users. */
.mode-toggle {
  display: inline-flex;
  margin: 0;
}
.mode-toggle button {
  padding: .25rem .6rem;
  border: 1px solid var(--brand-primary);
  border-radius: 999px;
  background: transparent;
  color: var(--brand-primary);
  font-weight: 600;
  cursor: pointer;
}
.mode-toggle button:hover {
  background: var(--brand-primary);
  color: #fff;
}

.display-title {
  padding: var(--space-2) var(--space-3);
  background: var(--brand-primary);
  color: #fff;
  text-align: center;
  font-weight: 600;
}

.hero {
  max-width: 640px;
  margin-inline: auto;
  padding: 12vh var(--space-4) var(--space-6);
  text-align: center;
}

/* PTM-73: hero-sized brand mark, shown in place of the plain-text H1 once a real
   logo has been uploaded -- the large-format context the brand book's own rule
   reserves for the full mark (vs. the small/running-text wordmark in the nav).
   Sized off the hero's own scale rather than an arbitrary number: max-width caps
   at half the .hero container (640px / 2 = 320px) and max-height at roughly 3x
   the H1's own max size (h1 clamps to 3rem/48px above), so an oversized upload
   can't blow out the hero but a normal mark reads at the same visual weight the
   H1 it replaces would have had.
   PTM-74 fix: the original `width: min(320px, 100%); height: auto; max-height:
   160px;` independently pinned the width while only clamping the height, so a
   square upload (e.g. Lai's 1254x1254 mark) got squashed into a 320x160
   rectangle instead of scaled down whole. Both dimensions must be `auto` and
   bounded only by max- constraints so the browser scales to fit the box without
   distortion, for square or non-square logos alike; max-width keeps the
   min(320px, 100%) responsive cap the original had. */
.hero-logo {
  display: block;
  margin: 0 auto var(--space-3);
  /* Interim big-hero sizing (Einh 2026-07-21, "way bigger" pending the real
     landing page). Both dimensions auto, bounded only by max- so any aspect
     ratio scales to fit without distortion (the PTM-74 invariant). */
  max-width: min(560px, 100%);
  max-height: 480px;
  width: auto;
  height: auto;
}

.subtitle {
  display: block;
  margin-bottom: var(--space-5);
  color: #555;
  font-size: 1.125rem;
}

/* Hero text sits on the page background (--brand-secondary), which real brand packs
   set dark (Coal, Midnight Navy). --hero-fg is contrast-picked per instance in
   public.context(); this narrow rule keeps the global body color for every other surface. */
.hero h1,
.hero .subtitle {
  color: var(--hero-fg);
}

/* PTM-72: generic placeholder sub-headline under the H1 (unrelated to the gated
   certificate_confirmed .subtitle above it) so the landing page never ships bare. */
.hero-tagline {
  margin: 0 0 var(--space-5);
  color: var(--hero-fg);
  font-size: 1.125rem;
}

/* PTM-72: minimal generic value-prop row below the hero, same dark-secondary-safe
   text color as the rest of the hero content — this is the only content on the page. */
.value-props {
  max-width: 860px;
  margin: 0 auto var(--space-6);
  padding: 0 var(--space-4);
}

.value-props ul {
  display: flex;
  flex-flow: row wrap;
  justify-content: center;
  gap: var(--space-2) var(--space-5);
  list-style: none;
  margin: 0;
  padding: 0;
  text-align: center;
}

.value-props li {
  color: var(--hero-fg);
  font-weight: 600;
}

/* Public landing-page sections. Keep the same restrained, card-based language as
   the authenticated surfaces while giving the marketing page a readable rhythm. */
.site-trust {
  padding: var(--space-3) var(--space-4);
  border-block: 1px solid #e5e5e5;
  background: #fff;
}

.site-trust ul {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2) var(--space-6);
  max-width: 1000px;
  margin: 0 auto;
  padding: 0;
  list-style: none;
  text-align: center;
  font-weight: 600;
}

.site-story,
.site-approach,
.site-services,
.site-faq,
.site-cta {
  max-width: 1100px;
  margin: var(--space-6) auto;
  padding: var(--space-6) var(--space-4);
}

.site-story {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-6);
  background: #fff;
  border-radius: 8px;
}

.site-story figure {
  margin: 0;
}

.site-story figure img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 8px;
}

.eyebrow {
  margin-bottom: var(--space-2);
  color: var(--brand-primary);
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
}

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

.story-body p:last-child {
  margin-bottom: 0;
}

.site-story blockquote {
  margin: 0 0 var(--space-4);
  padding-left: var(--space-3);
  border-left: var(--space-1) solid var(--brand-primary);
  font-style: italic;
}

.site-approach,
.site-services,
.site-faq,
.site-cta {
  text-align: center;
}

.site-approach > p:not(.eyebrow),
.site-services > p:not(.eyebrow) {
  max-width: 680px;
  margin-inline: auto;
}

.site-approach ol,
.service-cards {
  display: grid;
  gap: var(--space-4);
  margin: var(--space-5) 0 0;
  padding: 0;
  list-style: none;
}

/* The 3-step approach list is always exactly 3 steps (landing.html hardcodes
   step1/2/3), so a fixed 3-column grid is correct there. */
.site-approach ol {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* PTM-196 (PTM-92 MINOR 8): services is a coach-configured catalogue, not a fixed
   count -- the old fixed repeat(3, ...) left a lopsided gap at desktop whenever a
   coach's typical starter set was 1 or 2 services. auto-fit collapses the empty
   track instead of stretching a lone card full-width or leaving a hole next to a
   pair. */
.service-cards {
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.site-approach li,
.service-cards li {
  padding: var(--space-4);
  border: 1px solid #e5e5e5;
  border-radius: 8px;
  background: #fff;
}

.site-approach li {
  counter-increment: approach-step;
}

.site-approach ol {
  counter-reset: approach-step;
}

.site-approach li::before {
  content: counter(approach-step);
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  margin: 0 auto var(--space-3);
  border-radius: 50%;
  background: var(--brand-primary);
  color: #fff;
  font-weight: 700;
}

.service-cards li {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.service-cards .button {
  margin-top: auto;
}

.service-meta {
  color: #555;
  font-weight: 600;
}

.footnote {
  margin-top: var(--space-3);
  color: #555;
  font-size: .875rem;
}

.site-faq {
  text-align: left;
}

.site-faq > .eyebrow,
.site-faq > h2 {
  text-align: center;
}

.site-faq dl {
  max-width: 800px;
  margin: var(--space-5) auto 0;
}

.site-faq dt {
  padding-top: var(--space-3);
  border-top: 1px solid #e5e5e5;
  font-weight: 700;
}

.site-faq dd {
  margin: var(--space-2) 0 var(--space-4);
}

.site-cta {
  background: #fff;
  border-radius: 8px;
}

.button,
button {
  display: inline-block;
  padding: 12px 28px;
  border: 0;
  border-radius: 8px;
  background: var(--brand-primary);
  color: #fff;
  cursor: pointer;
  font-weight: 600;
  line-height: 1.25;
  text-decoration: none;
}

.button:hover,
button:hover {
  filter: brightness(1.1);
  text-decoration: none;
}

.button:focus-visible,
button:focus-visible {
  outline: 2px solid var(--brand-primary);
  outline-offset: 2px;
}

.button.quiet {
  background: transparent;
  color: var(--brand-primary);
}

.button.quiet:hover {
  text-decoration: underline;
}

.actions {
  display: flex;
  flex-flow: row wrap;
  justify-content: center;
  gap: var(--space-3);
}

.page {
  max-width: 860px;
  margin: var(--space-5) auto;
  padding: var(--space-5);
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .06);
}

form {
  margin-block: var(--space-4);
}

label {
  display: block;
  margin-bottom: var(--space-1);
  font-weight: 600;
}

input:not([type="checkbox"]):not([type="radio"]),
select,
textarea {
  width: 100%;
  margin-bottom: var(--space-4);
  padding: 10px 12px;
  border: 1px solid #d0d0d0;
  border-radius: 8px;
  background: #fff;
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--brand-primary);
  outline: 2px solid transparent;
}

textarea {
  min-height: 128px;
  resize: vertical;
}

/* PTM-202: the license-token paste field holds one dense ~300-character string, not
   prose -- the 128px floor above (sized for real multi-line fields like booking
   notes) rendered it as an oversized, mostly-empty box that made a TRUNCATED paste
   look just as normal as a complete one. Overriding min-height back down to what
   rows="4" already computes (~4 lines at this font/width) makes a short paste
   visibly short before the coach ever submits it. Width is untouched -- it stays
   the same 100% as every other field on the page. */
.license-token-field {
  min-height: auto;
}

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

/* PTM-48 phase 2: branding's qualifications/languages checkbox lists measured 13x13 --
   bumped to a friendlier ~18px without touching every checkbox in the app. */
.tap-checkbox-list input[type="checkbox"] {
  width: 18px;
  height: 18px;
  vertical-align: middle;
}

table {
  width: 100%;
  border-collapse: collapse;
}

th {
  color: #666;
  font-size: .75rem;
  letter-spacing: .05em;
  text-align: left;
  text-transform: uppercase;
}

th,
td {
  padding: 10px 8px;
  border-bottom: 1px solid #eee;
  vertical-align: top;
}

[role="alert"] { color: #b3261e; }
[role="status"] { color: #1b6e3c; }

.newcomer-card,
.panel,
.programme-week,
.programme-day,
.exercise {
  margin-bottom: var(--space-4);
  padding: var(--space-3);
  border: 1px solid #e5e5e5;
  border-radius: 8px;
}

.programme-day {
  border-left: var(--space-1) solid var(--brand-primary);
}

.device-activity-card {
  margin-bottom: var(--space-3);
  padding: var(--space-3);
  border: 1px dashed #999;
  border-left: var(--space-1) solid #666;
  border-radius: 8px;
  background: #f7f6f3;
}

/* PTM-116: the card heading dropped to h3 when "Other Workouts" became the section's
   own h2 -- the badge row is laid out here, so this selector has to follow it down. */
.device-activity-card h3 {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.device-activity-card .tag {
  padding: .1rem .5rem;
  border: 1px solid currentColor;
  border-radius: 999px;
  font-family: var(--brand-font);
  font-size: .75rem;
  font-weight: 600;
}

.device-activity-card p {
  margin: 0;
}

.exercise-prescription {
  font-size: 1.125rem;
  font-weight: 600;
}

video,
iframe {
  width: 100%;
}

iframe {
  border: 0;
  aspect-ratio: 16 / 9;
}

.admin-actions,
.admin-grid,
.stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.admin-actions form,
td form {
  margin: 0;
}

.admin-grid > *,
.stats > * {
  flex: 1 1 240px;
}

.admin-wide {
  flex-basis: 100%;
}

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

.stat {
  padding: var(--space-3);
  border-left: var(--space-1) solid var(--brand-primary);
  background: #fff;
}

@media (max-width: 480px) {
  .header-inner {
    padding: var(--space-3);
    flex-wrap: wrap;
  }

  .site-nav {
    flex-basis: 100%;
    margin-left: 0;
    justify-content: flex-start;
  }

  .public-nav {
    flex-basis: 100%;
    margin-left: 0;
  }

  .hero {
    padding-top: 8vh;
  }

  .value-props ul {
    flex-direction: column;
    gap: var(--space-2);
  }

  .site-trust ul {
    flex-direction: column;
  }

  .site-story,
  .site-approach,
  .site-services,
  .site-faq,
  .site-cta {
    margin-block: var(--space-4);
    padding: var(--space-4) var(--space-3);
  }

  .site-story,
  .site-approach ol,
  .service-cards {
    grid-template-columns: 1fr;
  }

  .page {
    margin: var(--space-3);
    padding: var(--space-3);
  }
}

/* Authenticated mobile shell must reserve the bottom-tab footprint after the
   generic .page padding rule above. */
body:has(.app-nav) .page-shell {
  padding-bottom: calc(88px + env(safe-area-inset-bottom));
}

.hub-utility {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid #ddd;
}

.hub-signout {
  min-height: 44px;
  padding: 10px 12px;
  display: flex;
  align-items: center;
  border: 1px solid #b52f2f;
  border-radius: 7px;
  color: #a52222;
  font-weight: 700;
  text-decoration: none;
}

@media (min-width: 1024px) {
  body:has(.app-nav) .page-shell {
    margin-left: max(292px, calc((100vw - 860px + 260px) / 2));
    margin-right: 32px;
    padding-bottom: var(--space-5);
  }
  .hub-utility { display: none; }
}
/* Global language control and collaboration surfaces. */
.language-toggle{display:inline-flex;align-items:center;gap:.25rem;margin:0}.language-toggle button{padding:.25rem .4rem;background:transparent;color:inherit;border:0}.language-toggle button[aria-current=true]{text-decoration:underline;font-weight:700}.review-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:1rem}.week-editor fieldset{margin:1rem 0;padding:1rem}.week-editor [data-exercise]{border-left:3px solid var(--brand-primary);padding:.75rem;margin:.75rem 0}.week-editor [data-set]{display:flex;gap:.5rem;margin:.4rem 0}
/* PTM-232: "Save and Publish, next to each other" -- Save (form= attribute, see
   client_detail.html) and Publish's own tiny <form> sit as flex items in one row
   instead of each stacking full-width the way every other .week-editor <form> does. */
.week-editor .week-editor-actions{display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center;margin-block:var(--space-4)}
.week-editor .week-editor-actions form{margin-block:0}
/* PTM-180: reps/load/RPE need a VISIBLE indicator per set row (Einh: placeholder text
   alone hides once a value is typed) -- but bold block-level .label from the generic
   form rule above would make every set row tall. Keep it light: small, non-bold,
   stacked above a narrower input, sized to share the flex row instead of 100%. */
.week-editor [data-set] .set-field{flex:1 1 0;font-weight:400;font-size:.8em;opacity:.85;margin:0}
.week-editor [data-set] .set-field input{width:100%;margin:.15rem 0 0}
@media(max-width:720px){.review-grid{grid-template-columns:1fr}}

/* Cost actions must read as cost (UX audit 2026-07-20): distinct from primary. */
.button-spend{background:#b3261e;color:#fff;border:2px solid #7f1a14;font-weight:600;}
.button-spend:hover{background:#7f1a14;}

/* Field help renders BETWEEN label and input (Einh 2026-07-20: a skip-note
   after the textbox is read too late). Block-level so it owns its line. */
.field-help{display:block;font-size:.85em;opacity:.75;margin:.15rem 0 .35rem;font-weight:400;}

/* PTM-211: session modality-mix intake sub-form (gym count + cardio count under one field),
   and the coach gap-form's mirror of it. Stacks like every other intake field at 390px;
   reuses .field-help for the muted sub-guidance. */
.session-mix{display:block}
.session-mix-sublabel{display:block;font-weight:600;margin:.35rem 0 .15rem}
.session-mix-field{display:block;margin-bottom:.35rem}
.session-mix input[type=number]{max-width:6rem}
.session-mix h4{margin:0 0 var(--space-1);font-size:1rem}

/* PTM-211: TABULAR FIGURES. Durations and paces sit in columns a coach reads down
   ("4:12", "5:45", "12:00"); with proportional digits a 1 is narrower than a 0 and the
   column jitters row to row. font-variant-numeric is the whole fix and it degrades to
   plain digits on a font without the feature -- never a layout dependency. */
.number-tabular{font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}

/* PTM-211: the per-day MODALITY choice -- four sports as text+icon badges. NEVER
   colour-only (colour-not-only): the sport's name is inside every badge and the icon is
   aria-hidden decoration on top of it, so the choice survives greyscale, low vision and a
   screen reader alike. The radio itself stays in the DOM (it is the form control and the
   focus target) and is positioned, not display:none, so keyboard focus still lands. */
.modality-choice{display:flex;flex-wrap:wrap;gap:var(--space-2);border:0;padding:0;margin:var(--space-2) 0}
.modality-choice legend{font-weight:600;margin-bottom:var(--space-1)}
.modality-badge{position:relative;display:inline-flex;align-items:center;gap:.4rem;
  min-height:44px;padding:.35rem .75rem;border:2px solid #ccc;border-radius:.5rem;
  font-weight:600;font-size:.9em;cursor:pointer}
.modality-badge input[type=radio]{position:absolute;opacity:0;width:100%;height:100%;left:0;top:0;margin:0;cursor:pointer}
.modality-badge:has(input:checked){border-color:var(--brand-primary);color:var(--brand-primary)}
.modality-badge:has(input:focus-visible){outline:3px solid var(--brand-primary);outline-offset:2px}
.modality-icon{width:20px;height:20px;flex:0 0 auto}

/* PTM-211: the cardio step editor. One step per row on desktop, stacked at 390px --
   the same shape the gym exercise rows take, so a coach switching a day between
   modalities does not have to relearn the form. */
[data-cardio-editor] h4{margin:var(--space-2) 0 var(--space-1);font-size:1rem}
[data-cardio-step]{display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:flex-end;
  padding:var(--space-2) 0;border-top:1px solid #e2e2e2}
[data-cardio-step] label{flex:1 1 8rem;min-width:7rem}
[data-cardio-step] input,[data-cardio-step] select{width:100%;min-height:44px}
@media (max-width:520px){[data-cardio-step] label{flex:1 1 100%}}

/* PTM-211c: the CLIENT-side cardio surfaces (§3.3 week view, §3.4 session card).
   The static modality badge is the read-only twin of .modality-badge above -- same box,
   minus the radio's pointer/hover affordances, so a badge the client cannot click does
   not look like a control they failed to press. */
.modality-badge--static{cursor:default;min-height:0;padding:.15rem .5rem;border-width:1px;
  font-size:.8em;text-transform:uppercase;letter-spacing:.02em;flex:0 0 auto}
.modality-badge--static .modality-icon{width:16px;height:16px}
.week-summary{margin:0 0 var(--space-2);font-weight:600}
.workout-headline{display:block;font-weight:400;font-size:.9em;color:#555}

/* The prescription card. Targets are a real table (each step is a row of labelled
   values, not four loose numbers), figures are tabular so the pace column aligns down
   the page, and the phase header carries its own "(n rounds)" so the repeat binds
   visibly to the steps it repeats. */
.cardio-card{margin:var(--space-2) 0}
.cardio-phase{margin:var(--space-3) 0}
.cardio-phase h3{margin:0 0 var(--space-1);font-size:.85rem;text-transform:uppercase;
  letter-spacing:.06em;color:#555}
.cardio-rounds{text-transform:none;letter-spacing:normal;font-weight:600;color:inherit}
.cardio-steps{width:100%;border-collapse:collapse}
.cardio-steps th,.cardio-steps td{text-align:left;padding:.4rem .5rem;border-bottom:1px solid #e2e2e2}
.cardio-steps thead th{font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;color:#666}
.cardio-steps tbody th{font-weight:600}
.cardio-note{margin:var(--space-2) 0}

/* The log card (§3.4). Garmin is the ENCOURAGED path and is rendered as a disabled
   control with its "coming soon" state spelled out next to it -- never an enabled button
   that quietly does nothing. Exactly one primary CTA ("Mark run complete"). */
.cardio-log{margin:var(--space-3) 0}
.cardio-log-import{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2)}
.cardio-log-or{margin:var(--space-2) 0;color:#666}
.cardio-log-fields{display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:flex-end}
.cardio-log-fields label{flex:1 1 8rem;min-width:7rem}
.cardio-log-fields input{width:100%;min-height:44px}
.cardio-log-pace{flex:1 1 8rem;min-width:7rem}
.cardio-log-pace strong{display:block;font-size:1.1em}
@media (max-width:520px){.cardio-log-fields label,.cardio-log-pace{flex:1 1 100%}
  .cardio-steps th,.cardio-steps td{padding:.35rem .25rem;font-size:.9em}}

/* PTM-111: the mail guide stays next to the load-bearing SMTP form, but does not
   make an already long settings page longer until the coach asks for it. */
.smtp-guide{margin:var(--space-3) 0;padding:var(--space-3);border:1px solid #ccc;border-radius:8px;}
.smtp-guide > summary{cursor:pointer;font-weight:700;color:var(--brand-primary);}
.smtp-guide[open] > summary{margin-bottom:var(--space-3);}
.smtp-guide h3{font-size:1rem;margin:var(--space-3) 0 var(--space-1);}
.smtp-guide li + li{margin-top:var(--space-2);}

/* PTM-34: 'Paid until' visual trigger (coach-only, never client-facing). Colour is
   never the only signal -- the text label ("Paid"/"Expired"/"Not set") always ships
   alongside it, matching the [role=alert]/[role=status] palette used elsewhere. */
.paid-badge{display:inline-block;padding:.1rem .5rem;border-radius:.25rem;font-weight:600;font-size:.85em;}
.paid-badge--current{color:#1b6e3c;border:1px solid #1b6e3c;}
.paid-badge--expired{color:#b3261e;border:1px solid #b3261e;}
.paid-badge--unset{color:#666;border:1px solid #999;}
/* PTM-203: client archive status, same pill as paid-until above -- deliberately its
   own modifier names (--active/--archived) rather than reusing --current/--expired,
   which mean something unrelated (payment, not archival) on the very same page. */
.paid-badge--active{color:#1b6e3c;border:1px solid #1b6e3c;}
.paid-badge--archived{color:#666;border:1px solid #999;}

/* PTM-205: WHY a client is archived, not just THAT (roster Archived tab + client
   detail). Same pill shape as .paid-badge, its own modifier names since 'manual' vs
   'unpaid' means something different from payment status or active/archived. Neutral
   grey for both -- this is informational, not an alarm (collapse-don't-hide already
   put the alarm on the dashboard's warning card before archival ever fires). */
.archived-reason-badge{display:inline-block;padding:.1rem .5rem;border-radius:.25rem;font-weight:600;font-size:.85em;color:#666;border:1px solid #999;}

/* PTM-43/PTM-44 metrics (styling pass, no new component vocabulary): the
   in-session duration/HR band reuses .stats/.stat (same box the admin
   dashboard's usage numbers use) with an h2 instead of a <strong> label --
   tighten it to read like a stat card rather than a full section heading. */
.stat h2{font-size:1rem;margin-bottom:var(--space-1);}

/* Inline SVG history chart (app/metrics.py::render_history_svg) ships with a
   bare `stroke="currentColor"` and un-filled points so it themes per
   whitelabel instance from CSS alone -- no brand colour is hardcoded here.
   Responsive by scaling (not by scrolling): the chart's own fixed viewBox
   is stretched to the panel's width so it never causes horizontal overflow
   on mobile. */
.metric-chart{display:block;width:100%;height:auto;color:var(--brand-primary);}

/* Coach vs. client points must not be told apart by hue alone (contrast /
   colour-vision requirement): filled-vs-hollow is the real distinguisher,
   colour is secondary, and the legend below repeats it as text. */
.metric-point-coach{fill:var(--brand-primary);stroke:#fff;stroke-width:1.5;}
.metric-point-client{fill:#fff;stroke:var(--brand-primary);stroke-width:2;}

.metric-chart-legend{margin:var(--space-2) 0 0;font-size:.85em;color:#555;}

/* PTM-87 (E14): a corrected entry and an archived definition are both still THERE --
   history is append-only and definitions are archived, never deleted. Both are dimmed
   rather than hidden, and the state is always spelled out in words next to them
   ("Corrected" / "Archived"), so opacity is never the only signal. No brand colour is
   introduced here: this reuses the same muted treatment as .field-help. */
.metric-superseded > td{opacity:.6;}
.metric-archived{opacity:.75;}
.legend-swatch{display:inline-block;width:10px;height:10px;border-radius:50%;margin:0 .35em 0 0;vertical-align:middle;}
.legend-swatch--coach{background:var(--brand-primary);}
.legend-swatch--client{background:#fff;border:2px solid var(--brand-primary);margin-left:.9em;}

/* PTM-93: Intake form builder redesign. Reuses the .panel/.button/form vocabulary
   above; nothing here introduces a new brand colour -- badges and warnings borrow
   the same semantic reds/greens already used by .paid-badge and [role=alert]. */
.intake-toolbar{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:var(--space-4);}
.intake-toolbar button{background:transparent;color:var(--brand-primary);border:1px solid var(--brand-primary);padding:.4rem .9rem;}
.intake-toolbar button:hover{background:var(--brand-primary);color:#fff;}

.intake-section{margin-bottom:var(--space-4);padding:0;border:1px solid #e5e5e5;border-radius:8px;overflow:hidden;}
.intake-section > summary,
.intake-section-header{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-3);background:#f7f6f3;cursor:pointer;list-style:none;font-weight:600;}
.intake-section > summary::-webkit-details-marker{display:none;}
/* PTM-114 (Einh 2026-07-23): "headers should collapse into a single line with a triangle to
   show there is a HEADERS section, without details". The native disclosure marker was hidden
   above to control layout -- so the affordance has to be put back explicitly, or a coach
   concludes sections are not collapsible at all (which is what happened). One triangle,
   rotating on open, on BOTH levels: the section and the individual field row. */
.intake-section > summary::before,
.intake-field-row > summary::before{content:"";flex:0 0 auto;align-self:center;width:0;height:0;
  border-left:.4em solid currentColor;border-top:.32em solid transparent;border-bottom:.32em solid transparent;
  margin-right:var(--space-1);transform:rotate(0deg);transition:transform .12s ease;opacity:.55;}
.intake-section[open] > summary::before,
.intake-field-row[open] > summary::before{transform:rotate(90deg);}
/* A COLLAPSED section is exactly one line: title + count, nothing wrapping. */
.intake-section:not([open]) > summary{white-space:nowrap;overflow:hidden;}
.intake-section:not([open]) > summary .intake-section-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
/* PTM-142: field-row summary is now flex + a tall stacked main column; keep the
   disclosure triangle pinned to the top-left next to the first label line (the shared
   rule above centres it, which would float it down the middle of a tall row). */
.intake-field-row > summary::before{align-self:flex-start;margin-top:.35rem;}
/* Keep the bilingual section label on one line in both states. Long labels yield
   space via ellipsis; the count and edit affordance remain discoverable. */
.intake-section-title{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.intake-section-count{flex:0 0 auto;white-space:nowrap;font-weight:400;color:#666;font-size:.85em;}
.intake-section-body{padding:0 var(--space-3);}
.intake-section-items{padding:var(--space-2) var(--space-3) var(--space-3);}
.intake-section--ungrouped{border-style:dashed;}
.intake-drop-hint{margin:0 0 var(--space-2);padding:.55rem .75rem;border:2px dashed #bbb;border-radius:6px;color:#666;text-align:center;}

.intake-field-row{margin-bottom:var(--space-2);border:1px solid #eee;border-radius:6px;}
/* PTM-142: summary is triangle (::before) + a single growing column that stacks the
   NL/EN/FR label block above a 3-row action stack. Flex (not grid) so the triangle and
   the main column sit on one line while the main column owns its own vertical rhythm. */
.intake-field-row > summary{display:flex;align-items:flex-start;gap:var(--space-2);padding:.6rem var(--space-2);cursor:pointer;list-style:none;}
.intake-field-row > summary::-webkit-details-marker{display:none;}
.intake-field-summary-main{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:var(--space-2);}
.intake-field-summary-label{display:flex;flex-direction:column;min-width:0;}
.intake-field-summary-label > span{overflow-wrap:anywhere;}
.intake-field-summary-label strong{display:inline-block;width:2.25rem;font-size:.75em;color:#666;}
.intake-field-summary-label small{font-weight:400;color:#666;}
/* PTM-142: three stacked action rows -- (1) Edit left + drag handle centred, (2) type
   badge, (3) answered badge -- so every question's handle lines up at the same x. */
.intake-summary-actions{display:flex;flex-direction:column;gap:var(--space-1);}
.intake-action-row{display:flex;align-items:center;}
.intake-action-row--top{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;}
.intake-action-row--top .intake-edit-action{grid-column:1;justify-self:start;}
.intake-action-row--top .drag-handle{grid-column:2;justify-self:center;}
.intake-edit-action{font-size:.85em;text-decoration:underline;color:var(--brand-primary);cursor:pointer;}
.intake-row-actions{display:flex;flex-wrap:wrap;gap:var(--space-2);padding:0 var(--space-2) .6rem calc(var(--space-2) + 1.1em + var(--space-2));}
.intake-row-actions form{margin:0;}
.intake-field-body{padding:0 var(--space-3) var(--space-3);}

/* PTM-142: 6-dot handle is the SortableJS drag grip -- a real >=40px touch target,
   centred in its row. touch-action:none lets a touch-drag start without scrolling. */
.drag-handle{display:inline-flex;align-items:center;justify-content:center;min-width:2.5rem;min-height:2.5rem;cursor:grab;color:#999;font-size:1.25em;line-height:1;user-select:none;touch-action:none;border-radius:6px;}
.drag-handle:hover{color:var(--brand-primary);background:rgba(0,0,0,.04);}
.drag-handle:active{cursor:grabbing;}
.drag-handle:focus-visible{outline:2px solid var(--brand-primary);outline-offset:2px;}
/* SortableJS drag placeholder: a dashed brand outline where the row will land. */
.intake-drag-ghost{opacity:.5;background:rgba(0,0,0,.02);outline:2px dashed var(--brand-primary);outline-offset:-2px;}

.intake-type-badge{display:inline-flex;flex:0 0 auto;white-space:nowrap;font-size:.75em;letter-spacing:.03em;text-transform:uppercase;color:#666;border:1px solid #ccc;border-radius:999px;padding:.1rem .5rem;}

/* Informational (not alarming) -- reuses the pill shape of .mode-toggle button. */
.answered-badge{display:inline-flex;flex:0 0 auto;white-space:nowrap;font-size:.8em;font-weight:600;color:var(--brand-primary);border:1px solid var(--brand-primary);border-radius:999px;padding:.1rem .55rem;}

.intake-key-note{opacity:.7;}

.lang-toggle-local{display:inline-flex;gap:.25rem;margin:0 0 var(--space-2);}
.lang-toggle-local .lang-btn{padding:.2rem .6rem;background:transparent;color:var(--brand-primary);border:1px solid var(--brand-primary);border-radius:999px;font-weight:600;font-size:.85em;}
.lang-toggle-local .lang-btn[aria-current="true"]{background:var(--brand-primary);color:#fff;}
/* A dot, not colour alone, marks a language with an empty optional field its
   sibling language filled in -- so a missing translation can't hide behind the
   toggle (PTM-93 design direction: "make a missing translation obvious"). */
.lang-toggle-local .lang-btn.lang-missing::after{content:"";display:inline-block;width:.4em;height:.4em;margin-left:.35em;border-radius:50%;background:#b3261e;vertical-align:middle;}

.lang-group.lang-hidden{display:none;}

/* PTM-114: [hidden] must beat a component's own display rule, or a group JS hides
   (e.g. number_bounds, which is display:flex) stays visible. One global guard. */
[hidden]{display:none !important;}
/* PTM-123: the always-open add-section / add-field forms collapse behind a button-
   styled summary so they stop eating the page. */
.intake-add-collapser{margin-top:var(--space-3);}
.intake-add-collapser > summary{display:inline-block;list-style:none;cursor:pointer;padding:12px 28px;border-radius:8px;background:var(--brand-primary);color:#fff;font-weight:600;}
.intake-add-collapser > summary::-webkit-details-marker{display:none;}
.intake-add-collapser > summary::before{content:"+ ";}
.intake-add-collapser[open] > summary{margin-bottom:var(--space-3);}
/* PTM-114: drag-drop drop-target cue -- the whole section (header included) highlights
   while a field is dragged over it, so 'the header IS the dropzone' is visible. */
/* PTM-114: section edit hidden behind a pencil anchored right of the title. The section
   expand (triangle) shows its fields; the pencil shows move/delete/rename/save. */
.intake-section > summary .section-edit-pencil{margin-left:auto;flex:0 0 auto;border:0;background:transparent;color:var(--brand-primary);font-size:1.1em;line-height:1;cursor:pointer;padding:.2rem .4rem;border-radius:6px;}
.intake-section > summary .section-edit-pencil:hover,.section-edit-pencil.is-open{background:rgba(0,0,0,.06);}
.intake-section-edit{padding:var(--space-2) var(--space-3);border-bottom:1px solid #eee;}
.intake-section.drop-target,.intake-section--ungrouped.drop-target{outline:2px dashed var(--brand-primary);outline-offset:2px;background:rgba(0,0,0,.02);}


.intake-attr-row{display:flex;gap:var(--space-3);}
.intake-attr-row > label{flex:1 1 0;}

.choice-editor{margin-bottom:var(--space-4);}
.choice-rows{display:flex;flex-direction:column;gap:.4rem;margin-bottom:.5rem;}
.choice-row{display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:.25rem .4rem;align-items:end;}
.choice-row label{margin:0;font-size:.85em;}
.choice-row label:nth-of-type(1){grid-column:1;}
.choice-row label:nth-of-type(2){grid-column:2;}
.choice-row label:nth-of-type(3){grid-column:3;}
.choice-row input{grid-row:2;}
.choice-row button{grid-column:4;grid-row:2;}
.choice-row input{margin-bottom:0;}
.choice-row [data-choice-label-en].choice-label-missing{border-color:#b3261e;background:#fdf1f0;}
.choice-row button{padding:.4rem .6rem;background:transparent;color:#b3261e;border:1px solid #b3261e;}
.choice-empty-hint{margin:0 0 .5rem;}

.orphan-gate{margin:var(--space-2) 0;padding:var(--space-2) var(--space-3);border:1px solid #b3261e;border-radius:8px;background:#fdf1f0;}
.orphan-gate .orphan-warning{margin-bottom:.5rem;}
.confirm-orphan-label{display:flex;align-items:flex-start;gap:.4rem;font-weight:400;}
.confirm-orphan-label input{margin-top:.2rem;}

.intake-preview{background:#fafafa;}
.preview-section{margin:var(--space-3) 0 var(--space-1);padding-top:var(--space-2);border-top:1px solid #e5e5e5;}
.preview-section:first-child{border-top:0;padding-top:0;}
.preview-field input:disabled,
.preview-field select:disabled,
.preview-field textarea:disabled{background:#f0f0f0;color:#666;}

@media (max-width: 720px){
  .choice-row{grid-template-columns:1fr;}
  .intake-attr-row{flex-direction:column;}
}

/* A background worker that is failing or dead: never quietly styled away. */
.worker-degraded{font-weight:700;color:#b3261e;}

/* PTM-124: the one-shot save confirmation. Sits directly under the header, above the
   page, so it is in the same place on every screen -- a coach should not have to hunt
   for it. Colour is not the only signal (the wording says what happened), because
   colour alone fails for a colour-blind reader. */
.flash {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-3) auto 0;
  padding: var(--space-3);
  max-width: 900px;
  border-radius: 8px;
  border-left: var(--space-1) solid currentColor;
}

.flash-success {
  color: #1d5b32;
  background: #e8f5ec;
}

.flash-error {
  color: #8a1f1f;
  background: #fbeaea;
}

/* PTM-226: saved, but a part of it did not take (the branding row is written; the
   Authentik login page has not picked it up yet). Amber, borrowed verbatim from
   .subscription-banner so the palette says the same thing in both places: red stays
   reserved for "your work was not kept". */
.flash-warning {
  color: #7a4b00;
  background: #fdf1dc;
}

.flash-dismiss {
  padding: 0 var(--space-2);
  border: 0;
  background: none;
  color: inherit;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

/* PTM-143: client intake render -- section headers are TITLES not boxes, and a unit
   suffix (cm/kg) sits to the RIGHT of its input, not on a dangling line below. */
.intake-form-heading{margin:var(--space-5) 0 var(--space-2);font-size:1.25rem;font-weight:700;
  padding-bottom:var(--space-1);border-bottom:2px solid var(--brand-primary);}
.intake-form-heading:first-child{margin-top:var(--space-2);}
.unit-field{display:flex;align-items:center;gap:var(--space-2);}
.unit-field > input{flex:1 1 auto;min-width:0;}
.unit-suffix{flex:0 0 auto;color:#555;white-space:nowrap;}
/* PTM-143: locked intake read-only view -- label/value rows, no input chrome. */
.intake-readonly{margin:var(--space-3) 0;}
.intake-readonly-row{display:flex;flex-wrap:wrap;gap:var(--space-1) var(--space-3);
  padding:var(--space-2) 0;border-bottom:1px solid #eee;}
.intake-readonly-label{flex:0 0 12rem;max-width:100%;font-weight:600;color:#333;}
.intake-readonly-value{flex:1 1 12rem;min-width:0;color:#111;overflow-wrap:anywhere;}

/* PTM-33/PTM-155: roster planning lights and demoted 4-week compliance. Every
   planning light carries text as well as its coloured dot. */
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}
.roster-search{display:block;margin:var(--space-3) 0;}
.roster-search input{width:100%;max-width:22rem;min-height:2.75rem;padding:.5rem .75rem;border:1px solid #ccc;border-radius:8px;font-size:1rem;}
.roster-tabs{display:flex;gap:var(--space-2);margin-bottom:var(--space-3);border-bottom:1px solid #e5e5e5;}
.roster-tab{min-height:2.75rem;padding:.5rem 1rem;background:transparent;border:0;border-bottom:3px solid transparent;color:#555;font-weight:600;cursor:pointer;}
.roster-tab:hover{color:var(--brand-primary);}
.roster-tab[aria-selected="true"]{color:var(--brand-primary);border-bottom-color:var(--brand-primary);}
.roster-tab-count{display:inline-block;min-width:1.4em;padding:0 .35em;margin-left:.25em;border-radius:999px;background:#eee;color:#444;font-size:.8em;font-weight:600;text-align:center;}
.roster-tab[aria-selected="true"] .roster-tab-count{background:var(--brand-primary);color:#fff;}
.roster-table{width:100%;border-collapse:collapse;}
.roster-table th{text-align:left;font-size:.8em;text-transform:uppercase;letter-spacing:.03em;color:#666;padding:.4rem .6rem;border-bottom:2px solid #e5e5e5;}
.roster-row td{padding:.6rem;border-bottom:1px solid #eee;vertical-align:middle;}
.roster-row a{font-weight:600;}
.roster-planning{display:inline-flex;align-items:center;gap:.45rem;min-height:2.75rem;padding:.2rem .65rem;border:1px solid;border-radius:999px;font-size:.85em;font-weight:600;}
.roster-planning-dot{width:.7rem;height:.7rem;flex:0 0 .7rem;border-radius:50%;background:currentColor;}
.roster-planning--green{color:#1b6e3c;border-color:#1b6e3c;}
.roster-planning--red{color:#b3261e;border-color:#b3261e;}
.roster-planning--darkred{color:#721c18;border-color:#721c18;background:#fff4f3;font-weight:800;}
.roster-paid-light{display:block;width:1.75rem;height:1.75rem;overflow:visible;}
.roster-paid-light path{fill:none;stroke:#fff;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
.roster-paid-light .roster-paid-light-shape{fill:currentColor;stroke:none;}
.roster-paid-light--green{color:#1b6e3c;}
.roster-paid-light--red{color:#b3261e;}
.roster-compliance{font-weight:700;}
.roster-compliance--high{color:#1b6e3c;}
.roster-compliance--mid{color:#8a6d0b;}
.roster-compliance--low{color:#b3261e;}
.roster-compliance--na{color:#999;}
.roster-compliance-detail{margin-left:.4em;color:#777;font-weight:400;}
.roster-compliance-muted{font-size:.82em;color:#777;}
.roster-compliance-muted .roster-compliance{font-weight:600;}
.roster-empty,.roster-no-match{color:#666;padding:var(--space-3) 0;}
.progression-head,.progression-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-2);align-items:start;}
.progression-head{margin-top:var(--space-2);padding:.4rem;background:#f4f4f4;}
.progression-row{padding:.5rem 0;border-bottom:1px solid #ddd;}
.progression-readonly{padding:.45rem;overflow-wrap:anywhere;}
.progression-readonly p{margin:.35rem 0 0;}
/* Narrow screens: stack each row into label/value pairs via the data-th attribute. */
@media (max-width:640px){
  .progression-head,.progression-row{grid-template-columns:1fr;}
  .roster-table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);}
  .roster-table,.roster-table tbody,.roster-row,.roster-row td{display:block;width:100%;}
  .roster-row{margin-bottom:var(--space-2);border:1px solid #eee;border-radius:8px;padding:.4rem .6rem;}
  .roster-row td{border:0;padding:.3rem 0;display:flex;justify-content:space-between;gap:var(--space-3);}
  .roster-row td::before{content:attr(data-th);font-size:.75em;text-transform:uppercase;letter-spacing:.03em;color:#888;font-weight:600;}
}

/* PTM-153: the one disclosure primitive used by portal and admin surfaces. */
.disclosure {
  display: block;
  margin-bottom: var(--space-4);
  padding: var(--space-3);
  border: 1px solid #e5e5e5;
  border-radius: 8px;
}
.disclosure > summary {
  display: flex;
  min-height: 44px;
  align-items: center;
  /* PTM-196 (PTM-92 MAJOR 5): nowrap (the flex default) forced a multi-node summary
     -- e.g. client_detail.html's week-editor heading (a <strong> + a plain-text run
     + the chevron span, each its own anonymous flex item) -- into fragments that
     broke across 3 stacked lines at 390px instead of wrapping as running text. Let
     it wrap; single-line summaries (every other disclosure caller) are unaffected. */
  flex-wrap: wrap;
  gap: var(--space-2);
  cursor: pointer;
  font-weight: 700;
}
.disclosure > summary:focus-visible {
  outline: 3px solid var(--brand-primary);
  outline-offset: var(--space-1);
}
.disclosure[open] > summary { margin-bottom: var(--space-3); }
.disclosure-chevron {
  width: .65rem;
  height: .65rem;
  margin-left: auto;
  flex: 0 0 auto;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform .15s ease;
}
.disclosure[open] > summary .disclosure-chevron { transform: rotate(225deg); }
.disclosure > summary h2,
.disclosure > summary h3,
.disclosure > summary p { margin: 0; }

/* PTM-166: exercises admin -- collapsible muscle-group tree + one-line muscle rows. */
.exercise-muscle-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-1);
}
.exercise-muscle-row label {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin-bottom: 0;
  font-weight: 400;
}
.exercise-row-name { flex: 1 1 auto; }
/* PTM-48 phase 2: measured hit area was 23x34 -- min-width/min-height (padding-
   driven, no font-size change) bring the pencil to a >=44px effective tap
   target without growing the visible glyph. */
.exercise-row-pencil {
  flex: 0 0 auto;
  font-size: 1.1em;
  text-decoration: none;
  padding: .25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  box-sizing: border-box;
}
.disclosure.exercise-row > summary { font-weight: 400; }
.disclosure.muscle-group { margin-bottom: var(--space-2); }
/* PTM-170: row-click opens a read-only view panel; pencil is a real link into edit mode. */
.exercise-view { display: block; }
.exercise-view-field { margin: 0 0 var(--space-2); }
.exercise-view-label { font-weight: 700; margin-right: var(--space-1); }
.exercise-view-tag { display: inline-block; margin-right: var(--space-2); }
.exercise-cancel-row { margin-top: var(--space-2); }
/* PTM-189: metrics-definitions admin -- same PTM-170 row-click-view / pencil-edit split,
   copied faithfully from the .exercise-row / .exercise-view rules above. */
.metric-row-name { flex: 1 1 auto; }
/* PTM-48 phase 2: same >=44px tap-target treatment as .exercise-row-pencil above. */
.metric-row-pencil {
  flex: 0 0 auto;
  font-size: 1.1em;
  text-decoration: none;
  padding: .25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  box-sizing: border-box;
}
.disclosure.metric-row > summary { font-weight: 400; }
.metric-view { display: block; }
.metric-view-field { margin: 0 0 var(--space-2); }
.metric-view-label { font-weight: 700; margin-right: var(--space-1); }
.metric-cancel-row { margin-top: var(--space-2); }
/* PTM-48 phase 2: the bare Cancel <a> was under 44px tall; inline-flex + min-height
   grows only the invisible hit area, not the text. */
.metric-cancel-row a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
/* PTM-48 phase 2: client_detail.html's "Client metrics" link -- same >=44px
   tap-target treatment, no visual size change to the text itself. */
.client-metrics-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.eoc-chart-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr));gap:1rem}
.eoc-chart-grid svg{display:block;width:100%;height:auto;overflow:visible}
.eoc-chart-grid figure{margin:0;min-width:0}
.eoc-chart-grid figcaption{font-weight:700;margin-bottom:.5rem}
.eoc-stats{margin:1.5rem 0}
.eoc-stats table{width:100%;border-collapse:collapse}
.eoc-stats th,.eoc-stats td{text-align:left;padding:.5rem;border-bottom:1px solid currentColor}
.table-scroll{overflow-x:auto}

/* PTM-68: the subscription grace banner and the LOCKED paywall.

   The banner sits in the same document slot as .flash and takes the same desktop
   sidebar offset (see the `body:has(.app-nav) > .flash` rule above, which this joins),
   so it lands in the identical place on every page instead of colliding with the fixed
   260px sidebar. It has NO dismiss control on purpose: .flash is a one-shot
   confirmation, this is an ongoing condition that must stay visible until it is
   actually resolved. Amber, kept distinct from .flash-error's red so red stays
   reserved for the lockout itself; the wording carries the meaning on its own, because
   colour alone fails for a colour-blind reader. */
.subscription-banner {
  margin: var(--space-3) auto 0;
  padding: var(--space-3);
  max-width: 900px;
  border-radius: 8px;
  border-left: var(--space-1) solid currentColor;
  color: #7a4b00;
  background: #fdf1dc;
  font-weight: 700;
}

@media (min-width: 1024px) {
  body:has(.app-nav) > .subscription-banner { margin-left: 260px; }
}

/* The paywall replaces the whole document, so it needs no sidebar/bottom-nav dodging:
   one centred, width-capped column that reads the same on a 390px phone and a desktop. */
.paywall {
  max-width: 34rem;
  margin: 0 auto;
  padding: var(--space-5) var(--space-3) var(--space-6);
}

.paywall-card {
  padding: var(--space-4);
  border-radius: 12px;
  border: 1px solid rgba(0, 0, 0, .12);
  background: #fff;
}

.paywall-card h1 { margin-top: 0; }
.paywall-card textarea { width: 100%; }
.paywall-exit { margin-top: var(--space-4); text-align: center; }
/* Unlike base.html, the standalone paywall has no desktop sidebar exit to take over. */
.paywall-account-exit { display: inline-flex; }

/* PTM-196 (PTM-92 BLOCKER 6): the settings-page language control -- always visible
   (unlike .nav-account, hidden below 1024px), icon-paired per the ruling's mitigation
   so the affordance reads without depending on the very language it switches. */
.profile-language {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}

/* ============================================================
   PTM-200 — coach<->client messaging
   ============================================================

   Nothing below invents a new visual language. The inbox is .roster-table wearing a
   different column set, the unread pill is .roster-tab-count's shape, the broadcast
   banner is .subscription-banner's amber, the read report is .disclosure, and the
   compose button is .button. app/static/app.css is the authority on consistency, so
   this section's job is to add three things that genuinely did not exist -- a message
   bubble, an unread dot, and a confirm screen -- and to reuse everything else. */

/* --- unread signals -------------------------------------------------------
   Two treatments on purpose, not an inconsistency: the DOT answers "is there anything
   new" (on the tab that gates entry, where a number would need the tap target to grow),
   the PILL answers "how much" (on the entry that names the page). Both are paired with
   text -- a visually-hidden line for the dot, the digits for the pill -- because colour
   and shape alone fail color-not-only. */
.msg-unread-dot {
  display: inline-block;
  width: .55rem;
  height: .55rem;
  margin-right: .4em;
  border-radius: 50%;
  background: var(--brand-primary);
  vertical-align: middle;
}

.nav-badge-count {
  display: inline-block;
  min-width: 1.4em;
  padding: 0 .35em;
  margin-left: .25em;
  border-radius: 999px;
  background: var(--brand-primary);
  color: #fff;
  font-size: .8em;
  font-weight: 600;
  text-align: center;
}

/* The bottom-tab presence dot. Absolutely positioned INSIDE the <a>, so it does not
   participate in .nav-primary's grid (PTM-48's repeat(3, minmax(0,1fr)) is untouched)
   and does not shrink the 64px tap target. aria-hidden by construction -- it is a
   ::after with no content -- and the <a> carries a visually-hidden sentence instead. */
.nav-primary > a.nav-has-unread {
  position: relative;
}
.nav-primary > a.nav-has-unread::after {
  content: "";
  position: absolute;
  top: 10px;
  left: 50%;
  margin-left: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--brand-primary);
}
@media (min-width: 1024px) {
  /* Sidebar layout: the row is left-aligned and icon-led, so the dot rides the icon
     rather than the centre line. */
  .nav-primary > a.nav-has-unread::after {
    top: 8px;
    left: 26px;
    margin-left: 0;
  }
}

/* --- coach inbox ---------------------------------------------------------- */
.msg-inbox-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-3) 0;
}
.msg-inbox-secondary { min-height: 44px; display: inline-flex; align-items: center; }
.roster-row--unread td a { font-weight: 800; }
/* Dot + name + count are one signal; keep them on one line together, and let the email
   (which has no natural break opportunity) wrap inside its own box rather than pushing
   the count off the row. */
.msg-client-cell {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .35em;
  min-width: 0;
  max-width: 100%;
}
.msg-client-cell a { overflow-wrap: anywhere; }
.msg-count { background: var(--brand-primary); color: #fff; }
/* One-line preview at desktop; the narrow-screen .roster-row collapse turns every cell
   into a label/value row, and a clamped preview there would fight the label. */
.msg-preview { color: #444; max-width: 32rem; overflow-wrap: anywhere; }
.msg-none { color: #888; }
.msg-when { color: #666; font-size: .85em; white-space: nowrap; }
.msg-tag {
  display: inline-block;
  white-space: nowrap;
  padding: 0 .45em;
  border: 1px solid #bbb;
  border-radius: 999px;
  color: #555;
  font-size: .75em;
  font-weight: 600;
}
.msg-tag--broadcast { color: #7a4b00; border-color: #c79a3d; background: #fdf1dc; }

/* --- thread ---------------------------------------------------------------
   Einh ruling 7's three colours. The classes encode OWN vs OTHER rather than coach vs
   client, because the ruling mirrors: each viewer's own messages render plain and the
   other party's render branded, so "coach = brand" is true from the CLIENT's side and
   the coach sees the mirror image. Broadcast beats both -- amber sits outside the pair
   entirely, so an announcement can never read as a personal reply, and it matches the
   grace-banner precedent this product already set for the announcement layer. */
.msg-page { max-width: 46rem; }
/* The coach thread's <h1> is a CLIENT'S EMAIL ADDRESS, which has no break opportunity a
   browser will take on its own -- at 390px the default h1 clamp pushed 160px of page off
   the right edge (measured, not guessed). Cap the size and let it break anywhere. */
.msg-page > h1 {
  font-size: clamp(1.5rem, 4.5vw, 2.25rem);
  overflow-wrap: anywhere;
}
.msg-back { margin-bottom: var(--space-2); }
.msg-back a, .msg-page-links a { min-height: 44px; display: inline-flex; align-items: center; }
.msg-thread {
  display: grid;
  gap: var(--space-3);
  margin: var(--space-4) 0;
  padding: 0;
  list-style: none;
}
.msg {
  max-width: 85%;
  padding: var(--space-3);
  border: 1px solid transparent;
  border-radius: 12px;
}
.msg-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: space-between;
  margin: 0 0 var(--space-1);
  font-size: .8em;
}
.msg-sender { font-weight: 700; }
.msg-body { margin: 0; overflow-wrap: anywhere; }
/* OWN: plain. White background, black text -- deliberately the quietest of the three,
   because a viewer does not need their own words highlighted back at them. */
.msg--own {
  margin-left: auto;
  background: #fff;
  border-color: #ddd;
  color: #17191c;
}
.msg--own .msg-when { color: #666; }
/* OTHER PARTY: brand colours. */
.msg--other {
  margin-right: auto;
  background: var(--brand-primary);
  color: #fff;
}
.msg--other a { color: #fff; }
.msg--other .msg-when { color: rgba(255, 255, 255, .82); }
/* BROADCAST: amber, black text, full width -- it is addressed to everyone, so it is not
   aligned to either side of the conversation. */
.msg--broadcast {
  margin: 0;
  max-width: 100%;
  background: #fdf1dc;
  border-color: #c79a3d;
  color: #17191c;
}
.msg--broadcast .msg-when { color: #7a4b00; }
.msg-empty { color: #666; }

.msg-composer { margin-top: var(--space-4); }
.msg-composer textarea { width: 100%; }
.msg-composer-actions { display: flex; justify-content: flex-end; }
.msg-error {
  margin: var(--space-2) 0;
  padding: var(--space-2) var(--space-3);
  border-radius: 8px;
  color: #8a1f1f;
  background: #fbeaea;
  font-weight: 600;
}

/* --- broadcast composer + confirm screen ---------------------------------- */
.msg-audience {
  padding: var(--space-3);
  border-left: var(--space-1) solid var(--brand-primary);
  background: #fff;
}
.msg-confirm-warning {
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border-radius: 8px;
  border-left: var(--space-1) solid currentColor;
  color: #7a4b00;
  background: #fdf1dc;
  font-weight: 700;
}
.msg-confirm-preview {
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border: 1px solid #ddd;
  border-radius: 8px;
  background: #fff;
}
.msg-confirm-form input[type="text"] {
  width: 100%;
  max-width: 18rem;
  min-height: 44px;
  padding: .5rem .75rem;
  border: 1px solid #999;
  border-radius: 8px;
  /* Tabular figures are wrong for a word, but the letter-spacing is right: the field
     asks for one specific string, so give each character room to be checked. */
  letter-spacing: .08em;
  text-transform: uppercase;
}
.msg-confirm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  margin-top: var(--space-4);
}
/* Disabled must READ as disabled -- reduced emphasis plus a cursor change, never a
   button that merely does nothing when pressed. */
.msg-send-broadcast[disabled] { opacity: .45; cursor: not-allowed; }
.msg-broadcast-list { display: grid; gap: var(--space-3); padding: 0; list-style: none; }
.msg-broadcast-item {
  padding: var(--space-3);
  border: 1px solid #e5e5e5;
  border-radius: 8px;
  background: #fff;
}
.msg-broadcast-counts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-2) 0 0;
}
.msg-broadcast-counts a { min-height: 44px; display: inline-flex; align-items: center; }
.msg-report-list { margin: 0; padding-left: var(--space-4); }
.msg-report-list li { padding: .25rem 0; }

/* --- client broadcast banner ---------------------------------------------
   Styled after .subscription-banner (Einh ruling 6 asked for exactly that), with ONE
   difference that matters: this one HAS a dismiss control. The subscription banner is
   an ongoing condition that must stay until it resolves; this is an announcement the
   client is allowed to put down. Dismissing hides the banner and leaves the message
   unread in the thread -- see app/messaging.py::dismiss_banner. */
.broadcast-banner {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-3) auto 0;
  padding: var(--space-3);
  max-width: 900px;
  border-radius: 8px;
  border-left: var(--space-1) solid currentColor;
  color: #7a4b00;
  background: #fdf1dc;
}
.broadcast-banner-text { display: grid; gap: var(--space-1); min-width: 0; }
.broadcast-banner-text a { color: #7a4b00; }
.broadcast-banner form { margin: 0; }
.broadcast-banner-dismiss {
  min-width: 44px;
  min-height: 44px;
  padding: 0 var(--space-2);
  border: 0;
  background: none;
  color: inherit;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}
@media (min-width: 1024px) {
  body:has(.app-nav) > .broadcast-banner { margin-left: 260px; }
}

/* --- client Messages card (the exact count lives here, not on the tab) ----- */
.messages-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-height: 44px;
  margin: var(--space-3) 0;
  padding: var(--space-3);
  border: 1px solid #e5e5e5;
  border-radius: 12px;
  background: #fff;
  color: #17191c;
  text-decoration: none;
}
.messages-card--unread { border-color: var(--brand-primary); }
.messages-card-title { font-weight: 700; }
.messages-card-state { color: #555; font-size: .9em; }
.messages-card--unread .messages-card-state { color: var(--brand-primary); font-weight: 700; }

/* Coach skill chat is deliberately modest; the warning is the strongest visual element. */
.skill-chat-warning {
  margin-bottom: var(--space-4); padding: var(--space-4); border: 3px solid #8a4b00;
  border-radius: 8px; background: #fff0cf; color: #572f00; font-size: 1.1rem;
}
.skill-chat-thread { display: grid; gap: var(--space-3); margin: var(--space-4) 0; }
.skill-chat-message { max-width: 85%; padding: var(--space-3); border-radius: 10px; background: #f1f1f1; }
.skill-chat-message--coach { justify-self: end; background: var(--brand-primary); color: #fff; }
.skill-chat-message--assistant { justify-self: start; border: 1px solid #ddd; background: #fff; }
.skill-chat-message--error { color: #8b1a1a; background: #fde8e8; }
.skill-chat-message button { display: block; margin-top: var(--space-2); }
.skill-chat-message small { display: block; margin-top: var(--space-2); color: #555; }

@media (max-width: 640px) {
  /* At 390px an 85% bubble plus a 15% gutter still leaves a readable measure, and the
     alignment (which is the viewer cue) survives. Broadcasts stay full width. */
  .msg { max-width: 92%; }
  .msg--broadcast { max-width: 100%; }
  .msg-confirm-actions > .button { flex: 1 1 100%; justify-content: center; }

  /* The inbox's narrow-screen collapse needs MORE than .roster-table's generic one.
     That rule turns each cell into a label-left / value-right flex row, which is right
     for the client roster's short values (a percentage, a date badge) and wrong here:
     the values are an email address and a sentence, so the label was squeezed into
     "LAST MES SAG E" down one side while the row still overflowed the viewport by 60px
     (measured at 390). Stack label above value instead, and let the long ones wrap. */
  .msg-inbox-table .roster-row td {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
  }
  .msg-inbox-table .roster-row td::before { white-space: nowrap; }
  .msg-inbox-table .roster-row a,
  .msg-inbox-table .msg-preview { overflow-wrap: anywhere; max-width: 100%; }
}
