/* components.css — reusable pieces. Geometry is pills and rounded cards. */

.card {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  box-shadow: var(--shadow-inset), var(--shadow-md);
}

.card-label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--color-text-dim);
}

/* ---- buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-6);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border);
  background: var(--color-surface-raised);
  color: var(--color-text);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  letter-spacing: .12em;
  text-transform: uppercase;
  transition: background var(--transition-fast), border-color var(--transition-fast);
  min-height: 46px;              /* thumb target, gym gloves, one hand */
}

.btn:hover:not(:disabled) { background: var(--color-hover); }
.btn:disabled { opacity: .45; cursor: default; }

.btn-primary {
  box-shadow: var(--shadow-sm);
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: #fff;
  font-weight: 600;
}
.btn-primary:hover:not(:disabled) {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
}

.btn-ghost { background: transparent; }
.btn-block { width: 100%; }

/* ---- pills and status ----------------------------------------------------- */

.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 4px 11px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* Inline SVG icons: no icon font, no sprite sheet, nothing to load. Sized in the
   element that holds them and coloured by currentColor. */
.icon {
  width: 22px;
  height: 22px;
  display: block;
  flex: none;
}

.dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
  flex: none;
  background: var(--color-text-dim);
}
.dot-synced  { background: var(--color-success); }
.dot-pending { background: var(--color-warning); }
.dot-offline { background: var(--color-text-dim); }
.dot-error   { background: var(--color-danger); }

/* Sync state is information, not an error — offline is the expected case in a
 * gym, so it must never read as a failure. */
.sync-line {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--color-text-dim);
}

/* ---- rows ----------------------------------------------------------------- */

.rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Each row is its own surface rather than a hairline-separated line of text.
   A dense list of near-identical rows reads as one block and gives the eye no
   place to land; separating them costs vertical space and is worth it. */
.row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-inset);
  text-align: left;
  width: 100%;
}

/* Draggable rows glide into the gap exactly as the exercise cards do. Scoped to
   [data-card] — the marker $dragCard measures — so the app's many other lists
   stay untouched. The dragged row itself must not be animated, or it trails the
   finger by one transition. */
.rows > .row[data-card] { transition: transform var(--transition-fast); }

.rows > .row[data-card].is-dragging {
  transition: none;
  position: relative;
  z-index: 5;
  box-shadow: var(--shadow-lg);
  border-color: var(--color-primary-soft);
}

/* Inside a card the surfaces would stack — a raised panel holding lifted rows
   reads as two competing layers. There, the original hairline is right. */
.card .rows { gap: 0; }

.card .row {
  background: none;
  border: 0;
  border-bottom: 1px solid var(--color-border);
  border-radius: 0;
  box-shadow: none;
  padding: var(--space-3) 0;
}
.card .row:last-child { border-bottom: 0; }

/* text-align is set explicitly because this is often a <button>, and the UA
   stylesheet's `center` beats the `left` it would otherwise inherit. */
.row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  text-align: left;
}

/* max-width is what actually contains these. The parent is a flex column with
   align-items: flex-start, so children size to their content and can exceed the
   column's own width — `overflow: hidden` then clips at the child's width, not
   the container's, and the text runs out over the buttons. */
.row-text b,
.row-text small {
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.row-text b {
  font-weight: 600;
  font-size: var(--text-sm);
}

.row-text small {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}

.row-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--color-text);
  flex: none;
}

/* A checkbox that has to be found and read before it is ticked — for the ones
   whose default is "no" and where saying yes overwrites something. */
.opt-in {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  font-size: var(--text-sm);
  cursor: pointer;
}

.opt-in input {
  flex: none;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  accent-color: var(--color-primary);
}

.opt-in small {
  display: block;
  margin-top: 2px;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
}

/* ---- feedback ------------------------------------------------------------- */

.notice {
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.notice-error {
  border-color: var(--color-danger);
  color: var(--color-text);
  background: rgba(214, 69, 58, .1);
}

.spinner {
  width: 20px;
  height: 20px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--color-border);
  border-top-color: var(--color-primary);
  animation: spin 700ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* The drawn figure. Fixed box so rows never reflow, and the SVG scales into it. */
.thumb {
  width: 44px;
  height: 44px;
  flex: none;
  border-radius: var(--radius-sm);
  background: var(--color-ink);
  display: grid;
  place-items: center;
  padding: 3px;
}

.thumb svg { width: 100%; height: 100%; display: block; }

/* Same figure, given room on the exercise history sheet. */
.figure {
  height: 150px;
  display: grid;
  place-items: center;
  background: var(--color-ink);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  box-shadow: var(--shadow-inset);
}

.figure svg { height: 100%; width: auto; display: block; }

/* No animation on the figure. It was built to say "this is what's doing the
   work", and the honest verdict after living with it is that a moving diagram
   is harder to read at a glance than a still one — you wait for the loop to come
   round instead of just looking. The class is still applied so the worked muscle
   stays highlighted; only the motion is gone.

   History, so this doesn't get rebuilt from scratch: transform: scale() needs
   `transform-box: fill-box` to pivot on the shape, which is unreliable on a <g>;
   SMIL <animate> injected through innerHTML never joins the SVG timeline; and
   the CSS opacity version worked all along but was masked by a stale service
   worker. If motion is ever wanted again, CSS opacity is the approach that
   works. */

/* Front and back together, for the exercise detail sheet. */
.figure-pair {
  display: grid;
  place-items: center;
  background: var(--color-ink);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-3);
  box-shadow: var(--shadow-inset);
}

.figure-pair svg { width: 100%; height: auto; display: block; }

/* The drawing stands in for the pair rather than sitting over it: it is square
   and the pair is wide, so a contained overlay would letterbox and leak the
   figures around its edges. Static, not absolute, overriding .art below. */
.figure-pair .art {
  position: static;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  max-width: 320px;
  margin: 0 auto;
}

/* Set by the image's own onload, so the pair only disappears once there is
   something to replace it with. */
.figure-pair.has-art svg { display: none; }

/* The second position sits exactly over the first and the two swap on a slow
   loop: a second and a half on each, with a short fade between. Absolute rather
   than a shared grid cell so it takes no space before it loads — an exercise
   with one drawing lays out precisely as it did. Centred with inset + auto
   margins, which lands on the same spot as the grid centres the first. */
.figure-pair { position: relative; }
.figure-pair .art-2 {
  position: absolute;
  inset: 0;
  margin: auto;
  opacity: 0;
}
.figure-pair.has-art.has-art-2 .art-2 { animation: art-swap 3s infinite; }

@keyframes art-swap {
  0%, 40%  { opacity: 0; }
  50%, 90% { opacity: 1; }
  100%     { opacity: 0; }
}

/* Under reduced motion the first drawing stands alone, as it always has. */
@media (prefers-reduced-motion: reduce) {
  .figure-pair.has-art.has-art-2 .art-2 { animation: none; }
}

/* ---- sheet furniture ------------------------------------------------------- */
/* A title row with a close control, so sheets stop ending in a stack of
   full-width buttons where only one is the actual action. */

.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-bottom: var(--space-1);
}

.sheet-head h2 {
  font-size: var(--text-lg);
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Secondary actions sit beside each other; only the primary gets full width. */
.btn-row {
  display: flex;
  gap: var(--space-2);
}

.btn-row .btn { flex: 1; padding-left: var(--space-3); padding-right: var(--space-3); }

.btn-danger {
  border-color: rgba(224, 54, 42, .5);
  color: var(--color-danger);
}
.btn-danger:hover:not(:disabled) { background: rgba(224, 54, 42, .12); }

/* ---- movement ------------------------------------------------------------- */
/* The figure performs the shape of the movement. Joint origins are in the SVG's
   own user units, which works because the right side is the left side mirrored —
   one origin serves both limbs.
 *
 * Archetypes, not choreography: a curl is a curl whether it's a barbell, a
 * dumbbell or a cable, and inventing 150 individual animations would be wrong
 * more often than right. */

/* Pivots sit at the JOINT — where the segment meets its parent — not at the
   middle of the segment. Getting this wrong tears the limb off the body. */
.mm-arm   { transform-origin: 40px 48px; }   /* shoulder */
.mm-fore  { transform-origin: 34px 88px; }   /* elbow */
.mm-thigh { transform-origin: 50px 99px; }   /* hip */
.mm-shin  { transform-origin: 51px 153px; }  /* knee */
.mm-torso { transform-origin: 60px 97px; }   /* pelvis */

/* The movement keyframes and their bindings were removed with the pulse. The
   pivots above are kept: they are the measured joint positions, and they would
   have to be re-derived from the figure geometry if motion ever returns. */

/* ---- swipe to remove ------------------------------------------------------ */

.row-swipe {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-md);
}

/* Opaque, or the action behind it shows through the row it is meant to sit
   under. Matches the surface the rows are painted on. */
.row-swipe > .row,
.row-swipe > .setrow {
  position: relative;
  background: var(--color-surface);
  /* No radius of its own — the wrapper's overflow clip supplies it.
     Two identically-sized rounded rects rasterise independently, and the
     antialiased seam between them let the red action show as a thin arc down
     the right-hand curve. One rounded shape cannot seam against itself. */
  border-radius: 0;
  transition: transform var(--transition-fast);
  will-change: transform;
  /* The browser keeps vertical panning and hands us the horizontal, so a list
     still scrolls under a finger while a row can be dragged sideways. */
  touch-action: pan-y;
  /* Or a mouse drag paints the row's own label blue on the way across. */
  user-select: none;
}

/* The fields are still fields: the row around them is what resists selection. */
.row-swipe > .setrow .cell { user-select: text; }

/* Set rows live inside a card, which is a lighter surface than a bare row sits
   on. The done state paints over this in pages.css and must stay opaque there —
   anything see-through reveals the remove action sitting behind the row. */
.row-swipe > .setrow { background: var(--color-surface-raised); }

.row-swipe.is-open > .row,
.row-swipe.is-open > .setrow { transform: translateX(-96px); }

/* Not painted at all until it is wanted.
 *
 * The row sits exactly on top of this and is opaque, which ought to be enough —
 * `elementFromPoint` agrees the row covers every pixel of it. It is not enough.
 * Two stacked layers under one rounded `overflow: hidden` clip get their edges
 * anti-aliased independently, and the difference showed as a red arc tracing the
 * corner of every row in the app. Covering it more carefully is a losing game;
 * not drawing it is exact. */
.row-remove {
  position: absolute;
  inset: 0 0 0 auto;
  width: 96px;
  border: 0;
  background: var(--color-danger);
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 600;
  opacity: 0;
  pointer-events: none;
}

/* Revealed while a drag is in progress, so it slides out from under the row the
   way it always did, and once the row has settled open. */
.row-swipe.is-sliding .row-remove,
.row-swipe.is-open .row-remove {
  opacity: 1;
  pointer-events: auto;
}

@media (prefers-reduced-motion: reduce) {
  .row-swipe > .row,
  .row-swipe > .setrow { transition: none; }
}

/* The brand, beside the name it belongs to. Lighter and unshouted: it is
   which one, not what it is. */
.brand {
  font-weight: 400;
  color: var(--color-text-muted);
  text-transform: none;
}

/* The estimate is a guess and has to keep saying so. Warning-coloured rather
   than neutral: this number did not come off a label. */
.estimate-caveat {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--color-warning);
  margin: 0;
}

.estimate-note {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin: 0;
}

/* Enough to confirm it's the right plate, not so much it pushes Read it off a
   phone screen once the keyboard is up. */
.meal-photo-thumb {
  display: block;
  width: 100%;
  max-height: 28vh;
  object-fit: cover;
  border-radius: var(--radius-md);
}

.btn.is-busy { opacity: .6; }

/* The generated drawing, laid over the figure it replaces. If the file isn't
   there the <img> removes itself and the figure underneath is what shows. */
.thumb, .figure { position: relative; }

.art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: inherit;
}

/* ---- settings rows --------------------------------------------------------- */

.setting {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
}

/* Unavailable rather than merely off: the browser has denied it, or there is a
   switch above that has to be on first. Still readable, so the reason shows. */
.setting.is-dead { opacity: .55; cursor: default; }

.setting-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.setting-text b { font-size: var(--text-sm); }
.setting-text small { font-size: var(--text-xs); color: var(--color-text-muted); }

.setting input[type="checkbox"] {
  appearance: none;
  flex: none;
  width: 46px;
  height: 27px;
  border-radius: var(--radius-pill);
  background: var(--color-ink);
  border: 1px solid var(--color-border);
  position: relative;
  transition: background .15s ease, border-color .15s ease;
}

.setting input[type="checkbox"]::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 21px;
  height: 21px;
  border-radius: var(--radius-pill);
  background: var(--color-text-dim);
  transition: transform .15s ease, background .15s ease;
}

.setting input[type="checkbox"]:checked {
  background: var(--color-primary);
  border-color: var(--color-primary);
}
.setting input[type="checkbox"]:checked::after {
  transform: translateX(19px);
  background: #fff;
}
.setting input[type="checkbox"]:disabled { cursor: default; }

.setting-meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin: 0;
  word-break: break-all;
}

/* ---- progress photos --------------------------------------------------------
   The locked state shows a lock and a PIN field — deliberately no thumbnails,
   no blur-that-hints. A curtain that previews its contents isn't one. */

.photo-lock {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.photo-lock-prompt {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.photo-lock-prompt .icon { width: 18px; height: 18px; }
.photo-lock-reset {
  align-self: flex-start;
  white-space: nowrap;
  background: none;
  border: 0;
  padding: var(--space-1) 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--color-text-dim);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.pin-row { display: flex; gap: var(--space-2); }
/* Outside a .field, so it dresses itself — a bare input renders as the
   browser's white box on this dark surface. */
.pin-row input {
  flex: 1;
  min-width: 0;
  text-align: center;
  font-size: var(--text-lg);
  letter-spacing: .35ch;
  color: var(--color-text);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  outline: none;
}
.pin-row input:focus { border-color: var(--color-primary); }
.pin-row input::placeholder { color: var(--color-text-dim); letter-spacing: .35ch; }
.pin-row .btn { flex: none; }

.photo-add-row {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.photo-add-row .btn { flex: 1; text-align: center; }

.photo-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 3px;
  margin-top: var(--space-3);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.photo-cell {
  position: relative;
  aspect-ratio: 3 / 4;
  padding: 0;
  border: 0;
  background: var(--color-surface-raised);
  cursor: pointer;
}
.photo-cell img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.photo-cell.is-selected { outline: 2px solid var(--color-primary); outline-offset: -2px; }
.photo-cell-date {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 2px 4px;
  font-size: var(--text-xs);
  color: #fff;
  background: linear-gradient(transparent, rgba(0, 0, 0, .55));
  text-align: left;
}

.photo-full {
  width: 100%;
  max-height: 62vh;
  object-fit: contain;
  border-radius: var(--radius-sm);
  background: var(--color-surface-raised);
}
.photo-caption { text-transform: capitalize; }

.compare-panes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
}
.compare-pane { margin: 0; }
.compare-pane img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--color-surface-raised);
}
.compare-pane figcaption {
  display: flex;
  justify-content: space-between;
  padding-top: var(--space-1);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.compare-pane figcaption b { color: var(--color-text); }


/* ---- the workout's progress bar --------------------------------------------
   Deliberately mute: a 4px track pinned to the top of the screen for the whole
   workout. No numbers, no animation — it fills as sets are checked and that is
   the whole story. The box-shadow is a page-coloured halo so the cards
   scrolling underneath vanish behind it instead of grazing it. */

.session-progress {
  position: sticky;
  top: var(--inset-top);
  z-index: 4;   /* over the cards; a dragged card (z 5) still passes above */
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--color-border);
  overflow: hidden;
  box-shadow: 0 0 0 6px var(--color-ink);
}
.session-progress-fill {
  height: 100%;
  border-radius: inherit;
  background: var(--color-primary);
}

.notice-busy {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
