/* ABOUTME: Shared "Editorial Native" design language — the single source of UI primitives. */

/* ABOUTME: Every member page renders these (.ps-page/.ph/.ps-card/.ps-list/.ps-row/...) for one cohesive look. */

/* ===== Page shell (the canvas every converted page sits on) ===== */
.ps-page {
  max-width: 480px;
  margin: 0 auto;
  min-height: 100vh;

  /* Glow retuned with the Sharpened Cream ground so it stays a subtle warm
     lift, not a pale patch on the deeper canvas. */
  background: radial-gradient(120% 70% at 85% 36%, var(--page-glow) 0, var(--page-glow-fade) 52%), var(--cream);
  color: var(--ink);
  font-family: var(--font-ui, -apple-system, system-ui, sans-serif);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizelegibility;
  padding: 0 16px calc(28px + env(safe-area-inset-bottom, 0px));
}

/* Button-style anchors keep their component colors; other links inherit the
   editorial page ink unless a more specific component says otherwise. */
.ps-page a:not(.ps-button, .sk-cta) { color: inherit; text-decoration: none; }
.ps-page a:is(.ps-button, .sk-cta) { text-decoration: none; }

/* The link reset above out-specifies .ps-empty-cta (0,2,1 vs 0,1,0) — without this the
   empty-state CTA would lose its own label color to the inherit. */
.ps-page a.ps-empty-cta { color: var(--btn-primary-ink); }

/* Form-button rows in grouped-inset lists (e.g. Connect Google = a real form POST).
   The form participates in the list via display:contents; the button resets to row chrome. */
.ps-row-form { display: contents; }
button.ps-row--button { width: 100%; background: none; border: 0; text-align: left; font: inherit; cursor: pointer; }
.ps-list .ps-row-form + .ps-row, .ps-list .ps-row + .ps-row-form > .ps-row { border-top: 1px solid var(--rule); }

/* No global top nav: main's padding-top clears only the status bar / notch,
   and .ps-page provides its side gutters. */

/* ===== Large-title header ===== */
.ph { display: flex; align-items: flex-start; gap: 12px; padding: 18px 2px 14px; }

.ph-back { width: 40px; height: 40px; border-radius: 50%; flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  background: var(--paper); border: 1px solid var(--rule); box-shadow: var(--specular), var(--e1); color: var(--ink); font-size: 16px; margin-top: 0; }
.ph--with-eyebrow .ph-back { margin-top: 20px; }
.ph-id { flex: 1; min-width: 0; }
.ph-eyebrow { display: block; font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--accent-ink); margin-bottom: 3px; }
.ph-title { font-family: var(--font-display, Georgia, serif); font-variant-ligatures: no-common-ligatures; font-weight: 600; font-size: 34px; letter-spacing: -.025em; line-height: 1.04; color: var(--ink); }
.ph-sub { display: block; margin-top: 4px; font-size: 13px; font-weight: 500; color: var(--ink-soft); }
.ph-sub-link { color: var(--accent-ink); font-weight: 700; white-space: nowrap; }
.ph-actions { display: flex; align-items: center; gap: 10px; margin-top: 2px; }

.ph-action { width: 44px; height: 44px; border-radius: 50%; flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  background: var(--paper); border: 1px solid var(--rule); box-shadow: var(--specular), var(--e1); color: var(--ink); font-size: 17px; position: relative; }
.ph-action.is-disabled { opacity: .35; pointer-events: none; }

/* Short intro lede — one line of context under a header or subnav band. On
   subnav'd pages (no big title, action-only toolbar) it also gives the top some
   breathing room so content doesn't butt against the fixed pill row. */
.ps-lede { margin: 4px 4px 16px; font-size: 14px; font-weight: 500; line-height: 1.5; color: var(--ink-soft); }

/* ===== Section label ===== */

/* This margin IS the page's vertical rhythm: cards and lists carry no bottom
   margin of their own, so the next section's label is what separates them. */
.ps-section-label-row { display: flex; align-items: baseline; justify-content: space-between; margin: 22px 4px 10px; }
.ps-section-label { font-size: 12px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-faint); }

/* A card that follows another card or list with NO section label between them
   has nothing supplying that gap and lands flush against its neighbor. This
   gives it one - the card-to-card gap, not the section gap. */
.ps-gap-above { margin-top: 12px; }

/* Today links into the existing Profile device setting. Leave breathing room
   above the anchored section under the native/web section navigation. */
.profile-health-devices { scroll-margin-top: 96px; }
.ps-section-trailing { font-size: 14px; font-weight: 700; color: var(--accent-ink); }

button.ps-section-trailing {
  appearance: none;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  cursor: pointer;
}

/* ===== Card primitive ===== */
.ps-card { background: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius-tile); box-shadow: var(--specular), var(--e1); padding: 16px; }
.ps-card--feature { box-shadow: var(--specular), var(--e2); }

/* ===== Grouped-inset list ===== */
.ps-list { background: var(--paper); border: 1px solid var(--rule); border-radius: var(--radius-tile); box-shadow: var(--specular), var(--e1); overflow: hidden; }
.ps-row { display: flex; align-items: center; gap: 13px; padding: 14px 16px; min-height: 60px; color: var(--ink); }
.ps-row + .ps-row { border-top: 1px solid var(--rule); }
.ps-row-ic { width: 38px; height: 38px; border-radius: 12px; flex: 0 0 auto; display: flex; align-items: center; justify-content: center; font-size: 17px; background: var(--paper-2); color: var(--orange-600); }
.ps-row-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ps-row-title { font-size: 16px; font-weight: 600; letter-spacing: -.01em; }
.ps-row-meta { font-size: 13px; color: var(--ink-soft); }

/* Quieter second line under the meta - who to ask, or why a row can't be edited */
.ps-row-hint { display: block; font-size: 12px; line-height: 1.4; color: var(--ink-faint); margin-top: 2px; }
.ps-row-value { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink); flex: 0 0 auto; }

/* A .ps-row-value that needs flagging - same figure, same slot, same size, and
   only the ink changes. Reach for .ps-row-badge instead when the thing on the
   right is a CATEGORY (a status, a reason); a pill around a measurement makes
   it read as a different measurement from the unpilled ones above it. */
.ps-row-value--alert { color: var(--accent-ink); }
.ps-row-badge { font-size: 12px; font-weight: 700; color: var(--accent-ink); background: var(--accent-wash); padding: 3px 9px; border-radius: 999px; flex: 0 0 auto; }
.ps-row-chev { color: var(--ink-faint); font-size: 15px; flex: 0 0 auto; }
a.ps-row:active { background: var(--paper-2); }

/* ===== Editable list row (Settings): trailing control saves on edit =====
   Text rows keep a real inline input. Select/date rows show a formatted
   value instead of the raw native control - the control becomes a full-row
   invisible overlay so tapping anywhere opens the native picker (iOS wheel /
   date sheet) while the row displays tidy, one-line text. */
.ps-row--field { flex-wrap: wrap; min-height: 52px; position: relative; }

/* flex-basis 0 (not auto): a text input's auto basis is its ~20ch intrinsic
   width regardless of value, which — under this row's flex-wrap — inflates the
   line and wraps the status slot to a second row on longer-titled fields (e.g.
   Nickname), padding the row's height and shifting the value's right edge. A 0
   basis lets the control grow into free space instead, so every field row keeps
   one line and its value aligns to the same right edge as select/date rows. */
.ps-row-control { flex: 1 1 0; display: flex; align-items: center; gap: 6px; min-width: 0; max-width: 64%; }

/* One wash per group, not one per row - see ps-row--field usages in profiles/show. */
.ps-row-ic--accent { background: var(--accent-wash); color: var(--accent-ink); }
.ps-row-ic--blue { background: var(--blue-wash); color: var(--steel); }
.ps-row--field .ps-row-ic { width: 28px; height: 28px; border-radius: 8px; font-size: 13px; }

.ps-row--field .ps-row-title { flex: 1 1 auto; min-width: 0; font-size: 15.5px; font-weight: 600; letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.ps-row--field .ps-row-value { flex: 0 1 auto; max-width: 170px; font-size: 15px; font-weight: 500; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ps-row--field .ps-row-value.is-muted { color: var(--ink-faint); font-weight: 400; }

/* Text rows: the input IS the row's value display, so at rest it must be
   indistinguishable from .ps-row-value (same size/weight/ink, same right
   edge - the negative right margin cancels the input's own 4px text inset).
   While editing it darkens to full ink for legibility. */
.ps-row-input { font: inherit; font-size: 15px; font-weight: 500; color: var(--ink-soft); background: none; border: 0;
  text-align: right; padding: 6px 4px; margin: -6px -4px -6px 0; border-radius: 9px; min-width: 0; width: 100%;
  appearance: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ps-row-input::placeholder { color: var(--ink-faint); font-weight: 400; }
.ps-row-input:focus { color: var(--ink); }

.ps-row-input:focus-visible { outline: none; background: var(--paper-2); box-shadow: 0 0 0 2px var(--orange); }

/* The native select/date control: same footprint as the row, fully transparent.
   It still receives the tap/click and opens the OS picker; the formatted
   ps-row-value span is what's actually visible. */
.ps-row-overlay { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0;
  background: none; opacity: 0; appearance: none; cursor: pointer; }

/* Keep the shared form at its natural height inside Sheet Kit's flex scroller;
   otherwise each grouped card shrinks and clips all but its first rows. */
.exercise-settings-fields { flex: 0 0 auto; }

/* Create forms use the same settings rows, but keep their native select visible
   so the field is both obviously editable and discoverable by assistive tech. */
.ps-row--field:has(.ps-row-select) .ps-row-title { flex: 1 1 0; }

.ps-row-select { flex: 0 1 44%; width: auto; max-width: 44%; min-width: 0; padding: 6px 28px 6px 9px;
  border: 1px solid var(--rule);
  border-radius: 9px; background: var(--paper-2); color: var(--ink); font: inherit; font-size: 15px; cursor: pointer; }
.ps-row-select:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--orange); }

/* opacity: 0 hides the control but it still receives focus, so the keyboard
   ring has to paint on the row - :has() reaches up from the overlay's own
   :focus-visible state to its row ancestor. .ps-list clips to its own
   rounded corners, so the first/last row's ring needs the same radius or
   it gets clipped square against the curve. */
.ps-row--field:has(.ps-row-overlay:focus-visible) { outline: none; background: var(--paper-2); box-shadow: inset 0 0 0 2px var(--orange); }
.ps-row--field:first-child:has(.ps-row-overlay:focus-visible) { border-radius: var(--radius-tile) var(--radius-tile) 0 0; }
.ps-row--field:last-child:has(.ps-row-overlay:focus-visible) { border-radius: 0 0 var(--radius-tile) var(--radius-tile); }
.ps-row--field:only-child:has(.ps-row-overlay:focus-visible) { border-radius: var(--radius-tile); }

/* Fixed-width status slot so saving/saved/error never shifts the row. */
.ps-field-status { flex: 0 0 auto; width: 16px; text-align: center; font-size: 13px; opacity: 0; transition: opacity .2s var(--ease-out); }
.ps-field-status[data-state="saving"] { opacity: .6; color: var(--ink-faint); }
.ps-field-status[data-state="saved"] { opacity: 1; color: var(--success-600); }
.ps-field-status[data-state="error"] { opacity: 1; color: var(--error-600); }
.ps-field-error { display: none; flex-basis: 100%; margin: 4px 0 0 41px; font-size: 13px; color: var(--error-600); }
.ps-row--field.has-error .ps-field-error { display: block; }

.ps-row--field.has-error .ps-row-input,
.ps-row--field.has-error .ps-row-value { color: var(--error-600); }

/* Checkbox rows: a right-aligned native check, sized as a comfortable touch target. */
.ps-row-checkbox { flex: 0 0 auto; width: 22px; height: 22px; accent-color: var(--orange); cursor: pointer; }
.ps-row-checkbox:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--orange); border-radius: 6px; }

/* Textarea + multi-select rows: the control is too tall for one line, so it
   breaks to its own full-width line beneath the label. */
.ps-row--field-textarea, .ps-row--field-multiselect { align-items: flex-start; }

.ps-row-textarea, .ps-row-multiselect { flex: 0 0 100%; width: 100%; margin-top: 10px; font: inherit; font-size: 15px;
  color: var(--ink); background: var(--paper-2); border: 1px solid var(--rule); border-radius: 10px; padding: 9px 11px;
  appearance: none; }
.ps-row-textarea { min-height: 84px; resize: vertical; line-height: 1.45; }
.ps-row-multiselect { min-height: 132px; }
.ps-row-textarea:focus-visible, .ps-row-multiselect:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--orange); }

/* Exercise instructional-video block: responsive 16:9 player, or a
   non-clickable error when the URL is not a recognized video. */
.ex-video { margin-top: 12px; }
.ex-video-heading { margin: 0 0 8px; font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-faint); }
.ex-video-frame { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: 10px; background: var(--paper-2); }
.ex-video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.ex-video-error { display: flex; align-items: center; gap: 8px; margin: 0; padding: 11px 13px; border-radius: 10px;
  background: var(--error-50); color: var(--error-600); font-size: 13px; font-weight: 500; }

/* Long free-text rows (alternate names, video URL) break onto their own
   full-width line so the value/placeholder isn't squeezed and truncated. */
.ps-row--field-full { align-items: flex-start; }
.ps-row--field-full .ps-row-control { flex: 0 0 100%; max-width: 100%; margin-top: 10px; }

.ps-row--field-full .ps-row-input { text-align: left; margin: 0; padding: 9px 11px; background: var(--paper-2);
  border: 1px solid var(--rule); border-radius: 10px; color: var(--ink); }
.ps-row--field-full .ps-row-input:focus-visible { box-shadow: 0 0 0 2px var(--orange); }

/* ===== Empty state ===== */
.ps-empty { text-align: center; padding: 26px 18px; display: flex; flex-direction: column; align-items: center; gap: 10px; }
.ps-empty-ic { width: 52px; height: 52px; border-radius: 16px; display: flex; align-items: center; justify-content: center; font-size: 24px; background: var(--paper-2); }
.ps-empty-title { font-size: 16px; font-weight: 700; color: var(--ink); }
.ps-empty-body { font-size: 14px; color: var(--ink-soft); max-width: 30ch; }

.profile-fact-empty-action { cursor: pointer; border-radius: var(--radius-tile); }

/* ===== Working toward: the directions a member is chasing =====
   No tick and no green. A check mark says a thing is DONE, and sage is this
   app's color for done everywhere else - on "Lose fat" that reads as a claim
   about the member's body rather than a goal they hold. */
.profile-goals { display: flex; flex-wrap: wrap; gap: 8px; }

.profile-goal { padding: 7px 14px; border-radius: 999px; background: var(--paper-2);
  border: 1px solid var(--hairline); color: var(--ink); font-size: 15px; font-weight: 600; }
.profile-goal--custom { background: var(--accent-wash); border-color: transparent; }

.ps-empty-cta { display: inline-flex; align-items: center; gap: 7px; min-height: 44px; padding: 0 18px; border-radius: 14px;
  background: var(--btn-primary-bg); color: var(--btn-primary-ink); border: 1.5px solid var(--btn-primary-border); font-weight: 800; font-size: 15px; }
.ps-empty-cta svg { color: var(--btn-primary-icon); stroke-width: 3; }

/* ===== Page buttons =====
   One visual family for page-level form actions and confirmations. Interaction
   behavior (press, focus, tap highlight) comes from button_interactions.css. */
.ps-button {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 18px;
  border: 1px solid var(--rule);
  border-radius: 14px;
  background: var(--paper);
  box-shadow: var(--specular), var(--e1);
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  line-height: 1.1;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}

.ps-button--primary {
  background: var(--btn-primary-bg);
  border: 1.5px solid var(--btn-primary-border);
  box-shadow: none;
  color: var(--btn-primary-ink);
  font-weight: 800;
}

.ps-button--danger {
  background: var(--error-600);
  border-color: transparent;
  box-shadow: 0 3px 10px rgb(220 38 38 / 24%);
  color: #fff;
}

a.ps-button--primary { color: var(--btn-primary-ink); }
a.ps-button--danger { color: #fff; }

.ps-button--sm { min-height: 36px; padding-inline: 13px; border-radius: 11px; font-size: 13px; }
.ps-button--block { width: 100%; }
.ps-button-row { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 24px; }
.ps-button-row--end { justify-content: flex-end; }

.ps-button:disabled,
.ps-button[disabled],
.ps-button[aria-disabled="true"] {
  opacity: .5;
  cursor: not-allowed;
}

@media (hover: hover) {
  .ps-button:hover { background: var(--paper-2); box-shadow: var(--specular), var(--e2); }
  .ps-button--primary:hover { background: var(--btn-primary-bg-hover); box-shadow: none; }
  .ps-button--danger:hover { background: var(--error-700); box-shadow: 0 3px 10px rgb(185 28 28 / 28%); }
}

.ps-button--primary:active:not(:disabled) { background: var(--btn-primary-bg-active); }
.ps-button--danger:active:not(:disabled) { background: var(--error-700); }

/* ===== Segmented control (e.g. Train: Schedule | Library) ===== */
.ps-segmented { position: relative; display: flex; gap: 4px; padding: 4px; background: var(--paper-2); border: 1px solid var(--rule); border-radius: 15px; }
.ps-seg { position: relative; z-index: 1; flex: 1; text-align: center; min-height: 40px; display: flex; align-items: center; justify-content: center; gap: 7px; font-size: 15px; font-weight: 600; color: var(--ink-soft); border-radius: 11px; }
.ps-seg.is-active { background: var(--paper); color: var(--ink); box-shadow: var(--specular), var(--e1); }

/* Sliding thumb (seg-slide controller): one shared paper pill positioned under
   the active tab, sliding between tabs on navigation like the check-in tracks.
   Until the controller connects (or without JS) the active tab keeps painting
   its own paper via .is-active above. */
.seg-thumb { position: absolute; top: 4px; bottom: 4px; left: 0; width: 0; border-radius: 11px; background: var(--paper); box-shadow: var(--specular), var(--e1); pointer-events: none; }
.seg-thumb.is-sliding { transition: transform 0.22s ease, width 0.22s ease; }
.has-seg-thumb .ps-seg.is-active { background: transparent; box-shadow: none; }

@media (prefers-reduced-motion: reduce) {
  .seg-thumb.is-sliding { transition: none; }
}

/* ===== Desktop: the editorial column breathes, the large title scales up =====
   Phone-first metrics above are untouched; this only relaxes the canvas where
   there's room. The global top nav remains the chrome on every viewport. */
@media (width >= 769px) {
  .ps-page { max-width: 640px; padding-left: 24px; padding-right: 24px; }

  /* The warm glow is ambient screen light, tuned for phones where .ps-page IS
     the viewport. On desktop the page is a centered column, so a glow painted
     on it stops dead at the column's right edge (the gradient is centered at
     85% x, still near full strength there) and reads as a mis-colored band.
     Paint it on the canvas instead, where it has no visible edge. */
  .ps-page { background: none; }

  body:has(.ps-page) {
    background-image: radial-gradient(120% 70% at 85% 36%, var(--page-glow) 0, var(--page-glow-fade) 52%);
    background-attachment: fixed;
  }
  .ph { padding-top: 30px; }
  .ph-title { font-size: 40px; }
  .ph--with-eyebrow .ph-back { margin-top: 23px; }
}
.ph-title { text-wrap: balance; }

/* ===== Pointer affordances — :active covers touch; hover is for desktop ===== */
a.ps-row, .ph-back, .ph-action, .ps-empty-cta { transition: background .15s var(--ease-out), box-shadow .15s var(--ease-out); }
a.ps-card { display: block; transition: box-shadow .15s var(--ease-out); }

@media (hover: hover) {
  a.ps-row:hover { background: var(--paper-2); }
  .ph-back:hover, a.ph-action:hover, button.ph-action:hover { background: var(--paper-2); }
  a.ps-card:hover { box-shadow: var(--specular), var(--e2); }
  .ps-card--feature[href]:hover { box-shadow: var(--specular), var(--e3); }
  .ps-empty-cta:hover { background: var(--btn-primary-bg-hover); }
}

/* ===== Focus ring (AA) ===== */
.ps-page a:focus-visible, .ph-action:focus-visible, .ps-empty-cta:focus-visible, .ps-seg:focus-visible,
button.ps-hm-cell:focus-visible {
  outline: 3px solid var(--orange); outline-offset: 2px; border-radius: 12px;
}

/* ===== Progress page specifics (heatmap rebuilt AA-safe: 7×2 grid, ≥44px cells, non-color signal + legend) ===== */
.ps-heatmap { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 8px; }

.ps-hm-cell { aspect-ratio: 1; min-height: 44px; border-radius: 12px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  background: var(--paper); border: 1px solid var(--rule); color: var(--ink-soft); position: relative;
  font: inherit; padding: 0; cursor: pointer; width: 100%; }
span.ps-hm-cell { cursor: default; }
.ps-hm-day { font-size: 11px; font-weight: 700; color: var(--ink-faint); }
.ps-hm-dot { width: 14px; height: 14px; border-radius: 50%; background: var(--rule); }

/* Today has not finished, so it carries no grade — an open ring, not a verdict.
   Grading a partial day on the same scale as complete ones made every morning
   read as a bad day. */
.ps-hm-cell.is-today { box-shadow: 0 0 0 2px var(--orange); }

/* Solid ring, not dashed: at 14px across, a 2px dashed border resolves to four
   stubby arcs that read as a broken glyph rather than an empty dot. The open
   center is what says "not graded yet"; the dashes added nothing. */
.ps-hm-cell.is-today .ps-hm-dot { background: none; border: 2px solid var(--orange); }

/* Grade fills — the class names ARE the member-facing tier names
   (excellent/solid/steady/off), so a view cannot paint a cell one grade while
   the legend calls it another. Warm, and always paired with the day label +
   legend so the reading is never color-only. */
.ps-hm--excellent { background: var(--sage-wash); } .ps-hm--excellent .ps-hm-dot { background: var(--sage); }
.ps-hm--solid     { background: var(--grade-solid-wash); }  .ps-hm--solid .ps-hm-dot     { background: var(--grade-solid); }
.ps-hm--steady    { background: var(--grade-steady-wash); } .ps-hm--steady .ps-hm-dot    { background: var(--grade-steady); }
.ps-hm--off       { background: var(--accent-wash); } .ps-hm--off .ps-hm-dot     { background: var(--orange); }
.ps-hm-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 12px; font-size: 12px; color: var(--ink-soft); }
.ps-hm-legend span { display: inline-flex; align-items: center; gap: 6px; }
.ps-hm-legend i { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }

/* The legend swatch borrows the same grade classes as the cells, but wants the
   DOT color rather than the cell wash — otherwise every swatch reads as a pale
   tint of the same thing. */
.ps-hm-swatch.ps-hm--excellent { background: var(--sage); }
.ps-hm-swatch.ps-hm--solid     { background: var(--grade-solid); }
.ps-hm-swatch.ps-hm--steady    { background: var(--grade-steady); }
.ps-hm-swatch.ps-hm--off       { background: var(--orange); }

/* ===== Range switch (14 days / 3 months / year) ===== */

/* A custom element is inline until told otherwise, and this one wraps the whole
   card body. */
.ps-scorerange { display: block; }

.ps-rangeswitch { position: relative; display: flex; gap: 4px; padding: 3px; margin-bottom: 14px; background: var(--paper-2); border-radius: 999px; }

.ps-range { position: relative; z-index: 1; flex: 1; min-height: 36px; display: flex; align-items: center; justify-content: center; border-radius: 999px;
  font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.ps-range.is-active { background: var(--paper); color: var(--ink); box-shadow: var(--specular), var(--e1); }

/* The shared seg-slide thumb, fitted to this row's tighter padding and full
   round. The switch is frame-backed, so the thumb starts moving on tap and the
   frame swaps the strip underneath it - the tap is acknowledged before the
   response lands. Without JS the active pill keeps painting its own paper. */
.ps-rangeswitch .seg-thumb { top: 3px; bottom: 3px; border-radius: 999px; }
.has-seg-thumb .ps-range.is-active { background: transparent; box-shadow: none; }

/* ===== Long-range stacking strip: one column per week, height = good days =====
   Height AND color carry the value, so the shape survives a colorblind read
   and a greyscale screenshot. */
.ps-weekstrip { display: flex; align-items: flex-end; height: 96px; padding: 0 1px; }

/* No column gap: the separation is PADDING inside each bar, with the fill
   painted only inside the content box. The bars look 2px apart while their hit
   areas stay contiguous, so at the year range - where 52 weeks leave each column
   a few pixels wide - a tap always lands on some week rather than between two.
   Height carries the rest of the target: a full-height column is 96px tall,
   comfortably past 44px in the dimension that is actually free. A near miss
   selects a neighboring week, which only moves a line of text. */
.ps-week { flex: 1 1 0; min-width: 0; height: 100%; display: flex; align-items: flex-end; border-radius: 3px;
  padding: 0 1px; background: var(--paper-2); background-clip: content-box; cursor: pointer; }
.ps-week-fill { display: block; width: 100%; border-radius: 3px; background: var(--rule); min-height: 3px; }
.ps-week.ps-hm--excellent { background: var(--sage-wash); } .ps-week.ps-hm--excellent .ps-week-fill { background: var(--sage); }
.ps-week.ps-hm--solid     { background: var(--grade-solid-wash); }  .ps-week.ps-hm--solid .ps-week-fill     { background: var(--grade-solid); }
.ps-week.ps-hm--steady    { background: var(--grade-steady-wash); } .ps-week.ps-hm--steady .ps-week-fill    { background: var(--grade-steady); }
.ps-week.ps-hm--off       { background: var(--accent-wash); } .ps-week.ps-hm--off .ps-week-fill     { background: var(--orange); }
.ps-week.is-month-start { box-shadow: -1px 0 0 var(--rule); }

/* The selected week. An inset ring rather than an outline so it cannot widen
   the bar and shove the whole strip along by a pixel. */
.ps-week.is-selected { box-shadow: inset 0 0 0 1.5px var(--ink-soft); }
.ps-week.is-selected.is-month-start { box-shadow: inset 0 0 0 1.5px var(--ink-soft), -1px 0 0 var(--rule); }
.ps-week:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; border-radius: 4px; }

/* The figures, in one persistent line rather than a tooltip nobody on a phone
   could ever open. Reserves its own height so selecting a week never reflows
   the page under the reader's thumb. */
.ps-weekstrip-detail { margin-top: 8px; min-height: 18px; font-size: 13px; font-weight: 600;
  color: var(--ink-soft); text-align: center; font-variant-numeric: tabular-nums; }

/* The current week has not finished, so it carries no grade — an outline, the
   same "in progress" tell today gets in the day strip. */
.ps-week.is-partial { background: none; box-shadow: inset 0 0 0 1px var(--orange); }
.ps-week.is-partial .ps-week-fill { background: var(--accent-wash); }
.ps-weekstrip-axis { display: flex; gap: 2px; margin-top: 5px; }
.ps-weekstrip-tick { flex: 1 1 0; min-width: 0; font-size: 10px; font-weight: 600; color: var(--ink-faint); white-space: nowrap; }

/* ===== Momentum card (good days stacked) =====
   The page's opening statement, so it gets the display face and the largest
   number on the screen — the behavior the whole product is trying to produce. */
.mo-head { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 2px; padding: 4px 0 2px; }

.mo-eyebrow { font-size: var(--type-eyebrow-size); font-weight: var(--type-eyebrow-weight); letter-spacing: var(--type-eyebrow-track);
  text-transform: uppercase; color: var(--accent-ink); }

.mo-figure { font-family: var(--font-display, serif); font-variant-ligatures: no-common-ligatures; font-size: 64px; font-weight: 600; line-height: 1; letter-spacing: -.03em;
  color: var(--accent-ink); font-variant-numeric: tabular-nums; }
.mo-caption { font-size: 15px; font-weight: 600; color: var(--ink-soft); }
.mo-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--rule); }
.mo-stat { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.mo-stat-value { font-size: 20px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.mo-stat-label { font-size: 12px; color: var(--ink-soft); }
.mo-intro { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--rule); font-size: 14px; line-height: 1.5; color: var(--ink-soft); text-align: center; }
.mo-definition { margin-top: 12px; font-size: 12px; line-height: 1.4; color: var(--ink-faint); text-align: center; }
.mo-milestone { margin-top: 14px; }
.mo-track { height: 8px; border-radius: 999px; background: var(--paper-2); overflow: hidden; }
.mo-fill { display: block; height: 100%; border-radius: 999px; background: var(--sage); }

/* ===== "Does stacking work?" contrast table ===== */
.sp-lede { font-size: 14px; line-height: 1.5; color: var(--ink-soft); margin-bottom: 12px; }

/* ONE grid for the whole table, with the rows as display:contents. Each row
   used to be its own grid, so its `auto` columns sized to its own content and
   no column ever lined up with the one above it - the header "67% STRONG DAYS"
   sat nowhere near the "2.6" it was labeling. */
.sp-table { display: grid; grid-template-columns: 1fr minmax(76px, auto) minmax(76px, auto); }
.sp-row { display: contents; }

/* Column separation comes from cell padding, NOT column-gap: a gap would fall
   between the cells' top borders and break each rule into three floating
   segments instead of one line across the table. */
.sp-table > .sp-row > * { padding: 9px 0; }
.sp-table > .sp-row > .sp-col { padding-left: 14px; }

/* display:contents rows cannot carry the rule themselves, so the cells do -
   above every row but the header. */
.sp-table > .sp-row:not(:first-child) > * { border-top: 1px solid var(--rule); }
.sp-row--head > * { padding-top: 0; }
.sp-metric { align-self: baseline; font-size: 14px; font-weight: 600; color: var(--ink); }

.sp-col { display: flex; flex-direction: column; align-items: flex-end; justify-content: flex-end; gap: 1px;
  text-align: right; font-size: 15px; font-weight: 600; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* A cohort needs a NAME before it needs a statistic - "67% good days" alone
   labels nothing, it just asks the reader to work out which column is which. */
.sp-col-name { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink); }
.sp-col-sub { font-size: 11px; font-weight: 600; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.sp-col--stacked { color: var(--sage); font-weight: 700; }
.sp-col--stacked .sp-col-name { color: var(--sage); }
.sp-note { margin-top: 12px; font-size: 12px; line-height: 1.5; color: var(--ink-faint); }

/* ===== Mined patterns ===== */
.pt-stack { display: flex; flex-direction: column; gap: 10px; }
.pt-headline { display: block; font-family: var(--font-display, serif); font-variant-ligatures: no-common-ligatures; font-size: 19px; font-weight: 600; letter-spacing: -.015em; color: var(--ink); }
.pt-detail { margin-top: 5px; font-size: 15px; line-height: 1.5; color: var(--ink-soft); }
.pt-meta { display: block; margin-top: 8px; font-size: 12px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }

/* ===== Then vs now ===== */
.cp-row { align-items: center; }
.cp-progression { display: flex; align-items: baseline; gap: 7px; font-size: 14px; font-variant-numeric: tabular-nums; }
.cp-then { color: var(--ink-faint); text-decoration: line-through; text-decoration-thickness: 1px; }
.cp-arrow { color: var(--ink-faint); }
.cp-now { color: var(--ink); font-weight: 700; }
.cp-delta { flex: 0 0 auto; font-size: 14px; font-weight: 700; color: var(--sage); font-variant-numeric: tabular-nums; }
.cp-note { margin: 8px 4px 0; font-size: 12px; line-height: 1.5; color: var(--ink-faint); }

/* Trend rows (Train > Progression) borrow the then/now shape but not its
   verdict. No strike-through: the earlier figure was not beaten, it is just
   what the lift was doing six weeks ago, and it may be the higher of the two.
   Only a rise takes the sage; flat and down stay neutral, because a lift that
   dipped through a deload is not a failure and a red number would be the app
   judging a stretch it cannot see the reasons for. */

/* The Progression screen's opening line. Set in the display serif, like the
   weekly letter's prose and the recap's - it is the one sentence on the page
   written to be read rather than scanned, and setting it as another line of UI
   text is what made the screen read as a report. */
.pg-lede { margin: 4px 4px 18px; font-family: var(--font-display, georgia, serif); font-variant-ligatures: no-common-ligatures; font-size: 17px;
  font-weight: 500; line-height: 1.45; letter-spacing: -.005em; color: var(--ink); }

/* Salvy's read of the training, in the daily note's own card so the companion
   looks like one companion across two screens. Only the margins differ: this
   page has no big title above it, so the card needs its own top breathing room
   under the subnav pill row. */

/* flow-root, because the avatar is FLOATED into the card and this note has a
   state the daily note does not: a single short line. "Analyzing your
   training…" is shorter than the orb is tall, so the float escaped the bottom
   of the card - Salvy hanging below a box whose rounded corners had been cut
   square by the border stopping at the text. A block formatting context makes
   the card contain its own float at every length. */
.pg-note { display: flow-root; margin: 4px 0 18px; }

/* The finished note needs normal document flow; the one-line waiting state
   does not. Center its status and orb in one compact row instead of inheriting
   the prose card's empty vertical space. */
.pg-note.daily-note-thinking { display: flex; align-items: center; min-height: 64px; padding-block: 11px; }
.pg-note.daily-note-thinking .daily-note-writing { flex: 1; min-width: 0; }
.pg-note.daily-note-thinking .salvy-avatar { float: none; order: 2; margin: 0 0 0 12px; }

/* The daily note is one paragraph and .daily-note-text is margin:0 for it. This
   note is allowed two - what has gone quiet, then what is moving - and without
   this they render as one block, losing the break the note was written around. */
.pg-note .daily-note-text + .daily-note-text { margin-top: 12px; }
.pg-note-section + .pg-note-section { margin-top: 16px; }

.pg-note-title { margin: 0 0 6px; font-family: var(--font-ui); font-size: 11px; line-height: 1.3;
  font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-faint); }

/* Two text lines already make these rows comfortably tappable. Keep the list
   scan-dense instead of adding the shared row's icon-sized vertical padding. */
.pg-pattern-row { min-height: 52px; padding-block: 7px; }

/* The observer's staleness hint. Set apart from the prose above it - a rule and
   the UI face - because it is the app talking about the note, not Salvy talking
   about the training, and the two must not read as one voice. */
.pg-note-stale { margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--rule);
  font-family: var(--font-ui, -apple-system, system-ui, sans-serif); font-size: 12px;
  line-height: 1.4; color: var(--ink-faint); }

.tp-row .cp-then { text-decoration: none; }

.tp-delta { flex: 0 0 auto; font-size: 14px; font-weight: 700; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; }
.tp-delta--up { color: var(--sage); }

/* The pattern drill-down: sessions, and the sets as they were performed. Set
   as a reading list rather than a table - a table of three columns on a phone
   is four columns of nothing, and the sets are the only figure here that
   varies.

   THE SETS ARE THE LOUDEST THING HERE. This was built the other way round -
   session heading biggest, exercise name bold, sets small and gray - so the
   page shouted its scaffolding and whispered its content. A member opening a
   pattern wants "5 x 5 reps @ 225 lb"; the date, the workout's name and the
   exercise are how they find it, not what they came for. */

/* The definition, above the sessions. Set as prose rather than as a note in
   the muted caption size: it is the first thing a member who does not know
   what a hinge is came here to read, and a caption reads as a footnote to
   something else. The examples sit under it, quieter, because they are the
   illustration and not the answer. */
.pd-pattern-about { margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid var(--rule); }
.pd-pattern-summary { font-size: 15px; line-height: 1.5; color: var(--ink); }
.pd-pattern-examples { margin-top: 8px; font-size: 13px; line-height: 1.5; color: var(--ink-faint); }
.pd-pattern-examples-label { font-weight: 700; color: var(--ink-soft); }
.pd-pattern-examples-label::after { content: ":\00a0"; }

.pd-session + .pd-session { margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--rule); }
.pd-session-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.pd-session-date { font-size: 13px; font-weight: 700; color: var(--ink-soft); }
.pd-session-meta { font-size: 13px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }

/* The workout's own name, trailing the date behind a separator - a detail, but
   a navigable one. It is the only route from "this is where that set happened"
   to the session itself, so it is quiet, not absent. */
.pd-session-name { font-weight: 500; color: var(--ink-faint); text-decoration: none; }

/* nbsp AFTER the separator, plain space before it. A plain trailing space in
   generated content collapses against the text that follows, which rendered
   "Sat 18 Jul ·Pull Day"; the nbsp also keeps the dot traveling with the name
   rather than being left stranded at the end of a wrapped line. */
.pd-session-name::before { content: " \00b7\00a0"; }
a.pd-session-name:hover, a.pd-session-name:focus-visible { color: var(--ink-soft); text-decoration: underline; }

.pd-exercise { margin-top: 10px; }
.pd-exercise-name { display: block; font-size: 13px; font-weight: 600; color: var(--ink-soft); }

.pd-sets { display: block; margin-top: 2px; font-size: 15px; font-weight: 500; line-height: 1.5;
  color: var(--ink); font-variant-numeric: tabular-nums; }

/* ===== Lifetime training totals (All time card) =====
   A row, not a grid: two or three big figures reading across is a headline,
   whereas boxing them would make them look like the activity counts below,
   which are a different kind of number. */

/* Tuned tight on purpose: these are numbers that only ever go up, so the card
   earns its place by being READABLE at a glance, not by being large. Every
   figure here kept its size; what came out is the air around them. */
.ps-lifetime { padding: 12px; }
.lt-hero { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 1px; }

/* Scoped past .workout-motivator-number, which the figure also wears (the
   achievements page reads that class) and which paints it 800-weight orange at
   up to 72px. Serif, deep, and 44 here - the same figure, sized for a card
   rather than for a trophy screen. */
.ps-lifetime .lt-hero-number { font-family: var(--font-display, serif); font-variant-ligatures: no-common-ligatures;
  font-size: 44px; font-weight: 600; line-height: 1.05; letter-spacing: -.02em; color: var(--accent-ink);
  font-variant-numeric: tabular-nums; }
.lt-hero-label { font-size: 15px; font-weight: 600; color: var(--ink); }
.lt-hero-next { font-size: 13px; color: var(--ink-soft); }

.lt-strip { display: flex; flex-wrap: wrap; gap: 6px 18px; justify-content: center; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rule); }
.lt-stat { display: flex; flex-direction: column; align-items: center; gap: 0; min-width: 74px; }
.lt-value { font-family: var(--font-display, serif); font-variant-ligatures: no-common-ligatures; font-size: 24px; font-weight: 600; letter-spacing: -.02em; color: var(--ink); font-variant-numeric: tabular-nums; }
.lt-label { font-size: 12px; color: var(--ink-soft); text-align: center; }

/* The counts grid under the totals. "1878" beside the bare word "sleep" reads
   as a sleep measurement - hours, a score, something - when it is a count of
   nights logged, and by the time a member has scrolled this far the "All time"
   section label is off the top of the screen, so the card cannot say WHEN
   either. A caption over the grid answers neither in practice: the eye takes
   the figure and the word under it and passes over anything set quieter. So
   the unit lives in the tile label, where the eye already is. The window
   stays with the section label above the card - repeating it as an eyebrow in
   here only put ALL TIME on the same card twice. */
.lt-counts { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px;
  margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--rule); }

.lt-count { display: flex; flex-direction: column; align-items: center; gap: 1px; padding: 8px 6px;
  background: var(--paper-2); border-radius: 12px; }
.lt-count-value { font-size: 20px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }

.lt-count-label { display: flex; align-items: center; justify-content: center; gap: 5px;
  font-size: 12.5px; color: var(--ink-soft); text-align: center; line-height: 1.2; }

/* The chip beside the label, not the 38px row chip stacked over the figure -
   at label scale it reads as part of the words rather than as its own tier. */
.lt-count-ic { width: 20px; height: 20px; border-radius: 6px; font-size: 11px; }

/* ===== What unlocks next ===== */
.cu-row .ps-row-title { color: var(--ink-soft); }
.cu-ic { background: var(--paper-2); color: var(--ink-faint); font-size: 13px; }
.cu-note { margin: 8px 4px 0; font-size: 12px; line-height: 1.5; color: var(--ink-faint); }

/* ===== Score detail sheet (why this day graded the way it did) ===== */
.sd-verdict { padding: 14px 16px; border-radius: var(--radius-tile); background: var(--paper-2); }
.sd-verdict--excellent { background: var(--sage-wash); }
.sd-verdict--solid { background: var(--grade-solid-wash); }
.sd-verdict--steady { background: var(--grade-steady-wash); }
.sd-verdict--off { background: var(--accent-wash); }
.sd-grade { display: block; font-family: var(--font-display, serif); font-variant-ligatures: no-common-ligatures; font-size: 22px; font-weight: 600; letter-spacing: -.02em; color: var(--ink); }
.sd-summary { margin-top: 4px; font-size: 14px; line-height: 1.5; color: var(--ink-soft); }
.sd-pillars { display: flex; flex-direction: column; gap: 13px; }
.sd-pillar-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 5px; }
.sd-pillar-name { font-size: 14px; font-weight: 600; color: var(--ink); }
.sd-pillar-weight { margin-left: 6px; font-size: 11px; font-weight: 600; color: var(--ink-faint); }
.sd-pillar-pct { font-size: 14px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
.sd-bar { position: relative; height: 8px; border-radius: 999px; background: var(--paper-2); overflow: hidden; }
.sd-bar-fill { display: block; height: 100%; border-radius: 999px; background: var(--sage); }

/* An anchor pillar under the excellent-day floor is the usual reason a strong
   average still did not earn the top grade — say so in the bar, not just prose. */
.sd-bar-fill.is-short { background: var(--yellow); }
.sd-bar-floor { position: absolute; top: -2px; bottom: -2px; width: 2px; background: var(--ink-faint); opacity: .55; }
.sd-nudge { margin-top: 12px; font-size: 14px; line-height: 1.5; color: var(--ink-soft); }
.sd-note { margin-top: 10px; font-size: 12px; line-height: 1.5; color: var(--ink-faint); }
.sd-recap { font-size: 15px; line-height: 1.55; color: var(--ink); }
.sd-actions { margin-top: 18px; }

/* Screen-reader-only text. Lives here rather than in a page stylesheet because
   any page that failed to load that stylesheet would render the text on screen -
   which is what happened when the trainer status bar, on a page that does not
   load manage_users.css, started labeling its score deltas. */
.mu-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ===== Movement compass (Progression hero) =====
   Petal fills step down from the brand orange so the wheel re-tints with a
   white-label rebrand; stale is drawn from the neutral ramp because "idle"
   must never look like a brand color. */
.pc-compass { max-width: 372px; margin: 4px auto 14px; padding: 14px 16px 16px; }
.pc-compass svg { display: block; width: 100%; height: auto; }
.pc-compass a { cursor: pointer; }
.pc-hit { fill: transparent; pointer-events: all; }
.pc-track { fill: #F2EBDF; }
.pc-compass a:focus-visible .pc-petal { stroke: var(--ink); stroke-width: 2px; }
.pc-petal--fresh { fill: var(--orange); }
.pc-petal--waning { fill: #F1A884; }
.pc-petal--fading { fill: #F8D4C1; }

.pc-petal--stale { fill: #DCCDB3; stroke: var(--ink-faint); stroke-dasharray: 3 3; }
.pc-label { font-size: 14px; font-weight: 800; fill: var(--ink); }
.pc-label--stale { fill: var(--ink-soft); }
.pc-sublabel { font-size: 11.5px; font-weight: 700; fill: var(--ink-soft); }

.pc-header { margin: 0 0 2px; font-size: 12px; font-weight: 800;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent-ink); }

.pc-key { display: flex; justify-content: center; gap: 16px; margin-top: 6px;
  font-size: 13px; font-weight: 600; color: var(--ink); }

.pc-key-dot { display: inline-block; width: 9px; height: 9px; border-radius: 3px;
  margin-right: 4px; vertical-align: -1px; }
.pc-key-dot--fresh { background: var(--orange); }
.pc-key-dot--waning { background: #F1A884; }
.pc-key-dot--fading { background: #F8D4C1; }

.pc-key-dot--stale { background: #DCCDB3; border: 1px dashed var(--ink-faint); }

/* The static fills above keep all four states distinguishable on WebKit older
   than 16.2 (the iOS shell deploys to 16.0), which drops color-mix
   declarations outright. Modern engines re-derive every step from the brand
   orange so white-label tints carry through. */
@supports (color: color-mix(in srgb, red 50%, blue)) {
  .pc-track { fill: color-mix(in srgb, var(--rule) 32%, var(--paper)); }
  .pc-petal--waning { fill: color-mix(in srgb, var(--orange) 62%, var(--paper)); }
  .pc-petal--fading { fill: color-mix(in srgb, var(--orange) 30%, var(--paper)); }
  .pc-petal--stale { fill: color-mix(in srgb, var(--rule) 85%, var(--paper)); }
  .pc-key-dot--waning { background: color-mix(in srgb, var(--orange) 62%, var(--paper)); }
  .pc-key-dot--fading { background: color-mix(in srgb, var(--orange) 30%, var(--paper)); }
  .pc-key-dot--stale { background: color-mix(in srgb, var(--rule) 85%, var(--paper)); }
}

.pc-caption { margin: 0 0 4px; max-width: 44ch; text-align: left;
  font-size: 13.5px; line-height: 1.45; font-weight: 500; color: var(--ink); }
.pc-caption span { display: block; text-wrap: pretty; }
.pc-caption span + span { margin-top: 3px; }

/* ===== Dismiss mark =====
   The standard x: a clear, a close, a remove. Drawn as a mask rather than set
   as a character, because "×" sits below the center of its own line box in our
   face - centering the button never centers the glyph, and every place that
   tried ended up nudging it by hand. */

/* font-size: 0 hides the glyph the mask replaces - but only where a mask can
   actually be drawn, or the control would degrade to a blank disc. */
.ps-clear-x {
  background-color: var(--ink-soft);
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M6 6l12 12M18 6L6 18'/%3E%3C/svg%3E");
  mask-repeat: no-repeat;
  mask-position: center;
  mask-size: 13px 13px;
}

@supports (mask-image: url("")) {
  .ps-clear-x {
    font-size: 0;
  }
}

.ps-clear-x:hover {
  background-color: var(--ink);
}

.ps-clear-x:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 2px;
}

/* ===== Searchable select =====
   A <select class="form-select ps-select"> driven by the searchable-select
   controller: Tom Select for the typeahead, wearing the same box as the plain
   select it sits beside. Tom Select copies the original's classes onto its
   wrapper, which is what these rules hang off.

   The !importants are not decoration. exercise_select.css paints every Tom
   Select control with `background: ... !important` for the picker sheets - a
   SHORTHAND, so it also sets background-image: none at that weight, and the
   caret has to be marked important or it silently never draws. A page-level
   field on cream has to out-shout the color for the same reason. Worth
   unpicking there one day.

   Specificity is also only a tie with modals.css's .ts-wrapper.form-select
   .ts-control, which loads after this file and wins ties - a select carries
   both classes. Anything added here that must hold needs the same weight. */

/* Tom Select copies .form-select onto the wrapper as well as leaving it on the
   hidden original, so the wrapper arrives wearing a border and radius of its
   own - a second box drawn 1px outside the one .ts-control paints, and 2px
   taller than the plain select beside it. The control is the field; the wrapper
   is just its position: relative anchor. */
.ts-wrapper.ps-select {
  width: 100%;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
}

.ts-wrapper.ps-select .ts-control,
.ts-wrapper.ps-select.single.input-active .ts-control {
  /* Height DERIVED, not pinned. The plain select's height is its own line box
     plus .form-select's padding and border, and that line box moves: the UI
     font's metrics differ from the fallback's, so the same select measures 44px
     on a cold load and 47px once the webfont is cached. Any number hard-coded
     here is therefore only ever right on one of those paints. Giving the
     control the same vertical padding and the same "normal" line box as the
     select instead means both boxes are computed from whichever font actually
     rendered, and they move together for free.

     One gap this leaves: mid-swap, a UA-drawn select and this div disagree
     about the fallback's line box, so the pair is briefly 3px apart while
     Manrope loads. Apple platforms never see it (-apple-system leads the
     stack and needs no load), and it corrects itself on swap. */
  align-items: center;
  padding: var(--space-3) var(--space-4) !important;
  padding-right: 2.75rem !important;
  border: 1px solid var(--border) !important;
  border-radius: var(--radius) !important;
  background-color: var(--bg) !important;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") !important;
  background-repeat: no-repeat !important;
  background-position: right 1.25rem center !important;
  background-size: 1rem !important;
  box-shadow: none;
}

/* The dropdown is parented to <body>, so it is not in here to be styled -
   exercise_select.css sizes it globally. */
.ts-wrapper.ps-select .ts-control,
.ts-wrapper.ps-select .ts-control input {
  font-size: 0.9375rem;

  /* normal, not a ratio: this is the line box the plain select gets from the
     UA, and matching it is what keeps the two fields the same height in
     whichever font ends up rendering. */
  line-height: normal;
  color: var(--ink);
}

/* The text caret takes its height from the input's own type, which Tom Select
   leaves at its 13px default - a stub of a cursor next to 15px text. */
.ts-wrapper.ps-select .ts-control input {
  font-size: inherit;
  height: auto;
}

/* Typing replaces what is picked, so the chosen item gets out of the text's
   way; a long name truncates rather than wrapping the control onto two lines. */
.ts-wrapper.ps-select.single.input-active .ts-control > .item {
  display: none;
}

.ts-wrapper.ps-select .ts-control > .item {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ts-wrapper.ps-select.focus .ts-control {
  border-color: var(--orange);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--orange) 10%, transparent);
}

/* With something picked, clearing IS the action the right-hand control should
   offer, so the x takes the caret's place rather than crowding in beside it.
   It also stays put: the stock plugin reveals it on hover, which on a phone
   means never. has-selection comes from the controller - Tom Select's own
   has-items counts the blank "all" option as an item. */
.ts-wrapper.ps-select:not(.has-selection) .clear-button {
  display: none;
}

.ts-wrapper.ps-select.has-selection .ts-control {
  background-image: none !important;
}

.ts-wrapper.ps-select.has-selection .clear-button {
  opacity: 1;
  right: 10px !important;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background-color: var(--ink-soft) !important;
}

.ts-wrapper.ps-select.has-selection .clear-button:hover {
  background-color: var(--ink) !important;
}

/* Under 640px the plain select takes 16px type (the iOS zoom guard), which
   makes it taller - the searchable one follows or the pair sits uneven. Only
   the type is set here: the height derives from it, so it needs no second
   number of its own. */
@media (width <= 640px) {
  .ts-wrapper.ps-select .ts-control,
  .ts-wrapper.ps-select .ts-control input {
    font-size: 1rem;
  }
}

/* ===== Typed filter over a long list =====
   Shared by the People roster and the workout library. A row the filter
   rules out is hidden outright rather than dimmed: a 150-folder library
   filtered to three should read as three, not as three among many. */
.mu-search {
  display: block;
  position: relative;
  margin: 0 2px 12px;
}

.mu-search-ic {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 13px;
  color: var(--ink-faint);
  pointer-events: none;
}

.mu-search-input {
  width: 100%;
  height: 42px;
  padding: 0 14px 0 38px;
  border: 1px solid var(--rule);
  border-radius: var(--radius-tile);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 15px;
}

.mu-search-input::placeholder {
  color: var(--ink-faint);
}

/* The focus ring travels with the input. It replaces the browser's outline
   rather than adding to it, so leaving it behind in a page-scoped stylesheet
   would have given the library a different ring from the roster. */
.mu-search-input:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-wash);
}

.mu-row--hidden {
  display: none;
}

/* ===== Sort bar =====
   Sits under the typed filter on a list of folders or workouts. Quiet by
   default: the list is the page, and the control that reorders it should not
   compete with it. */
.ps-sortbar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 2px 12px;
}

.ps-sortchip {
  appearance: none;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink-soft);
  font: inherit;
  font-size: 13px;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
}

.ps-sortchip.is-active {
  background: var(--accent-wash);
  border-color: transparent;
  color: var(--ink);
  font-weight: 600;
}

.ps-sortchip:focus-visible,
.ps-sortdir:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-wash);
}

.ps-sortdir {
  appearance: none;
  margin-inline-start: auto;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink-soft);
  width: 32px;
  height: 32px;
  border-radius: var(--radius-tile);
  cursor: pointer;
}

/* One glyph, rotated, so ascending and descending are the same shape read two
   ways rather than two icons the eye has to tell apart. */
.ps-sortdir[data-direction="desc"] i {
  transform: scaleY(-1);
}
