/* ----------------------------------------------------------------- reset */
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
  /* Zinc-100 base, Zinc-900 ink, Zinc-200 dividers — see design spec. */
  --bg-0: #f2f2f1;
  --bg-1: #e7e6e2;
  --ink: #18181b;
  --ink-dim: #71717a;
  --glass: rgba(250, 250, 250, 0.75);
  --glass-brd: rgba(228, 228, 231, 0.9);
  --accent: #0f8fb3;
  --stretch: #0f8fb3;
  --strengthen: #d9713c;
  --flag: #e5342b;
  --capture: #e5484d;
  --capture-hi: #f0605f;
  --dark-bg: #0a0a0b;
  --dark-brd: rgba(255, 255, 255, 0.1);
  --dark-ink: #f4f4f5;
  --dark-ink-dim: #a1a1aa;
  --font: "Google Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;
}

html, body {
  height: 100%;
  width: 100%;
  overflow: hidden;
  background: linear-gradient(165deg, var(--bg-0), var(--bg-1));
  color: var(--ink);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  user-select: none;
}

#scene {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

/* Every internal scroll region in the app (the muscle index, the exercise
   panel, the console's card list, a flipped card's trend charts, the
   heatmap's day list, a parsed-workout review) keeps scrolling — this only
   hides the browser's own scrollbar chrome. A thin bar floating over the
   glass panels read as stray debug UI, not part of the design. */
.indexpanel, .panel__list, .sheet__scroll, .trends, .heatpeek__list, .review {
  scrollbar-width: none;      /* Firefox */
  -ms-overflow-style: none;   /* legacy Edge */
}
.indexpanel::-webkit-scrollbar,
.panel__list::-webkit-scrollbar,
.sheet__scroll::-webkit-scrollbar,
.trends::-webkit-scrollbar,
.heatpeek__list::-webkit-scrollbar,
.review::-webkit-scrollbar {
  display: none;
}
/* Desktop: the sheet is a fixed right-hand column, so the model is confined
   to the left of it. Under the mobile breakpoint the split turns horizontal
   instead — the model takes the top half (see the mobile block). */
@media (min-width: 621px) {
  body.is-console #scene { width: calc(100% - min(460px, 42vw)); }
}

/* --------------------------------------------------------------- loader */
.loader {
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  z-index: 40;
  color: var(--ink-dim);
  letter-spacing: 0.02em;
  transition: opacity 0.6s ease;
}
.loader.is-hidden { opacity: 0; pointer-events: none; }
.loader__pulse {
  width: 46px; height: 46px;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 40%, var(--accent), transparent 70%);
  animation: pulse 1.4s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { transform: scale(0.8); opacity: 0.5; }
  50% { transform: scale(1.15); opacity: 1; }
}

/* ----------------------------------------------------------- browse index */
.indextoggle {
  position: fixed;
  top: 68px; left: 24px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 15px;
  border-radius: 12px;
  background: var(--dark-bg);
  border: 1px solid var(--dark-brd);
  box-shadow: 0 10px 30px rgba(24, 24, 27, 0.05);
  color: var(--dark-ink);
  font: 500 14px var(--font);
  cursor: pointer;
  z-index: 26;
  transition: background 0.2s ease;
}
.indextoggle:hover { background: #1a1a1c; }
.indextoggle[aria-expanded="true"] { box-shadow: inset 0 0 0 1px rgba(15, 143, 179, 0.3); }
.indextoggle__ico { font-size: 15px; }

.indexpanel {
  position: fixed;
  top: 118px; left: 24px;
  width: 250px;
  max-height: calc(100vh - 180px);
  overflow-y: auto;
  padding: 8px;
  border-radius: 18px;
  background: var(--glass);
  border: 1px solid var(--glass-brd);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: 0 20px 45px rgba(24, 24, 27, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.6);
  z-index: 24;
  opacity: 0;
  transform: translateX(-16px);
  pointer-events: none;
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.indexpanel.is-open { opacity: 1; transform: none; pointer-events: auto; }

.idx__search {
  position: sticky;
  top: -8px;
  margin: -8px -8px 6px;
  padding: 8px 8px 6px;
  background: var(--glass);
  z-index: 1;
}
.idx__search input {
  width: 100%;
  padding: 8px 12px;
  border-radius: 10px;
  border: 1px solid var(--glass-brd);
  background: rgba(24, 24, 27, 0.04);
  color: var(--ink);
  font: 400 13.5px var(--font);
  outline: none;
  transition: background 0.15s ease, box-shadow 0.15s ease;
}
.idx__search input::placeholder { color: var(--ink-dim); }
.idx__search input:focus {
  background: #fff;
  box-shadow: inset 0 0 0 1.5px var(--accent);
}

.idx__region.is-hidden { display: none; }
.idx__item.is-hidden { display: none; }
.idx__region { margin-bottom: 6px; }
.idx__title {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-dim);
  padding: 12px 10px 6px;
}
.idx__item {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 0;
  background: transparent;
  border-radius: 10px;
  cursor: pointer;
  text-align: left;
  transition: background 0.15s ease;
}
.idx__item:hover { background: rgba(24, 24, 27, 0.04); }
.idx__item.is-active { background: rgba(24, 24, 27, 0.06); box-shadow: inset 0 0 0 1px var(--glass-brd); }
.idx__item.is-away { opacity: 0.38; }
.idx__item.is-away:hover { background: transparent; }
.idx__dot { flex: 0 0 auto; width: 10px; height: 10px; border-radius: 50%; box-shadow: 0 0 8px currentColor; }
.idx__meta { display: flex; flex-direction: column; min-width: 0; }
.idx__name { font-size: 14px; font-weight: 500; color: var(--ink); }
.idx__mus { font-size: 11px; color: var(--ink-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* -------------------------------------------------------------- tooltip */
.tooltip {
  position: fixed;
  z-index: 30;
  padding: 7px 12px;
  border-radius: 10px;
  background: rgba(250, 250, 250, 0.96);
  border: 1px solid var(--glass-brd);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  color: var(--ink);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  pointer-events: none;
  transform: translate(-50%, calc(-100% - 14px));
  white-space: nowrap;
  box-shadow: 0 10px 30px rgba(24, 24, 27, 0.12);
}
.tooltip small { display: block; font-weight: 400; color: var(--ink-dim); font-size: 11px; margin-top: 1px; }

/* ----------------------------------------------------------------- hint */
.hint {
  position: fixed;
  bottom: 22px; left: 50%;
  transform: translateX(-50%);
  padding: 9px 18px;
  border-radius: 999px;
  background: var(--glass);
  border: 1px solid var(--glass-brd);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: 0 10px 26px rgba(24, 24, 27, 0.06);
  color: var(--ink-dim);
  font-size: 13px;
  z-index: 20;
  transition: opacity 0.4s ease, transform 0.4s ease;
}
.hint b { color: var(--ink); font-weight: 600; }
.hint.is-hidden { opacity: 0; transform: translateX(-50%) translateY(12px); pointer-events: none; }

/* ---------------------------------------------------------------- panel */
.panel {
  position: fixed;
  top: 118px;
  left: 24px;
  width: min(390px, calc(100vw - 48px));
  max-height: calc(100vh - 150px);
  display: flex;
  flex-direction: column;
  border-radius: 22px;
  padding: 0;
  z-index: 35;

  /* "Surface Layer": oklab(0.98 0 0 / 0.75) ≈ rgba(250,250,250,.75), 16px blur */
  background: var(--glass);
  border: 1px solid var(--glass-brd);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow:
    0 24px 60px rgba(24, 24, 27, 0.12),
    inset 0 1px 0 rgba(255, 255, 255, 0.7);

  opacity: 0;
  transform: translateX(-30px) scale(0.98);
  pointer-events: none;
  transition: opacity 0.45s cubic-bezier(0.22, 1, 0.36, 1),
              transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  overflow: hidden;
}
.panel.is-open {
  opacity: 1;
  transform: translateX(0) scale(1);
  pointer-events: auto;
}
/* subtle top sheen */
.panel::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 40%;
  background: linear-gradient(180deg, rgba(255,255,255,0.5), transparent);
  pointer-events: none;
}

.panel__close {
  position: absolute;
  top: 14px; right: 14px;
  width: 32px; height: 32px;
  border-radius: 50%;
  border: 1px solid var(--glass-brd);
  background: rgba(24, 24, 27, 0.04);
  color: var(--ink);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  z-index: 2;
  transition: background 0.2s ease;
}
.panel__close:hover { background: rgba(24, 24, 27, 0.08); }

/* vertical rhythm: 32px between major blocks (head / list / disclaimer) */
.panel__head {
  padding: 32px 28px 24px;
  border-bottom: 1px solid var(--glass-brd);
}
.panel__region {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 10px;
}
.panel__name {
  font-size: 26px;
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1.15;
}
.panel__muscles {
  margin-top: 8px;
  font-size: 13px;
  color: var(--ink-dim);
  font-weight: 400;
}
.panel__blurb {
  margin-top: 16px;
  font-size: 15px;
  line-height: 1.55;
  color: #3f3f46;
}

.panel__isolate {
  margin-top: 14px;
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid var(--glass-brd);
  background: rgba(24, 24, 27, 0.04);
  color: var(--ink);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.panel__isolate:hover { background: rgba(24, 24, 27, 0.09); }
.panel__isolate.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.panel__list {
  padding: 12px 16px 8px;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* exercise card */
.ex {
  border-radius: 14px;
  padding: 4px 4px;
  margin: 10px 6px;
  transition: background 0.2s ease;
}
.ex__btn {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  text-align: left;
  background: transparent;
  border: 0;
  color: var(--ink);
  padding: 12px 12px;
  border-radius: 12px;
  cursor: pointer;
}
.ex__btn:hover { background: rgba(24, 24, 27, 0.035); }
.ex__tag {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 4px 8px;
  border-radius: 7px;
}
.ex__tag--stretch { background: rgba(15, 143, 179, 0.12); color: var(--stretch); }
.ex__tag--strengthen { background: rgba(217, 113, 60, 0.14); color: var(--strengthen); }
.ex__meta { flex: 1 1 auto; min-width: 0; }
.ex__name { font-size: 15px; font-weight: 500; }
.ex__time { font-size: 12px; color: var(--ink-dim); margin-top: 2px; }
.ex__chev {
  flex: 0 0 auto;
  color: var(--ink-dim);
  transition: transform 0.3s ease;
  font-size: 13px;
}
.ex.is-open .ex__chev { transform: rotate(90deg); }

.ex__body {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}
.ex.is-open .ex__body { max-height: 460px; }
.ex__inner { padding: 2px 14px 16px; }
.ex__why {
  font-size: 13px;
  line-height: 1.55;
  color: #52525b;
  font-style: italic;
  margin-bottom: 12px;
  padding-left: 12px;
  border-left: 2px solid rgba(15, 143, 179, 0.35);
}
.ex__steps { list-style: none; counter-reset: step; }
.ex__steps li {
  position: relative;
  padding: 6px 0 6px 30px;
  font-size: 13.5px;
  line-height: 1.55;
  color: #3f3f46;
  counter-increment: step;
}
.ex__steps li::before {
  content: counter(step);
  position: absolute;
  left: 0; top: 5px;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: rgba(24, 24, 27, 0.05);
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

.panel__disclaimer {
  padding: 20px 28px 24px;
  font-size: 11px;
  line-height: 1.5;
  color: #a1a1aa;
  border-top: 1px solid var(--glass-brd);
}

/* ------------------------------------------------------------------ sheet */
/* The console is the whole app now, so this is the app's primary surface.
   Desktop: a fixed right-hand column beside the model. Mobile: a bottom
   sheet that is always 100vh tall and slid down by half via a transform
   (framer-motion owns that `y` — see ProgressSheet), so expanding to full
   screen is a GPU transform rather than an animated height. */
.sheet {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: min(460px, 42vw);
  z-index: 35;
  display: flex;
  flex-direction: column;
  background: var(--glass);
  border-left: 1px solid var(--glass-brd);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}

.sheet__pane {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 22px 20px 20px;
}
/* Top-aligned, not vertically centred: the intro is the first thing a new
   user reads, so it starts where their eye already is. */
.sheet__pane--center {
  justify-content: flex-start;
  /* Centers the intro block as a whole on a wide/full-width sheet (the
     mobile breakpoint expands .sheet to 100% viewport width), so extra
     space is split evenly left/right instead of collecting only on the
     right while the text hugs the left edge. .sheet__intro's own max-width
     keeps it from stretching to fill under this. */
  align-items: center;
  gap: 18px;
  padding-bottom: 40px;
  /* Outer gutter for narrow sheets, where the intro block fills edge-to-edge
     minus this — same role the old fixed padding played. */
  padding-left: 32px;
  padding-right: 32px;
}
.sheet__intro {
  width: 100%;
  max-width: 420px;
  /* The block centers; the heading and lede inside it stay left-aligned
     against the block's own edge, which is the actual request — not
     centered text, a centered column of left-aligned text. */
  text-align: left;
  /* Same 18px the pane's own flex gap used to provide when the heading and
     lede were its direct children — wrapping them in this container moved
     them off that flex row, so the gap has to be restored here instead. */
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.sheet__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex: 0 0 auto;
}
.sheet__title {
  font-size: 19px;
  font-weight: 500;
  letter-spacing: -0.015em;
  line-height: 1.25;
}
.sheet__sub {
  margin-top: 5px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-dim);
}
.sheet__display {
  font-size: clamp(28px, 6.6vw, 38px);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.03em;
}
.sheet__lede {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--ink-dim);
  max-width: 42ch;
}

/* The only scroll region in the app — the card list. */
.sheet__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-bottom: 8px;
}

/* ------------------------------------------------------------------ buttons */
.btn {
  appearance: none;
  border: 0;
  border-radius: 999px;
  font: 500 14px var(--font);
  cursor: pointer;
  transition: transform 0.15s ease, opacity 0.15s ease, background 0.15s ease;
}
.btn:active { transform: scale(0.985); }
.btn--primary {
  flex: 0 0 auto;
  align-self: stretch;
  padding: 15px 22px;
  background: var(--ink);
  color: var(--dark-ink);
}
.btn--primary:disabled { opacity: 0.4; cursor: default; }
.sheet__pane--center .btn--primary { align-self: flex-start; }
.btn--ghost {
  flex: 0 0 auto;
  padding: 8px 14px;
  font-size: 13px;
  background: rgba(24, 24, 27, 0.06);
  color: var(--ink);
}
.btn--ghost:hover { background: rgba(24, 24, 27, 0.1); }
.btn--back {
  align-self: flex-start;
  padding: 8px 14px 8px 10px;
  font-size: 13px;
  background: rgba(24, 24, 27, 0.06);
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.composer__input {
  flex: 1 1 auto;
  min-height: 150px;
  resize: none;
  border-radius: 16px;
  border: 1px solid var(--glass-brd);
  background: rgba(255, 255, 255, 0.6);
  padding: 14px 16px;
  font: 400 14px/1.65 var(--font);
  color: var(--ink);
  user-select: text;
}
.composer__input:focus { outline: none; border-color: rgba(24, 24, 27, 0.28); }
.composer__input::placeholder { color: #b6b6bd; }
.composer__error { font-size: 12.5px; line-height: 1.5; color: var(--flag); }

/* ------------------------------------------------------------- score cards */
/* Dark cards on a light app: the scores are the one thing on screen that
   should stop you, and inverting them out of the surface they sit on is the
   cheapest way to say so without adding chrome. */
.scorecard,
.selected {
  --accent: #a1a1aa;
  --wash: rgba(40, 40, 46, 0.5);
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 100%;
  text-align: left;
  padding: 16px 18px 18px;
  border-radius: 24px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  background:
    radial-gradient(120% 90% at 0% 0%, var(--wash) 0%, transparent 62%),
    linear-gradient(155deg, #17171a 0%, #0c0c0e 100%);
  color: var(--dark-ink);
  box-shadow: 0 10px 30px rgba(24, 24, 27, 0.12);
}
.scorecard {
  appearance: none;
  font: inherit;
  cursor: pointer;
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.scorecard:hover { transform: translateY(-1px); box-shadow: 0 14px 34px rgba(24, 24, 27, 0.18); }
.scorecard:active { transform: scale(0.995); }

.scorecard__head,
.selected__head {
  display: flex;
  align-items: center;
  gap: 12px;
}
/* Portrait, not a circle: the badge holds a whole standing figure, so it
   needs the height to render one legibly — and enough padding that the figure
   isn't pressed against the edges. */
.scorecard__badge {
  flex: 0 0 auto;
  width: 56px; height: 88px;
  display: grid;
  place-items: center;
  padding: 8px 6px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.08);
}
.scorecard__headtext {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.scorecard__name {
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.scorecard__status {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--accent);
}
.scorecard__chev {
  flex: 0 0 auto;
  width: 22px; height: 22px;
  color: rgba(244, 244, 245, 0.5);
}
.scorecard__chev svg {
  width: 100%; height: 100%;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.scorecard__body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.07);
}
.scorecard__score,
.selected__score {
  font-size: 46px;
  font-weight: 300;
  line-height: 1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}
.selected__score { font-size: 34px; margin-left: auto; }
.scorecard__headline,
.selected__headline {
  font-size: 21px;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.02em;
}
.scorecard__blurb {
  font-size: 14px;
  line-height: 1.55;
  color: rgba(244, 244, 245, 0.72);
}
.scorecard__meta {
  margin-top: 2px;
  font-size: 11.5px;
  letter-spacing: 0.02em;
  color: rgba(244, 244, 245, 0.42);
  font-variant-numeric: tabular-nums;
}
.selected {
  flex: 0 0 auto;
  gap: 10px;
}
.selected__headline {
  font-size: 19px;
  margin-top: 2px;
}

/* The body image inside each badge — contained, never cropped or stretched. */
.glyph {
  height: 100%;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  display: block;
}

/* ---------------------------------------------------------------- wordmark */
.wordmark {
  position: fixed;
  top: 22px; left: 24px;
  z-index: 40;
  font: 700 16px var(--font);
  letter-spacing: -0.01em;
  color: var(--ink);
  pointer-events: none;
}

/* ------------------------------------------------------------------ tabbar */
.tabbar {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  z-index: 45;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px 8px 8px;
  border-radius: 999px;
  background: var(--dark-bg);
  border: 1px solid var(--dark-brd);
  box-shadow: 0 12px 34px rgba(24, 24, 27, 0.22);
}
.tabbar__item {
  appearance: none;
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 7px 18px;
  border-radius: 999px;
  font: 500 13px var(--font);
  color: var(--dark-ink-dim);
  transition: background 0.15s ease, color 0.15s ease;
}
.tabbar__item:hover { color: var(--dark-ink); }
.tabbar__item.is-active { background: rgba(255, 255, 255, 0.1); color: var(--dark-ink); }

/* An action, not a second page — so it's a red "+" rather than a label.
   Both it and Console are pinned to the same min-width so the bar reads as
   evenly divided instead of the "+" collapsing to its glyph. */
.tabbar__item,
.tabbar__capture { min-width: 86px; text-align: center; }
.tabbar__capture {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  appearance: none;
  border: 0;
  padding: 7px 18px;
  border-radius: 999px;
  background: var(--capture);
  color: #fff;
  font: 400 19px/1 var(--font);
  cursor: pointer;
  transition: background 0.15s ease, transform 0.15s ease;
}
.tabbar__capture:hover { background: var(--capture-hi); }
.tabbar__capture:active { transform: scale(0.97); }
.tabbar__capture.is-active { background: var(--capture-hi); }
/* The + is the app's one call to action, so it carries a red halo until the
   button has been pressed once (see is-glowing in App). The pieces of the
   shadow are named rather than inlined into the keyframes, so the dim and
   bright ends stay legible next to each other. */
:root {
  --glow-dur: 2.2s;
  --glow-dim: 0.73;
  --glow-bright: 0.95;
  --glow-blur: 21px;
  --glow-spread: 6px;
  --glow-halo-blur: 40px;
  --glow-halo: 0.75;
}
.tabbar__capture.is-glowing { animation: captureGlow var(--glow-dur) ease-in-out infinite; }

/* Holds dim, blooms fast, holds bright — a beat rather than a shimmer. */
@keyframes captureGlow {
  0%, 18% { box-shadow: var(--glow-off); }
  46%, 62% { box-shadow: var(--glow-on); }
  92%, 100% { box-shadow: var(--glow-off); }
}

:root {
  --glow-off:
    0 0 4px 0 rgba(229, 72, 77, var(--glow-dim)),
    0 0 10px 0 rgba(229, 72, 77, calc(var(--glow-dim) * 0.45));
  --glow-on:
    0 0 var(--glow-blur) var(--glow-spread) rgba(229, 72, 77, var(--glow-bright)),
    0 0 var(--glow-halo-blur) calc(var(--glow-spread) * 2.5) rgba(229, 72, 77, var(--glow-halo));
}

/* Recording replaces the glow with its own pulse so the two never stack. */
.tabbar__capture.is-recording { animation: capturePulse 1.6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
  .tabbar__capture.is-glowing {
    animation: none;
    box-shadow: 0 0 16px 3px rgba(229, 72, 77, 0.6), 0 0 36px 8px rgba(229, 72, 77, 0.3);
  }
}
/* Hold-to-dictate: the button has to survive a long press without the OS
   text-selection / callout gestures firing on top of it. */
.tabbar__capture {
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.tabbar__capture.is-recording { background: var(--capture-hi); }
@keyframes capturePulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(240, 96, 95, 0.55); }
  50% { box-shadow: 0 0 0 9px rgba(240, 96, 95, 0); }
}

/* --------------------------------------------------------------- mobile */
@media (max-width: 620px) {
  /* The model gets the top half of the screen and nothing else — the sheet
     owns the bottom half, and the canvas is sized to the half it actually
     occupies (not the viewport) so the figure is framed inside that slice
     instead of being centred behind the sheet. */
  #scene { height: 50%; }

  .sheet {
    top: 0; left: 0; right: 0; bottom: 0;
    width: 100%;
    border-left: 0;
    border-top: 1px solid var(--glass-brd);
    border-radius: 26px 26px 0 0;
    box-shadow: 0 -14px 40px rgba(24, 24, 27, 0.12);
  }
  /* Bands at both ends of the sheet: the floating tab bar owns the bottom,
     and the wordmark owns the top. When the sheet is expanded it covers the
     whole screen, so without the top band its heading would run straight
     under ATOMO. */
  .sheet__pane { padding: 64px 16px 92px; }
  /* The intro only ever shows with the sheet at half height, so the wordmark
     is over the model rather than over this pane — no top band needed. It
     does have to clear the sheet's 26px corner radius, though, or the first
     line starts inside the curve. */
  .sheet__pane--center { padding-top: 34px; padding-bottom: 108px; padding-left: 24px; padding-right: 24px; }

  /* Centred in the blank band the sheet reserves for it (the pane's 92px
     bottom padding): 92px band, 50px bar, so 21px of clearance either side. */
  .wordmark { top: 18px; left: 18px; }
  .tabbar { bottom: 21px; }

  .panel {
    top: auto; right: 0; left: 0; bottom: 0;
    width: 100%;
    max-height: 68vh;
    border-radius: 22px 22px 0 0;
    transform: translateY(40px);
  }
  .panel.is-open { transform: translateY(0); }

  /* In peek the sheet is still a full-height element slid halfway off the
     bottom, so only its top half is actually on screen. The selected pane is
     the one pane shown in that state, so it has to be bounded by the visible
     half — left at the sheet's own height, the flip card sizes itself to
     100vh and its back face lands below the fold. */
  /* Extra bottom padding over the usual 92px: this pane starts below the
     grip, so its box runs slightly past the viewport, and the card has to
     stop clear of the floating tab bar rather than tuck under it. */
  /* The peek pane starts halfway down the screen, so the wordmark is over
     the model rather than over it — no top band needed here. */
  .sheet__pane--selected { flex: 0 0 auto; height: 50vh; padding-top: 14px; padding-bottom: 106px; }

  .hint { display: none; }
}

/* ------------------------------------------------------------------ search */
.search {
  flex: 0 0 auto;
  position: relative;
  display: flex;
  align-items: center;
}
.search__ico {
  position: absolute;
  left: 14px;
  width: 16px; height: 16px;
  fill: none;
  stroke: var(--ink-dim);
  stroke-width: 1.8;
  stroke-linecap: round;
  pointer-events: none;
}
.search__input {
  width: 100%;
  appearance: none;
  border-radius: 999px;
  border: 1px solid var(--glass-brd);
  background: rgba(255, 255, 255, 0.6);
  padding: 11px 14px 11px 38px;
  font: 400 14px var(--font);
  color: var(--ink);
  user-select: text;
}
.search__input:focus { outline: none; border-color: rgba(24, 24, 27, 0.28); }
.search__input::placeholder { color: #a8a8b0; }
.search__input::-webkit-search-cancel-button { display: none; }
.search__clear {
  position: absolute;
  right: 8px;
  width: 26px; height: 26px;
  display: grid;
  place-items: center;
  appearance: none;
  border: 0;
  border-radius: 50%;
  background: rgba(24, 24, 27, 0.08);
  color: var(--ink-dim);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}
.search__empty {
  padding: 18px 2px;
  font-size: 13.5px;
  color: var(--ink-dim);
}

/* -------------------------------------------------------------- flip card */
/* Motion reproduced from the Framer "Flip Card 3D Animations" component —
   perspective 1500, preserve-3d, hidden backfaces, rotateY 0 -> -180. The
   rotation itself is animated by framer-motion on .flip__inner; everything
   here is the 3D staging that makes that rotation read as a card with two
   physical sides rather than a squashing rectangle. */
.flip {
  flex: 1 1 auto;
  min-height: 0;
  perspective: 1500px;
}
.flip__inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
}
.flip__face {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  height: 100%;
  text-align: left;
  appearance: none;
  font: inherit;
  cursor: pointer;
  padding: 16px 18px;
  border-radius: 24px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  background:
    radial-gradient(120% 90% at 0% 0%, var(--wash) 0%, transparent 62%),
    linear-gradient(155deg, #17171a 0%, #0c0c0e 100%);
  color: var(--dark-ink);
  box-shadow: 0 10px 30px rgba(24, 24, 27, 0.14);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  overflow: hidden;
}
.flip__face--back { transform: rotateY(180deg); }

/* Pushed to the bottom of whichever face is showing — the flip has no other
   affordance, so the card has to say it can be turned over. */
.flip__cue {
  margin-top: auto;
  flex: 0 0 auto;
  font-size: 11.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(244, 244, 245, 0.4);
}
/* The face is a fixed box (shortest on the mobile peek, where the sheet is
   only half the viewport) with overflow:hidden, and the narration sentence
   varies a lot in length by status. Clamped so a long one can never push
   "Tap for trends" below the visible edge — the one thing on this face that
   must never be the part that goes missing. */
.flip__face--front .scorecard__blurb {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}
.flip__backhead {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}

/* ---------------------------------------------------------------- trends */
.trends {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 4px 0 2px;
}
.trends__empty {
  font-size: 13.5px;
  line-height: 1.55;
  color: rgba(244, 244, 245, 0.6);
}

.trend { display: flex; flex-direction: column; gap: 7px; }
.trend__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.trend__name {
  font-size: 13.5px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.trend__delta {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.trend__delta.is-up { color: #5fd3a2; }
.trend__delta.is-down { color: #ff7d6b; }
.trend__delta.is-flat { color: rgba(244, 244, 245, 0.45); }

.trend__bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 52px;
}
.trend__bar {
  flex: 1 1 0;
  min-width: 3px;
  border-radius: 2px 2px 0 0;
  background: rgba(244, 244, 245, 0.24);
}
/* The most recent session is the one the user is asking about. */
.trend__bar.is-last { background: var(--accent); }

.trend__foot {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: 11px;
  color: rgba(244, 244, 245, 0.42);
  font-variant-numeric: tabular-nums;
}
.trend__sessions { color: rgba(244, 244, 245, 0.3); }

/* The peek pane hands its whole remaining height to the flip card so both
   faces share one box and the back's charts have somewhere to live. */
.sheet__pane--selected { gap: 12px; }

/* ------------------------------------------------------------- heatmap card */
/* Same shell as a score card — it does the same job (tap it, the model shows
   you something), so it should not look like a different species. */
.heatcard { --accent: #f08a86; --wash: rgba(104, 40, 36, 0.5); }

.heatpeek {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px 18px;
  border-radius: 24px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  background:
    radial-gradient(120% 90% at 0% 0%, rgba(104, 40, 36, 0.5) 0%, transparent 62%),
    linear-gradient(155deg, #17171a 0%, #0c0c0e 100%);
  color: var(--dark-ink);
  box-shadow: 0 10px 30px rgba(24, 24, 27, 0.14);
}
.heatpeek__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex: 0 0 auto;
}
.heatpeek__toggle {
  display: flex;
  gap: 2px;
  padding: 3px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.07);
}
.heatpeek__range {
  appearance: none;
  border: 0;
  background: transparent;
  color: rgba(244, 244, 245, 0.55);
  font: 500 11.5px var(--font);
  padding: 5px 11px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.heatpeek__range:hover { color: var(--dark-ink); }
.heatpeek__range.is-active { background: rgba(255, 255, 255, 0.14); color: var(--dark-ink); }

.heatpeek__list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.heatpeek__row {
  display: grid;
  grid-template-columns: 86px 1fr 20px;
  align-items: center;
  gap: 10px;
  font-size: 12.5px;
}
.heatpeek__name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: rgba(244, 244, 245, 0.8);
}
.heatpeek__bar {
  height: 7px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
}
/* Same white-to-deep-red ramp the model is tinted with. */
.heatpeek__bar > span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, #f5f5f7, #96181c);
}
.heatpeek__count {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: rgba(244, 244, 245, 0.45);
}
.heatpeek__empty {
  flex: 1 1 auto;
  font-size: 13px;
  color: rgba(244, 244, 245, 0.5);
}
.heatpeek__legend {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 10.5px;
  color: rgba(244, 244, 245, 0.4);
}
.heatpeek__scale {
  width: 84px;
  height: 5px;
  border-radius: 999px;
  background: linear-gradient(90deg, #f5f5f7, #96181c);
}

/* -------------------------------------------------------------- voice pill */
/* Sits just above the tab bar while dictating, showing what is being heard so
   the user can tell it is working before they let go. */
.voicepill {
  position: fixed;
  left: 50%;
  bottom: 84px;
  transform: translateX(-50%);
  z-index: 46;
  display: flex;
  align-items: center;
  gap: 9px;
  max-width: min(88vw, 420px);
  padding: 10px 16px;
  border-radius: 999px;
  background: var(--dark-bg);
  border: 1px solid var(--dark-brd);
  box-shadow: 0 12px 34px rgba(24, 24, 27, 0.28);
  color: var(--dark-ink);
  font: 500 13px var(--font);
}
.voicepill.is-error { color: #ff9d94; }
.voicepill__dot {
  flex: 0 0 auto;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--capture-hi);
  animation: capturePulse 1.6s ease-in-out infinite;
}
.voicepill__text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The one-time hold-to-dictate tip, in the same pill as the recording and
   error states so it reads as the same voice affordance speaking. */
.voicepill--hint {
  appearance: none;
  cursor: pointer;
  font: 500 13px var(--font);
  gap: 10px;
  padding-right: 12px;
  animation: voicepillIn 0.35s ease both;
}
.voicepill--hint:hover { background: #131315; }
.voicepill__mic {
  flex: 0 0 auto;
  width: 15px; height: 15px;
  fill: none;
  stroke: var(--capture-hi);
  stroke-width: 1.7;
  stroke-linecap: round;
}
.voicepill__close {
  flex: 0 0 auto;
  font-size: 16px;
  line-height: 1;
  color: var(--dark-ink-dim);
}
.voicepill--hint .voicepill__text { white-space: normal; }
@keyframes voicepillIn {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}

/* Weekly-sets chart with the effective-range band behind the bars. */
.volume__plot { position: relative; height: 58px; }
.volume__band {
  position: absolute;
  left: 0; right: 0;
  border-radius: 3px;
  background: rgba(95, 211, 162, 0.16);
  border-top: 1px dashed rgba(95, 211, 162, 0.4);
  border-bottom: 1px dashed rgba(95, 211, 162, 0.28);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
/* Self-labeled, since this shape is exactly what's left visible when a
   group has no sets to bar-chart yet — nothing else on the card would tell
   you it's a target zone rather than a plotted value. */
.volume__band-label {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(95, 211, 162, 0.65);
  white-space: nowrap;
}
.volume__bars { position: absolute; inset: 0; height: 100%; }
.trend__bar.is-inband { background: rgba(95, 211, 162, 0.75); }
.trend__bar.is-inband.is-last { background: #5fd3a2; }

/* Keeps the score honest about what it is: a dose proxy, not a measurement. */
.sheet__note {
  padding: 4px 2px 2px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink-dim);
}

/* The + becomes a live waveform while dictating. Bars are scaled on the
   compositor (transform, not height) so the meter stays smooth at 60fps
   alongside the 3D scene. */
.wave {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2.5px;
  height: 18px;
}
.wave__bar {
  width: 2.5px;
  height: 100%;
  border-radius: 999px;
  background: #fff;
  transform: scaleY(0.2);
  transform-origin: center;
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .wave__bar { transform: scaleY(0.55) !important; }
}

/* ------------------------------------------------------------------ review */
/* What the parser made of the text, shown before any of it becomes a score.
   The muscle mapping is its biggest guess, so the chips carry as much weight
   here as the numbers do. */
.review {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-right: 2px;
}
.review__row {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 12px 14px;
  border-radius: 16px;
  border: 1px solid var(--glass-brd);
  background: rgba(255, 255, 255, 0.55);
}
.review__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.review__name { font-size: 14.5px; font-weight: 500; }
.review__nums {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}
.review__groups { display: flex; flex-wrap: wrap; gap: 5px; }
.review__chip {
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 500;
  background: rgba(229, 72, 77, 0.12);
  color: #a8302f;
}
/* Half credit reads as a lighter chip — the same distinction the score makes. */
.review__chip.is-secondary {
  background: rgba(24, 24, 27, 0.06);
  color: var(--ink-dim);
  font-weight: 400;
}
/* The textarea gives up room once there is a review list to show. */
.composer__input.is-compact { flex: 0 0 auto; min-height: 62px; }

/* ---------------------------------------------------------------- calendar */
/* A month of training as one square per day, Sunday to Saturday. Same dark
   card shell as the scores so it reads as another row in the same list. */
.calcard {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px 18px 14px;
  border-radius: 24px;
  border: 1px solid rgba(255, 255, 255, 0.07);
  background: linear-gradient(155deg, #17171a 0%, #0c0c0e 100%);
  color: var(--dark-ink);
  box-shadow: 0 10px 30px rgba(24, 24, 27, 0.12);
}
.calcard__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.calcard__month {
  font-size: 14.5px;
  font-weight: 500;
  letter-spacing: -0.01em;
}
.calcard__nav {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  display: grid;
  place-items: center;
  appearance: none;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.07);
  color: rgba(244, 244, 245, 0.75);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, opacity 0.15s ease;
}
.calcard__nav:hover { background: rgba(255, 255, 255, 0.14); color: var(--dark-ink); }
.calcard__nav:disabled { opacity: 0.25; cursor: default; }
.calcard__nav svg {
  width: 15px; height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.calcard__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}
.calcard__weekday {
  text-align: center;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: rgba(244, 244, 245, 0.35);
  padding-bottom: 2px;
}
/* Square cells: the padding trick keeps them square at any column width. */
.calcard__day {
  position: relative;
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
  border-radius: 5px;
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  /* Untrained days sit just above the card — the tint (or lack of it) on the
     cell itself is what carries "trained vs. not"; the date number stays
     fully legible either way. */
  background: rgba(255, 255, 255, 0.06);
  color: var(--dark-ink);
}
/* Tinted days carry dark text, since the scale runs toward white. */
.calcard__day[style] { color: rgba(24, 24, 27, 0.72); font-weight: 500; }
.calcard__day.is-blank { background: transparent; }
.calcard__day.is-today { box-shadow: inset 0 0 0 1.5px rgba(244, 244, 245, 0.55); }

.calcard__legend {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  font-size: 10.5px;
  color: rgba(244, 244, 245, 0.4);
}
.calcard__scale {
  width: 72px;
  height: 5px;
  border-radius: 999px;
  background: linear-gradient(90deg, #ffffff, #c8181c);
}

/* ------------------------------------------------------------------ reveal */
/* Sequential load-in for the console list — each row arrives a beat after the
   one above it. animation-delay is set per row in ProgressSheet. */
.reveal {
  animation: revealIn 420ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes revealIn {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .reveal { animation: none; }
}

/* Heatmap rows are tappable: each one flies the camera to that group. */
.heatpeek__row {
  width: 100%;
  appearance: none;
  border: 0;
  background: transparent;
  font: inherit;
  color: inherit;
  cursor: pointer;
  padding: 4px 6px;
  margin: -4px -6px;
  border-radius: 9px;
  text-align: left;
  transition: background 0.15s ease;
}
.heatpeek__row:hover { background: rgba(255, 255, 255, 0.06); }
.heatpeek__row.is-framed { background: rgba(255, 255, 255, 0.1); }
.heatpeek__row.is-framed .heatpeek__name { color: var(--dark-ink); }
