.focus-view{display:grid;gap:12px}
.focus-superset-inline{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:12px;padding-bottom:12px;border-bottom:var(--hair) solid var(--sep);color:var(--acc);font-size:13px}
.focus-superset-inline strong{font-weight:600}
.focus-superset-inline span{color:var(--label-2)}
.focus-superset-inline .icn{font-size:14px}
.focus-superset-inline .btn{min-height:32px;padding-inline:8px;color:var(--acc)}
.focus-superset-nav{display:flex;margin-left:auto}
.focus-superset-nav button{display:grid;place-items:center;width:44px;height:44px;color:var(--label-2)}
.focus-superset-nav button:disabled{opacity:.3}
.focus-card{padding:16px;border-radius:var(--r-lg);background:var(--surface);box-shadow:inset 0 0 0 1.5px var(--acc-line)}
.focus-card-header{display:flex;align-items:center;gap:6px;padding-bottom:12px;border-bottom:var(--hair) solid var(--sep)}
.focus-card-header h2{flex:1;font-size:20px;line-height:1.2;text-transform:capitalize}
.focus-warmup{font-size:16px}
/* The set on screen is taken to failure: the same purple "F" the set rows wear. */
.focus-failure{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 4px;border-radius:10px;background:var(--purple);color:#fff;font-size:12px;font-weight:700}
.focus-context{display:grid;gap:5px;margin-top:10px}
.focus-tags{display:flex;flex-wrap:wrap;gap:5px}
.focus-note,.focus-last,.focus-guidance{color:var(--label-2);font-size:12px;line-height:1.35}
.focus-note{padding-left:8px;border-left:var(--hair) solid var(--sep)}
.focus-note.pinned{color:var(--yellow);display:flex;gap:5px;align-items:flex-start}
.focus-note .icn{margin-top:1px;flex:none}
.focus-guidance{display:flex;align-items:center;gap:5px;width:max-content;text-align:left;color:var(--acc);font-weight:600}
.focus-guidance .icn{font-size:13px}
.focus-prescription{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:14px 0}
.focus-badge{display:inline-flex;align-items:center;gap:5px;max-width:100%;min-height:30px;padding:6px 11px;border-radius:99px;background:var(--surface-2);color:var(--label);font-size:14px;font-weight:500;line-height:1.2;white-space:nowrap}
.focus-badge .icn{font-size:13px;flex:none}
.focus-progress{padding-inline:12px;background:var(--acc);color:var(--on-acc);font-weight:600}
.focus-badge.plan{background:var(--acc-soft);color:var(--acc);white-space:normal}
.focus-badge.warning{background:color-mix(in srgb,var(--orange) 14%,transparent);color:var(--orange);white-space:normal}
.focus-badge .short{color:var(--orange)}
.focus-badge-action{margin-inline-start:2px;color:inherit;font-size:13px;font-weight:700;text-decoration:underline;text-underline-offset:2px}
.focus-set>.stp-w,.focus-side>.stp-w{margin-top:12px}
.focus-set.complete .stp .num:disabled,.focus-set.locked .stp .num:disabled,.focus-set.complete .focus-timed strong,.focus-set.locked .focus-timed strong{color:color-mix(in srgb,var(--label) 48%,var(--surface))!important;-webkit-text-fill-color:color-mix(in srgb,var(--label) 48%,var(--surface))!important;opacity:1}
.focus-lock-note{margin:-4px 2px 2px;color:var(--label-2);font-size:12px;line-height:1.35}
.focus-effort{width:100%;margin-top:16px}
.focus-card>.btn.primary{margin-top:18px;min-height:48px}
.focus-secondary{display:grid;grid-template-columns:1fr 44px;gap:8px;margin-top:8px}
.focus-secondary>.iconbtn{width:44px;height:44px;border:1px solid var(--sep);border-radius:8px}
.focus-skip{display:flex;align-items:center;justify-content:center;gap:6px;min-height:44px;border:1px solid var(--sep);border-radius:8px;color:var(--label)}
.focus-pager{display:grid;grid-template-columns:44px 1fr 44px;align-items:center;gap:8px}
.focus-pager>button{display:grid;place-items:center;width:44px;height:44px;border:1px solid var(--sep);border-radius:50%;color:var(--label)}
.focus-pager>button:disabled{opacity:.3}
.focus-dots{display:flex;align-items:center;justify-content:center;gap:4px}
.focus-dots button{position:relative;width:44px;height:44px}
.focus-dots button::after{content:'';position:absolute;left:50%;top:50%;width:6px;height:6px;border-radius:50%;background:var(--label-3);transform:translate(-50%,-50%)}
.focus-dots button.active::after{width:9px;height:9px;background:var(--acc)}
.focus-dots button.done::after{box-shadow:0 0 0 2px var(--acc-line)}
.focus-sides{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.focus-side{min-width:0;padding:10px;border-radius:var(--r);background:var(--surface-2)}
.focus-side-header{display:flex;align-items:center;justify-content:space-between}
.focus-side-check{display:grid;place-items:center;width:44px;height:44px;border-radius:50%;color:var(--label-3)}
.focus-side-check.done{background:var(--acc);color:var(--on-acc)}
.focus-timed{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:12px;padding:12px;border-radius:var(--r);background:var(--surface-2)}
.focus-timed .btn{width:auto;min-width:120px}
.focus-extras{display:grid;gap:8px;margin-top:12px}
.focus-extra{display:grid;grid-template-columns:minmax(56px,auto) 1fr 1fr;align-items:end;gap:8px;padding:8px;border-radius:var(--r-sm);background:var(--surface-2)}
.focus-extra .stp-l{font-size:11px}
@media (max-width:400px){
  .focus-sides{grid-template-columns:1fr}
  .focus-extra{grid-template-columns:1fr 1fr}
  .focus-extra>span:first-child{grid-column:1/-1}
}

/* Admin dashboard — operator-facing, English-only. Sits on top of the app's tokens; nothing
   here is used outside views/Admin.jsx and views/AdminCoach.jsx. The page is looked at maybe
   twice a year, so every card says what it is for in a sentence and every state is a word, not
   a colour to decode. */

.adm-intro{color:var(--label-2);font-size:14px;line-height:1.45;margin:-6px 0 16px}

/* one sentence under a card heading that says what the card is for */
.adm-lead{color:var(--label-2);font-size:13px;line-height:1.45;margin:2px 0 12px}

/* status pill: a word and a colour, never a paragraph */
.adm-pill{
  display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:999px;
  font-size:12.5px;font-weight:600;letter-spacing:-.004em;line-height:1.5;white-space:nowrap;
  background:var(--surface-2);color:var(--label-2)
}
.adm-pill::before{content:'';width:7px;height:7px;border-radius:50%;background:currentColor;flex:none}
.adm-pill.ok{background:color-mix(in srgb,var(--green) 16%,transparent);color:var(--green)}
.adm-pill.warn{background:color-mix(in srgb,var(--yellow) 18%,transparent);color:var(--yellow)}
.adm-pill.bad{background:color-mix(in srgb,var(--red) 16%,transparent);color:var(--red)}
.adm-pill.acc{background:var(--acc-soft);color:var(--acc)}

/* the one-line "what state is this in" under the card title */
.adm-status{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:14px;color:var(--label-2);margin:4px 0 14px;line-height:1.4}

/* numbered setup steps: a <details> per step, so a finished step folds away */
.adm-step{border-top:var(--hair) solid var(--sep);padding:2px 0}
.adm-step:first-of-type{border-top:0}
.adm-step>summary{
  list-style:none;cursor:pointer;display:flex;align-items:center;gap:12px;min-height:50px;padding:6px 0;
  -webkit-tap-highlight-color:transparent
}
.adm-step>summary::-webkit-details-marker{display:none}
.adm-step>summary:active{opacity:.7}
.adm-num{
  width:26px;height:26px;border-radius:50%;flex:none;display:grid;place-items:center;
  font-size:13px;font-weight:700;background:var(--surface-3);color:var(--label)
}
.adm-step.done .adm-num{background:var(--acc);color:var(--on-acc)}
.adm-step.todo .adm-num{background:var(--surface-3);color:var(--label-2)}
.adm-step-t{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.adm-step-t b{font-size:16px;font-weight:600;letter-spacing:-.012em;line-height:1.25}
.adm-step-t span{font-size:13px;color:var(--label-2);line-height:1.3}
.adm-step>summary .chev{color:var(--label-3);font-size:14px;transition:transform .18s ease;flex:none}
.adm-step[open]>summary .chev{transform:rotate(90deg)}
.adm-step-b{padding:2px 0 14px 38px}
@media (max-width:380px){.adm-step-b{padding-left:0}}

/* a dim explainer that reads like a sentence, not a footnote */
.adm-hint{font-size:13px;color:var(--label-2);line-height:1.45;margin:6px 0 10px}
.adm-hint code{font-size:12px;background:var(--surface-2);padding:1px 5px;border-radius:5px}

/* provider chips grouped under a small heading */
.adm-group{margin:0 0 12px}
.adm-group-t{font-size:11.5px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--label-3);margin:0 0 7px}
.adm-chips{display:flex;flex-wrap:wrap;gap:8px}
.adm-chips .chip{min-height:38px;display:inline-flex;align-items:center;gap:7px}
.adm-chip-key{
  font-size:10.5px;font-weight:700;letter-spacing:.02em;padding:1px 6px;border-radius:999px;
  background:var(--acc-soft);color:var(--acc)
}
.chip.on .adm-chip-key{background:rgba(0,0,0,.18);color:inherit}

/* label + control pairs inside a step */
.adm-field{display:flex;flex-direction:column;gap:6px;margin:0 0 10px}
.adm-field>label{font-size:13px;font-weight:600;color:var(--label-2)}
/* Inside a card the app's default field colour is the card's own colour, so the box was
   invisible and the placeholder floated in space. One shade up, and a size that is readable
   without iOS zooming into it. */
.adm-field .field{width:100%;background:var(--surface-2);font-size:16px;min-height:44px}
.adm-select{
  width:100%;min-height:44px;padding:10px 38px 10px 12px;border-radius:var(--r);border:0;
  background:var(--surface-2);color:var(--label);font-size:15px;appearance:none;-webkit-appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,var(--label-2) 50%),linear-gradient(135deg,var(--label-2) 50%,transparent 50%);
  background-position:calc(100% - 20px) 50%,calc(100% - 14px) 50%;background-size:6px 6px;background-repeat:no-repeat
}
.adm-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:6px}
.adm-actions .btn{min-height:44px}

/* inline result box under the Test button */
.adm-result{
  margin-top:10px;padding:10px 12px;border-radius:var(--r);font-size:13.5px;line-height:1.45;
  background:var(--surface-2);color:var(--label)
}
.adm-result.ok{background:color-mix(in srgb,var(--green) 14%,transparent)}
.adm-result.bad{background:color-mix(in srgb,var(--red) 14%,transparent)}
.adm-result b{display:block;margin-bottom:2px}

/* advanced / activity folds */
.adm-fold{border-top:var(--hair) solid var(--sep);margin-top:4px}
.adm-fold>summary{
  list-style:none;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:10px;
  min-height:48px;padding:6px 0;font-size:15px;font-weight:600;letter-spacing:-.012em
}
.adm-fold>summary::-webkit-details-marker{display:none}
.adm-fold>summary .chev{color:var(--label-3);font-size:14px;transition:transform .18s ease}
.adm-fold[open]>summary .chev{transform:rotate(90deg)}
.adm-fold-b{padding:0 0 12px}

.adm-kv{display:flex;justify-content:space-between;gap:12px;padding:9px 0;border-bottom:var(--hair) solid var(--sep);font-size:14px}
.adm-kv:last-child{border-bottom:0}
.adm-kv .k{color:var(--label-2);flex:none}
.adm-kv .v{text-align:right;min-width:0;overflow-wrap:anywhere}
/* the two daily-limit numbers: a real field, not the browser's white default box */
.adm-kv .v input{
  width:84px;min-height:40px;padding:6px 10px;text-align:right;font-size:16px;font-weight:600;
  border:0;border-radius:10px;background:var(--surface-2);color:var(--label);outline:none;
  -moz-appearance:textfield;appearance:textfield
}
.adm-kv .v input::-webkit-outer-spin-button,.adm-kv .v input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.adm-kv .v input:focus{box-shadow:inset 0 0 0 2px var(--acc)}

.adm-log{display:flex;flex-direction:column}
.adm-log-row{display:flex;justify-content:space-between;gap:10px;padding:8px 0;border-bottom:var(--hair) solid var(--sep);font-size:13.5px}
.adm-log-row:last-child{border-bottom:0}
.adm-log-row .when{color:var(--label-3);font-size:12px;flex:none}

/* users list rows with a copy affordance for invite codes */
.adm-code{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600;letter-spacing:.08em;
  background:var(--surface-2);padding:4px 9px;border-radius:8px;font-size:14px
}
.adm-iconbtn{width:44px;height:44px;border-radius:12px;font-size:17px}
.adm-empty{color:var(--label-3);font-size:13.5px;padding:10px 2px}

/* ---------- the Coach before setup: a pitch, not a form ---------- */
.adm-hero{display:flex;flex-direction:column;align-items:flex-start;gap:4px;padding:6px 2px 2px}
.adm-hero-av{
  width:52px;height:52px;border-radius:16px;display:grid;place-items:center;font-size:26px;
  background:linear-gradient(135deg,var(--acc),color-mix(in srgb,var(--acc) 55%,var(--teal)));
  color:var(--on-acc);margin-bottom:8px;box-shadow:0 8px 24px -8px color-mix(in srgb,var(--acc) 55%,transparent)
}
.adm-hero h2{margin:0 0 4px;font-size:22px;letter-spacing:-.022em}
.adm-hero p{margin:0 0 14px;font-size:14px;line-height:1.5;color:var(--label-2)}
.adm-hero-feats{display:grid;gap:10px;margin-bottom:18px}
.adm-hero-feats>div{display:flex;gap:11px;align-items:flex-start}
.adm-hero-feats .icn{color:var(--acc);font-size:18px;flex:none;margin-top:2px}
.adm-hero-feats span{font-size:13.5px;line-height:1.45;color:var(--label-2)}
.adm-hero-feats b{color:var(--label);font-weight:600}
.adm-hero .btn{width:100%}

/* ---------- setup progress ---------- */
.adm-progress{height:5px;border-radius:99px;background:var(--surface-2);overflow:hidden;margin:0 0 10px}
.adm-progress i{display:block;height:100%;border-radius:99px;background:var(--acc);transition:width var(--med) var(--ease)}

/* ---------- provider chips: clearly idle vs. clearly chosen ---------- */
.adm-chips .chip{
  background:var(--surface-2);border:1.5px solid transparent;color:var(--label);
  font-weight:500;text-transform:none
}
.adm-chips .chip.on{background:var(--acc-soft);border-color:var(--acc);color:var(--acc);font-weight:600}
.adm-chips .chip:active{transform:scale(.97)}

/* a finished step's summary reads as a receipt, not a control */
.adm-step.done>summary .adm-step-t span{color:var(--acc)}

/* The Coach's two screens — the questionnaire and the conversation.
   Everything here sits on the app's tokens (index.css); nothing is redefined. */

/* ---------- onboarding ---------- */
.ob{display:flex;flex-direction:column;min-height:calc(100dvh - var(--sat) - 200px)}
.ob-top{display:flex;align-items:center;justify-content:space-between;margin:2px 0 26px;min-height:36px}
.ob-dots{display:flex;gap:6px;align-items:center}
.ob-dot{width:6px;height:6px;border-radius:99px;background:var(--label-4);transition:all var(--med) var(--ease)}
.ob-dot.on{background:var(--acc);width:18px}
.ob-dot.done{background:var(--acc);opacity:.55}
.ob-skip{background:none;color:var(--label-2);font-size:15px;padding:6px 2px}
.ob-body{flex:1;padding-bottom:150px}
.ob-eyebrow{color:var(--acc);font-size:13px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;margin-bottom:8px}
.ob-h{font-size:30px;line-height:1.12;font-weight:700;letter-spacing:-.026em;margin:0 0 8px}
.ob-p{color:var(--label-2);font-size:16px;line-height:1.45;margin:0 0 24px;letter-spacing:-.006em}
.ob-choices{display:flex;flex-direction:column;gap:10px}
.ob-choice{
  display:flex;align-items:center;gap:14px;width:100%;text-align:left;
  padding:16px 18px;border-radius:var(--r-lg);background:var(--surface);
  font-size:17px;letter-spacing:-.012em;color:var(--label);
  border:1.5px solid transparent;transition:border-color var(--fast),background var(--fast),transform var(--fast)
}
.ob-choice:active{transform:scale(.985);background:var(--surface-2)}
.ob-choice.on{border-color:var(--acc);background:var(--acc-soft)}
.ob-choice .icn{font-size:22px;color:var(--acc);flex:none}
.ob-choice .ob-choice-t{flex:1}
.ob-choice .ob-choice-s{display:block;font-size:13px;color:var(--label-2);margin-top:2px}
.ob-choice .ob-k{color:var(--acc);font-size:18px;--icon-stroke:2.6;flex:none;opacity:0}
.ob-choice.on .ob-k{opacity:1}
.ob-days{display:grid;grid-template-columns:repeat(7,1fr);gap:8px;margin:6px 0 4px}
.ob-day{
  aspect-ratio:1;border-radius:99px;background:var(--surface);color:var(--label);
  font-size:20px;font-weight:600;display:flex;align-items:center;justify-content:center;
  border:1.5px solid transparent;transition:all var(--fast)
}
.ob-day.on{background:var(--acc);color:var(--on-acc);border-color:var(--acc)}
.ob-day:active{transform:scale(.94)}
.ob-week{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;margin-top:6px}
.ob-wd{
  padding:10px 0 9px;border-radius:var(--r);background:var(--surface);text-align:center;
  font-size:13px;font-weight:600;color:var(--label-2);border:1.5px solid transparent;transition:all var(--fast)
}
.ob-wd.on{color:var(--acc);border-color:var(--acc);background:var(--acc-soft)}
.ob-sub{font-size:15px;color:var(--label-2);margin:22px 0 10px;letter-spacing:-.006em}
/* The days screen: the count and the weekdays each in their own box, with their own label. */
.ob-group{background:var(--surface);border-radius:14px;padding:12px 12px 14px;margin-top:14px}
.ob-group .ob-sub-first{margin:0 0 10px;font-weight:600;color:var(--label)}
.ob-group .ob-days,.ob-group .ob-week{margin:0}
.ob-group .ob-day:not(.on),.ob-group .ob-wd:not(.on){background:var(--surface-2)}
.ob-quick{display:flex;gap:7px;justify-content:center;flex-wrap:wrap;margin-top:12px}
.ob-chips{display:flex;flex-wrap:wrap;gap:8px}
.ob-chips .chip{padding:10px 16px;font-size:15px;border:1.5px solid transparent}
.ob-chips .chip.on{background:var(--acc-soft);color:var(--acc);border-color:var(--acc)}
.ob-field{margin-bottom:14px}
.ob-field .field{font-size:16px}
.ob-note{font-size:13px;line-height:1.45;color:var(--label-2);margin-top:10px}
.ob-note.warn{color:var(--yellow)}
.ob-consent{display:flex;flex-direction:column;gap:10px;margin-bottom:18px}
.ob-consent-row{display:flex;gap:12px;align-items:flex-start;background:var(--surface);border-radius:var(--r-lg);padding:13px 15px}
.ob-consent-row .icn{color:var(--acc);font-size:19px;flex:none;margin-top:2px}
.ob-consent-row b{display:block;font-size:15px;font-weight:600;margin-bottom:2px;letter-spacing:-.008em}
.ob-consent-row span{font-size:13px;color:var(--label-2);line-height:1.4}
.ob-fine{font-size:13px;color:var(--label-2);line-height:1.5;display:grid;gap:8px;margin-bottom:20px}
.ob-foot{position:sticky;bottom:calc(58px + var(--sab));padding:30px 0 14px;background:linear-gradient(to top,var(--bg) 58%,transparent);display:flex;gap:10px;margin-top:18px}
.ob-foot .btn{font-size:17px;padding:15px 18px;border-radius:var(--r-lg)}
.ob-hint{text-align:center;font-size:13px;color:var(--label-3);margin-top:8px}

/* ---------- chat ---------- */
.chat{padding-bottom:calc(40px + var(--composer-extra,0px))}
.chat-hdr{
  display:flex;align-items:center;gap:10px;
  /* Pinned like a messenger's title bar: it eats the app shell's top padding so the blur
     reaches the status bar, and gives it back as its own padding — below the connection
     indicator (--conn) when there is one. */
  position:sticky;top:0;z-index:40;
  margin:calc(-1 * (var(--sat) + var(--conn) + 8px)) calc(-1 * var(--pad)) 8px;
  padding:calc(var(--sat) + var(--conn) + 8px) var(--pad) 8px;
  background:color-mix(in srgb,var(--bg) 88%,transparent);
  -webkit-backdrop-filter:saturate(180%) blur(12px);backdrop-filter:saturate(180%) blur(12px)
}
.chat-hdr .chat-av{
  width:38px;height:38px;border-radius:99px;background:var(--acc);color:var(--on-acc);
  display:flex;align-items:center;justify-content:center;font-size:20px;flex:none
}
.chat-hdr h1{font-size:20px;font-weight:700;letter-spacing:-.02em;margin:0;line-height:1.1}
.chat-hdr .chat-st{font-size:13px;color:var(--label-2);margin-top:2px}
.chat-hdr .chat-st.live{color:var(--acc)}
.chat-hdr .grow{flex:1;min-width:0}
.msgs{display:flex;flex-direction:column;gap:10px}
.msg{display:flex;flex-direction:column;max-width:88%;animation:msg-in var(--med) var(--ease)}
.msg.coach{align-self:flex-start}
.msg.user{align-self:flex-end;align-items:flex-end}
.bub{
  padding:11px 15px;border-radius:20px;font-size:16px;line-height:1.42;letter-spacing:-.008em;
  background:var(--surface);color:var(--label);white-space:pre-wrap;word-break:break-word
}
.msg.coach .bub{border-bottom-left-radius:6px}
.msg.user .bub{background:var(--acc);color:var(--on-acc);border-bottom-right-radius:6px}
.bub.err{background:color-mix(in srgb,var(--red) 14%,var(--surface));color:var(--label)}
.bub.sys{background:none;color:var(--label-2);font-size:13px;text-align:center;padding:4px 8px;align-self:center;max-width:100%}
.msg.sys{align-self:center;max-width:100%;align-items:center}
.bub ul{margin:6px 0 0;padding-left:18px}
.bub li{margin:2px 0}
.msg-t{font-size:11px;color:var(--label-3);margin:4px 8px 0}
.typing{display:flex;align-items:center;gap:5px;padding:14px 16px;min-width:64px}
.typing i{width:8px;height:8px;border-radius:99px;background:var(--label-2);animation:typing 1.2s infinite ease-in-out}
.typing i:nth-child(2){animation-delay:.15s}
.typing i:nth-child(3){animation-delay:.3s}
.typing-eta{font-size:13px;color:var(--label-2);margin:6px 8px 0;line-height:1.4}
@keyframes typing{0%,80%,100%{transform:translateY(0);opacity:.45}40%{transform:translateY(-4px);opacity:1}}
@keyframes msg-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* the proposal, as a card inside the coach's column */
.pcard{
  background:var(--surface);border-radius:var(--r-xl);overflow:hidden;width:100%;
  border:1px solid var(--sep-op)
}
.pcard-hd{padding:16px 18px 6px}
.pcard-eyebrow{font-size:12px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--acc);margin-bottom:4px}
.pcard-h{font-size:22px;font-weight:700;letter-spacing:-.022em;line-height:1.15;margin:0}
.pcard-sum{font-size:15px;color:var(--label-2);line-height:1.45;margin:8px 0 0}
.pcard-tabs{display:flex;gap:6px;overflow-x:auto;padding:12px 18px 4px;scrollbar-width:none}
.pcard-tabs::-webkit-scrollbar{display:none}
.pcard-tab{
  flex:none;display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:99px;background:var(--surface-2);color:var(--label-2);
  font-size:14px;font-weight:500;transition:all var(--fast);white-space:nowrap
}
.pcard-tab.on{background:var(--label);color:var(--bg)}
.pcard-rt{padding:10px 18px 4px}
.pcard-rt-h{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
.pcard-rt-h b{display:inline-flex;align-items:center;gap:8px;font-size:18px;font-weight:600;letter-spacing:-.015em}
.pcard-rt-h span{font-size:13px;color:var(--label-2);white-space:nowrap}
.pcard-why{font-size:14px;color:var(--label-2);line-height:1.45;margin:4px 0 8px}
.pcard-ex{padding:11px 0;border-top:var(--hair) solid var(--sep)}
.pcard-ex:first-of-type{border-top:none}
.pcard-ex-r{display:flex;justify-content:space-between;gap:10px;align-items:baseline}
.pcard-ex-n{font-size:16px;font-weight:500;letter-spacing:-.01em;text-transform:capitalize}
.pcard-ex-l{font-size:14px;color:var(--label-2);white-space:nowrap;font-variant-numeric:tabular-nums}
.pcard-ex-w{font-size:13px;color:var(--label-2);line-height:1.42;margin-top:3px}
.pcard-week{display:flex;gap:5px;padding:10px 18px 4px}
.pcard-wd{flex:1;text-align:center;font-size:11px;font-weight:600;padding:7px 0;border-radius:var(--r-sm);background:var(--surface-2);color:var(--label-3)}
.pcard-wd.on{background:var(--acc-soft);color:var(--acc)}
.pcard-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 18px;border-top:var(--hair) solid var(--sep)}
.pcard-row .lrow-t{font-size:15px}
.pcard-row .lrow-s{font-size:12px}
.pcard-ft{padding:12px 18px 16px;display:flex;flex-direction:column;gap:8px;border-top:var(--hair) solid var(--sep)}
.pcard-ft .btn{font-size:16px;padding:14px 18px;border-radius:var(--r-lg)}
.pcard-note{font-size:12px;color:var(--yellow);line-height:1.45;padding:0 18px 6px}
.pcard-chg{display:flex;gap:12px;align-items:flex-start;padding:12px 18px;border-top:var(--hair) solid var(--sep)}
.pcard-chg.stale{opacity:.5}
.pcard-chg .grow{flex:1;min-width:0}
.pcard-chg-t{font-size:15px;font-weight:600;letter-spacing:-.01em}
.pcard-chg-v{display:flex;align-items:center;gap:6px;margin:4px 0;font-size:13px}
.pcard-chg-v .tag{font-size:12px}
.pcard-chg-w{font-size:13px;color:var(--label-2);line-height:1.42}
.pcard-chg-stale{font-size:12px;color:var(--yellow);margin-top:4px}
.pcard-notes{padding:10px 18px 4px;border-top:var(--hair) solid var(--sep)}
.pcard-notes div{font-size:13px;color:var(--label-2);line-height:1.45;padding:4px 0}

/* composer */
.composer{
  position:fixed;left:0;right:0;bottom:0;z-index:40;
  padding:8px var(--pad) calc(10px + var(--sab));
  background:color-mix(in srgb,var(--bg) 88%,transparent);
  -webkit-backdrop-filter:saturate(180%) blur(12px);backdrop-filter:saturate(180%) blur(12px)
}
/* The Android app reports the keyboard instead of shrinking the page (lib/native-keyboard.js):
   the composer rides on top of it. */
:root[data-native-kb] .composer{bottom:var(--native-kb);padding-bottom:10px}
.composer-in{max-width:560px;margin:0 auto;display:flex;gap:8px;align-items:flex-end;position:relative}
/* The height is set by components/ChatInput.jsx: as tall as the text, up to 40% of the screen
   above the keyboard, or 70% opened into the tall editor. */
.composer textarea{
  flex:1;min-height:44px;resize:none;border-radius:22px;padding:11px 16px;overflow-y:auto;
  font-size:16px;line-height:1.35;background:var(--surface);color:var(--label);border:none;outline:none;
  font-family:inherit;letter-spacing:-.008em
}
.composer textarea:focus{box-shadow:inset 0 0 0 1.5px var(--acc)}
.composer-in.tall textarea{border-radius:18px}
/* The toggle between the growing box and the tall editor, on the box's top corner. */
.composer-grow{
  position:absolute;top:-30px;left:4px;width:28px;height:28px;border-radius:99px;
  display:flex;align-items:center;justify-content:center;font-size:15px;
  background:var(--surface);color:var(--label-2);box-shadow:0 1px 3px rgba(0,0,0,.18)
}
.composer textarea::placeholder{color:var(--label-3)}
.send{
  width:44px;height:44px;border-radius:99px;background:var(--acc);color:var(--on-acc);flex:none;
  display:flex;align-items:center;justify-content:center;font-size:20px;transition:transform var(--fast),opacity var(--fast)
}
.send:active{transform:scale(.92)}
.send:disabled{opacity:.3}
.composer-cap{max-width:560px;margin:4px auto 0;text-align:center;font-size:11px;color:var(--label-3)}

/* menu sheet rows reuse .lrow; the cadence sub-sheet reuses Section */
.chat-menu .lrow-t{font-size:17px}

/* ---------- quick actions above the composer ---------- */
.chips-row{max-width:560px;margin:0 auto 8px;display:flex;gap:6px;overflow-x:auto;scrollbar-width:none;padding:0 2px}
.chips-row::-webkit-scrollbar{display:none}
.qchip{
  flex:none;display:inline-flex;align-items:center;gap:6px;padding:8px 13px;border-radius:99px;
  background:var(--surface);color:var(--label);font-size:13px;font-weight:500;letter-spacing:-.006em;
  border:1px solid var(--sep-op);transition:transform var(--fast),background var(--fast)
}
.qchip .icn{font-size:14px;color:var(--acc)}
.qchip:active{transform:scale(.96);background:var(--surface-2)}
.chat-empty{font-size:14px;color:var(--label-2);line-height:1.45;padding:14px 4px}

/* ---------- the numbers behind a card ---------- */
.ins{padding:6px 18px 4px;display:grid;gap:10px;min-width:0}
.ins>*{min-width:0}
.ins-tiles{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}
.ins-tile{background:var(--surface-2);border-radius:14px;padding:10px 6px 9px;text-align:center;min-width:0}
.ins-tile-v{font-size:19px;font-weight:700;letter-spacing:-.03em;font-variant-numeric:tabular-nums;line-height:1.05;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ins-tile-l{font-size:10.5px;color:var(--label-2);margin-top:4px;letter-spacing:-.004em;line-height:1.15;overflow-wrap:anywhere}
.ins-block{background:var(--surface-2);border-radius:16px;padding:12px 14px 10px}
.ins-h{display:flex;justify-content:space-between;align-items:baseline;gap:8px;font-size:13px;font-weight:600;letter-spacing:-.008em;margin-bottom:6px;min-width:0}
.ins-h>span:first-child{flex:none}
.ins-h>span:last-child{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:right}
.ins-h .dim{font-weight:400;font-size:12px}
.ins-block .chart{margin:2px -4px 0}
.ins-delta{font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--label-2);white-space:nowrap;flex:none}
.ins-delta.up{color:var(--green)}
.ins-delta.down{color:var(--red)}
.ins-row{display:flex;align-items:center;gap:8px;padding:7px 0;border-top:var(--hair) solid var(--sep)}
.ins-row:first-of-type{border-top:none}
.ins-row-n{flex:1;min-width:0;font-size:14px;letter-spacing:-.008em;text-transform:capitalize;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ins-row-n .dim{font-size:12px;text-transform:none}
.ins-row-v{display:flex;align-items:center;gap:4px;font-size:12px;flex:none}
.ins-row-v .icn{font-size:11px;color:var(--label-3)}
.ins-row-v .tag{font-size:12px;font-variant-numeric:tabular-nums}
.ins-bar{display:grid;grid-template-columns:76px 1fr 28px;align-items:center;gap:8px;padding:4px 0}
.ins-bar-l{font-size:12.5px;color:var(--label-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ins-bar-t{height:8px;border-radius:99px;background:var(--surface-3);overflow:hidden}
.ins-bar-t i{display:block;height:100%;border-radius:99px;background:var(--acc);transition:width var(--med) var(--ease)}
.ins-bar-v{font-size:12px;font-weight:600;text-align:right;font-variant-numeric:tabular-nums}
.ins-cmp{font-size:12.5px;color:var(--label-2);line-height:1.5;display:flex;flex-wrap:wrap;gap:4px 10px;align-items:baseline;padding:0 2px}
.pcard-notes div{display:flex;gap:8px;align-items:flex-start}
.pcard-notes .icn{color:var(--yellow);flex:none;font-size:15px;margin-top:1px}

/* ---------- a workout debrief ---------- */
.deb-score{display:flex;align-items:center;gap:14px;padding:6px 18px 10px}
.deb-ring{
  --pct:.5;width:64px;height:64px;border-radius:99px;flex:none;display:flex;align-items:baseline;justify-content:center;
  background:conic-gradient(var(--acc) calc(var(--pct) * 360deg),var(--surface-3) 0);position:relative
}
.deb-ring::before{content:"";position:absolute;inset:6px;border-radius:99px;background:var(--surface)}
.deb-ring span{position:relative;font-size:24px;font-weight:700;letter-spacing:-.03em;line-height:64px}
.deb-ring small{position:relative;font-size:11px;color:var(--label-2);line-height:64px;margin-left:1px}
.deb-score-t{display:flex;flex-direction:column;gap:2px;min-width:0}
.deb-score-t b{font-size:17px;font-weight:600;letter-spacing:-.015em}
.deb-score-t span{font-size:13px;color:var(--label-2)}
.deb-list{padding:8px 18px 4px}
.deb-list-h{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;margin-bottom:4px}
.deb-list-h .icn{font-size:15px}
.deb-item{font-size:14.5px;line-height:1.45;letter-spacing:-.008em;padding:6px 0 6px 22px;position:relative;border-top:var(--hair) solid var(--sep)}
.deb-item:first-of-type{border-top:none}
.deb-item::before{content:"";position:absolute;left:8px;top:14px;width:5px;height:5px;border-radius:99px;background:var(--label-3)}

/* ---------- a decided proposal, folded into the thread ---------- */
.recap{
  display:block;width:100%;text-align:left;margin-top:6px;padding:14px 16px 12px;border-radius:var(--r-xl);
  background:var(--surface);border:1px solid var(--sep-op);color:var(--label);transition:transform var(--fast),background var(--fast)
}
.recap:active{transform:scale(.99);background:var(--surface-2)}
.recap-top{display:flex;justify-content:space-between;align-items:center;gap:8px}
.recap-top .pcard-eyebrow{margin:0}
.recap-state{font-size:11px;font-weight:600;padding:3px 9px;border-radius:99px;background:var(--surface-2);color:var(--label-2);white-space:nowrap;flex:none}
.recap-state.yes{background:color-mix(in srgb,var(--green) 16%,transparent);color:var(--green)}
.recap-state.no{background:color-mix(in srgb,var(--red) 14%,transparent);color:var(--red)}
.recap-state.mix{background:var(--acc-soft);color:var(--acc)}
.recap-t{font-size:17px;font-weight:600;letter-spacing:-.018em;margin-top:6px;line-height:1.2}
.recap-s{font-size:13.5px;color:var(--label-2);line-height:1.42;margin-top:4px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.recap-chips{display:flex;flex-wrap:wrap;gap:5px;margin-top:9px}
.recap-chip{display:inline-flex;align-items:center;gap:4px;font-size:12px;padding:4px 9px;border-radius:99px;background:var(--surface-2);color:var(--label-2);max-width:100%}
.recap-chip .icn{font-size:11px;flex:none}
.recap-chip.yes .icn{color:var(--green)}
.recap-chip.no{text-decoration:line-through;opacity:.75}
.recap-chip.no .icn{color:var(--red)}
.recap-more{display:flex;align-items:center;gap:3px;font-size:13px;color:var(--acc);font-weight:500;margin-top:10px}
.recap-more .icn{font-size:12px}
.pcard-chg.declined .pcard-chg-t{color:var(--label-2)}
.pcard-chg .recap-state{align-self:center}
.pdetail .pcard-chg,.pdetail .pcard-notes,.pdetail .ins{padding-left:0;padding-right:0}
.pdetail .pcard-rt{padding-left:0;padding-right:0}
.pdetail .pcard-week{padding-left:0;padding-right:0}

/* ---------- you against the room ---------- */
.cmp{padding:8px 0;border-top:var(--hair) solid var(--sep)}
.cmp:first-of-type{border-top:none}
.cmp-h{display:flex;justify-content:space-between;font-size:13.5px;font-weight:500;text-transform:capitalize;margin-bottom:5px}
.cmp-h .dim{font-weight:400;font-size:12px;text-transform:none}
.cmp-bar{position:relative;height:22px;border-radius:7px;background:var(--surface-3);overflow:hidden;margin-top:4px}
.cmp-bar i{display:block;height:100%;border-radius:7px;background:var(--label-3);opacity:.55}
.cmp-bar.you i{background:var(--acc);opacity:1}
.cmp-bar span{position:absolute;left:8px;top:0;line-height:22px;font-size:11.5px;font-weight:600;color:var(--label);font-variant-numeric:tabular-nums;text-shadow:0 0 6px var(--bg)}

/* ============================================================================
   openGym — design system
   ----------------------------------------------------------------------------
   Rebuilt around four rules, each aimed at a specific "generated" tell in the
   previous sheet:

   1. ONE TYPE SCALE, MOSTLY REGULAR WEIGHT. The old sheet set font-weight 700/800
      on nearly every element, which flattens hierarchy — when everything shouts,
      position is the only cue left. Here weight carries meaning: 600 for a title,
      400 for its content, and size does the rest.
   2. A NEUTRAL SURFACE RAMP. Cards are near-neutral greys a few points apart, not
      saturated blue-greys. Colour is spent only on the accent and on status.
   3. HAIRLINES, NOT BORDERS. Separators are sub-pixel and inset past the icon
      rail, so a list reads as one object instead of a stack of outlined boxes.
   4. MOTION THAT ACKNOWLEDGES, NOT ANIMATES. Presses scale ~2%, transitions run
      140–220ms on an ease-out curve. Nothing bounces or slides for decoration.
   ========================================================================== */

/* ---------------------------------------------------------------- tokens -- */
:root {
  /* surfaces — dark is the default */
  --bg:        #000000;
  --bg-el:     #0e0e10;   /* elevated backdrop (sheets, bars) */
  --surface:   #1c1c1e;   /* card / grouped-list fill */
  --surface-2: #2c2c2e;   /* pressed + nested fill */
  --surface-3: #3a3a3c;   /* controls (segmented track, switch off) */

  /* text */
  --label:     #ffffff;
  --label-2:   rgba(235,235,245,.60);
  --label-3:   rgba(235,235,245,.32);
  --label-4:   rgba(235,235,245,.18);

  /* lines */
  --sep:       rgba(84,84,88,.60);
  --sep-op:    rgba(84,84,88,.34);   /* for edges sitting over blur */
  --hair:      .5px;

  /* system palette (iOS dark values) */
  --blue:#0a84ff; --green:#30d158; --red:#ff453a; --orange:#ff9f0a;
  --yellow:#ffd60a; --teal:#40c8e0; --indigo:#5e5ce6; --pink:#ff375f;
  --purple:#bf5af2; --mint:#63e6e2; --brown:#ac8e68; --grey:#8e8e93;

  /* accent (per-account, synced) */
  --acc:  var(--green);
  --acc-2:#248a3d;
  --on-acc:#000000;
  --acc-soft: color-mix(in srgb, var(--acc) 16%, transparent);
  --acc-line: color-mix(in srgb, var(--acc) 38%, transparent);

  /* geometry */
  --r-sm:8px; --r:12px; --r-lg:16px; --r-xl:22px; --r-card:14px;
  --pad:16px;
  --icon-stroke:1.7;

  /* motion */
  --ease:cubic-bezier(.32,.72,0,1);
  --fast:140ms; --med:220ms;

  /* The system bars the page is drawn under. env() is all a browser and the iOS app give; the
     Android WebView reports only the camera cutout there, and on Android 15, which draws the app
     under the status and navigation bars, the app passes the bars themselves as --native-sat and
     --native-sab (MainActivity.java). The larger one wins, so a WebView that learns to report
     the bars does not count them twice. */
  --sab:max(env(safe-area-inset-bottom,0px),var(--native-sab,0px));
  --sat:max(env(safe-area-inset-top,0px),var(--native-sat,0px));
  /* height of the connection indicator under the status bar, 0 while there is none — set by
     components/SyncBanner.jsx. Whatever starts at the top of the screen starts below it. */
  --conn:0px;
}

:root[data-theme="light"] {
  --bg:        #f2f2f7;
  --bg-el:     #f7f7fa;
  --surface:   #ffffff;
  --surface-2: #ececef;
  --surface-3: #e3e3e8;

  --label:     #000000;
  --label-2:   rgba(60,60,67,.60);
  --label-3:   rgba(60,60,67,.30);
  --label-4:   rgba(60,60,67,.16);

  --sep:       rgba(60,60,67,.29);
  --sep-op:    rgba(60,60,67,.20);

  --blue:#007aff; --green:#34c759; --red:#ff3b30; --orange:#ff9500;
  --yellow:#ffcc00; --teal:#30b0c7; --indigo:#5856d6; --pink:#ff2d55;
  --purple:#af52de; --mint:#00c7be; --brown:#a2845e; --grey:#8e8e93;
  --acc-2:#248a3d;
  --on-acc:#ffffff;
}

/* accents — Apple's system colours, so a recolour never looks off-system */
:root[data-accent="lime"]  { --acc:var(--green);  --acc-2:#248a3d; --on-acc:#000 }
:root[data-accent="sky"]   { --acc:var(--blue);   --acc-2:#0060df; --on-acc:#fff }
:root[data-accent="orange"]{ --acc:var(--orange); --acc-2:#c76b00; --on-acc:#000 }
:root[data-accent="violet"]{ --acc:var(--purple); --acc-2:#8944ab; --on-acc:#fff }
:root[data-accent="pink"]  { --acc:var(--pink);   --acc-2:#d70036; --on-acc:#fff }
:root[data-accent="red"]   { --acc:var(--red);    --acc-2:#d70015; --on-acc:#fff }
:root[data-accent="teal"]  { --acc:var(--teal);   --acc-2:#0071a4; --on-acc:#000 }
:root[data-accent="gold"]  { --acc:var(--yellow); --acc-2:#b25000; --on-acc:#000; --knob-ring:rgba(0,0,0,.28) }
/* Label colour on a filled accent button is picked per accent by measured contrast,
   not by taste. Button text is 17px/600, so the AA bar is 3:1 (large text). White on
   the light-theme green is 2.22:1 and on teal 2.57:1 — both fail, and green is the
   DEFAULT accent, so every primary button in light mode was failing. Black scores
   9.46 / 8.16 there. Blue, purple, pink and red keep white (3.5–4.1:1), which both
   passes and matches the platform convention. */
:root[data-theme="light"][data-accent="lime"],
:root[data-theme="light"][data-accent="teal"]  { --on-acc:#000 }
:root[data-theme="light"][data-accent="sky"],
:root[data-theme="light"][data-accent="violet"],
:root[data-theme="light"][data-accent="pink"],
:root[data-theme="light"][data-accent="red"]   { --on-acc:#fff }

/* ---------------------------------------------------------------- reset -- */
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html{
  font-size:16px;background:var(--bg);overscroll-behavior:none;overflow-x:clip;
  -webkit-text-size-adjust:100%;
}
body{
  background:var(--bg);color:var(--label);
  font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text','SF Pro Display','Segoe UI',Roboto,system-ui,sans-serif;
  font-size:17px;line-height:1.29;letter-spacing:-.01em;
  min-height:100vh;min-height:100dvh;overscroll-behavior-y:none;overflow-x:clip;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  touch-action:manipulation;
  font-variant-numeric:tabular-nums;
}
button{font:inherit;color:inherit;background:none;border:none;cursor:pointer;letter-spacing:inherit}
input,select,textarea{font:inherit;color:var(--label);letter-spacing:inherit}
img{max-width:100%}
a{color:var(--acc);text-decoration:none}
:focus{outline:none}
:focus-visible{outline:2.5px solid var(--acc);outline-offset:2px;border-radius:6px}
button,.chip,.tile,.item,.wday,.cal-d,.swatch,.hm-c,.lrow,.seg button,.stp button,.sld
{-webkit-user-select:none;user-select:none}

/* icons inherit colour and optical size from their context */
.icn{
  width:1em;height:1em;flex:none;display:block;
  fill:none;stroke:currentColor;stroke-width:var(--icon-stroke);
  stroke-linecap:round;stroke-linejoin:round;
}

@media (prefers-reduced-motion: reduce){*{animation:none!important;transition:none!important}}

/* ------------------------------------------------------------ type scale -- */
/* The nine .t-* classes that used to live here emitted from no JSX in the app, while the views
   typed their numbers inline (Home.jsx:223 is .t-title2 with a different weight). Deleted
   2026-09-22 rather than adopted, because adopting a scale means changing what four screens
   render, which is a design call and not a cleanup. `git log -- src/index.css` has them. */

.muted{color:var(--label-2)} .dim{color:var(--label-3)}
.small{font-size:13px;line-height:1.38}
.accent{color:var(--acc)}
.capitalize{text-transform:capitalize}
/* exercise names and body-part/equipment terms arrive lowercase from the dataset,
   so tags and chips capitalise by default. Anything that is a unit or a sentence opts
   back out — blanket-capitalising turned "60 kg" into "60 Kg" and a whole hint into
   Title Case. The compound selectors are needed: .tag/.chip come later in this file
   and would otherwise win on source order. */
.nocap,.tag.nocap,.chip.nocap{text-transform:none}

/* ---------------------------------------------------------------- layout -- */
#app{
  padding:calc(var(--sat) + var(--conn) + 8px) var(--pad) calc(88px + var(--sab));
  max-width:560px;margin:0 auto;
}
/* Android 15 with the keyboard up: room to scroll the last field on a page above the keys.
   The tab bar stays where it is, under the keyboard, rather than riding up on it. */
:root[data-native-kb] #app,:root[data-native-kb] body.resting #app,:root[data-native-kb] body.no-tabbar.resting #app{padding-bottom:calc(88px + var(--native-kb))}
/* Keep the last workout controls clear of the mobile overlays without adding a second
   screenful of blank scroll space. The stacked rest timer needs the larger reserve. */
body.resting #app{padding-bottom:calc(166px + var(--sab))}
/* The workout screen has no tab bar (App.jsx): only the docked rest bar, when it is there. */
body.no-tabbar #app{padding-bottom:calc(24px + var(--sab))}
body.no-tabbar.resting #app{padding-bottom:calc(96px + var(--sab))}
.workout-end-spacer{height:0}
@keyframes viewfade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
#app > .page.vfade{animation:viewfade var(--med) var(--ease)}
/* The page an edge swipe back returns to (App.jsx Page `under`, lib/edge-swipe-back.js): laid
   out where #app lays out a page, fixed under the current one, at the scroll position it was
   left at (.page-in is moved up by it), hidden until the finger has committed to the swipe. */
#app > .page.under{
  position:fixed;top:0;left:0;right:0;bottom:0;z-index:0;overflow:hidden;
  padding:inherit;max-width:inherit;margin:0 auto;
  visibility:hidden;pointer-events:none;will-change:transform;
}

/* Connection indicator (components/SyncBanner.jsx): pinned under the status bar for as long as
   the app is not connected to a server. It carries the page's own ground and blur, like the
   pinned workout header, so rows scrolling under it read as under it; --conn is the height of
   its row, which #app and the pinned headers leave free. Above those headers (20, 40), below the
   tab bar (50), the timer and every sheet. The sentence is never cut: its last words are the ones
   that say the changes are kept, so a narrow phone gets a third line rather than an ellipsis. */
.conn-bar{
  position:fixed;inset-inline-start:0;inset-inline-end:0;top:0;z-index:45;
  padding:var(--sat) var(--pad) 0;
  background:color-mix(in srgb,var(--bg) 94%,transparent);
  -webkit-backdrop-filter:saturate(180%) blur(10px);backdrop-filter:saturate(180%) blur(10px);
}
.conn-row{max-width:560px;margin:0 auto;padding:6px 0}
.conn{
  display:flex;align-items:center;gap:9px;width:100%;text-align:start;min-height:34px;
  padding-block:6px;padding-inline:12px 8px;border-radius:var(--r);
  background:color-mix(in srgb,var(--yellow) 16%,var(--surface));color:var(--label);font-size:13px;line-height:1.3;
}
.conn .icn{flex:none;font-size:16px;color:var(--label-2)}
.conn-t{flex:1;min-width:0;overflow-wrap:break-word}
.conn-a{flex:none;padding:4px 10px;border-radius:99px;background:var(--surface-3);font-weight:600;white-space:nowrap}
button.conn:active{filter:brightness(1.12)}
.conn-bar.off .conn{background:color-mix(in srgb,var(--label-3) 18%,var(--surface))}
.conn-bar.bad .conn{background:color-mix(in srgb,var(--red) 16%,var(--surface))}
.conn-bar.bad .conn .icn{color:var(--red)}
/* a deliberate local setup: said, not flagged — one small line, no fill */
.conn-bar.quiet .conn-row{padding:2px 0 4px}
.conn-bar.quiet .conn{min-height:26px;padding-block:2px;padding-inline:4px 2px;background:none;color:var(--label-2);font-size:12px;gap:6px}
.conn-bar.quiet .conn .icn{font-size:13px;color:var(--label-3)}
.conn-bar.quiet .conn-a{padding:2px 9px;font-size:12px;color:var(--acc);background:var(--acc-soft)}
/* its × (#454): beside the line, not inside its button, so hiding it never also connects */
.conn-bar.quiet .conn-row{display:flex;align-items:center;gap:2px}
.conn-x{flex:none;display:grid;place-items:center;width:26px;height:26px;border-radius:99px;color:var(--label-3);font-size:12px}
.conn-x:active{background:var(--surface-2)}
:root[data-theme="light"] .conn-bar:not(.quiet) .conn-a{background:var(--surface)}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .conn-bar{background:var(--bg)}
}
/* large title, the way a nav bar does it */
.hdr{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin:8px 0 18px}
/* The title block gives way, the button on the right never does: a flex item will not shrink
   below its longest word, so "Упражнения" at 34px or "Hi Bartholomew_Jones" used to push the
   gear / "By muscle" past the screen edge. Two things follow from letting it shrink. Below 420px
   — every phone width we test — the title drops a step to 30px, because the 95 to 125px the
   Library button leaves the Polish and Russian titles is less than half of "Ćwiczenia" or
   "Упражнения" at 34px and they came apart into three lines; at 30px all 14 packs take two at
   most. And a word that still does not fit is broken with overflow-wrap:break-word, which moves
   the whole word to the next line first and breaks it only if it does not fit there either —
   `anywhere` broke "Bartholomew_Jones" mid-word even at 412px, where a line of its own was
   waiting for it. hyphens:auto goes with them: the hyphen costs a character's worth of width in
   a box this tight, and that is what turned "Упражнения" into "Упра / жнен / ия". */
.hdr>div{min-width:0}
.hdr>.btn{flex:none}
.hdr>.hdr-actions{display:flex;align-items:center;gap:8px;flex:none}
.hdr h1{font-size:34px;line-height:1.06;font-weight:700;letter-spacing:-.028em;overflow-wrap:break-word}
@media (max-width:420px){.hdr h1{font-size:30px}}
.hdr .sub{color:var(--label-2);font-size:15px;letter-spacing:-.006em;margin-top:4px}
/* Library: the count line runs under the title row at full width instead of beside the "By muscle"
   button, which left it ~200px on a phone: the catalogue count plus your own exercises stays one
   line in every language. The ellipsis is only the safety net (the strings are kept short), and
   text-overflow follows the text direction, so Arabic clips on its own end. */
.hdr.lib-hdr{flex-wrap:wrap;align-items:center;row-gap:0}
.hdr.lib-hdr>.sub.lib-count{flex:1 0 100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@media (max-width:360px){.hdr.lib-hdr>.sub.lib-count{white-space:normal;overflow:visible;text-overflow:clip}}

.iconbtn{
  width:36px;height:36px;border-radius:50%;flex:none;
  background:var(--surface);color:var(--label);
  display:flex;align-items:center;justify-content:center;font-size:18px;
  transition:background var(--fast),transform var(--fast);
}
.iconbtn:active{background:var(--surface-2);transform:scale(.92)}
/* invisible hit-area extension: these look right at their size but fall short of a
   finger-sized target, so the pseudo element widens what the tap lands on without
   changing the painted box. Segmented buttons sit flush against each other, so they
   only grow vertically or the neighbour would steal the edge of the tap. */
.iconbtn,.chip,.seg button,.helpbtn{position:relative}
.iconbtn::before,.chip::before,.seg button::before,.helpbtn::before{content:'';position:absolute;inset:-6px}
.seg button::before{inset:-6px 0}
.iconbtn.on-ss{background:var(--acc-soft);color:var(--acc)}
.iconbtn:disabled{opacity:.32}

h4.sec{
  font-size:13px;font-weight:400;letter-spacing:-.004em;color:var(--label-2);
  text-transform:none;margin:22px 0 8px;padding:0 4px;
}
.card h2{
  font-size:13px;font-weight:400;letter-spacing:-.004em;color:var(--label-2);
  text-transform:none;margin:0 0 12px;
}
.divider{height:var(--hair);background:var(--sep);margin:14px 0;border:0}
.row{display:flex;align-items:center;gap:12px}
.row.between{justify-content:space-between}
/* The body-weight card header (Home and Stats): title on the left, Goal + Log on the right. In
   German the title and the two buttons ("Ziel", "+ Eintragen") outgrow a 320px card by about
   55px, so the button pair may drop to a second line — still flush right, hence the auto margin. */
.bw-head{flex-wrap:wrap;row-gap:8px}
.bw-head>.row{margin-inline-start:auto}

/* ----------------------------------------------------------------- card -- */
.card{
  background:var(--surface);border-radius:var(--r-card);
  padding:16px;margin-bottom:12px;
}
.card .big{font-size:30px;font-weight:600;letter-spacing:-.026em;line-height:1.1}

/* ------------------------------------------------------ inset group list -- */
.sect{margin-bottom:22px}
.sect-t{
  font-size:13px;font-weight:400;color:var(--label-2);
  letter-spacing:-.004em;padding:0 4px 7px;display:block;
}
.sect-b{background:var(--surface);border-radius:var(--r-card);overflow:hidden}
.sect-f{font-size:13px;color:var(--label-2);padding:7px 4px 0;line-height:1.38}

.lrow{
  display:flex;align-items:center;gap:12px;width:100%;text-align: start;
  padding:11px 14px;min-height:46px;position:relative;color:var(--label);
  background:none;
}
/* hairline starts after the icon rail so the list reads as one object */
.lrow+.lrow::before{
  content:'';position:absolute;inset-inline-start:14px;inset-inline-end:0;top:0;height:var(--hair);background:var(--sep);
}
.lrow:has(.lrow-i)+.lrow:has(.lrow-i)::before{inset-inline-start:55px}
.lrow.tap:active{background:var(--surface-2)}
.lrow-i{
  width:29px;height:29px;border-radius:7px;flex:none;font-size:18px;
  display:flex;align-items:center;justify-content:center;
  background:var(--tint,var(--acc));color:#fff;
}
/* The title column never shrinks below its own longest word. With a basis of 0 and min-width:0 it
   only got what a capped value left over, and on a 320px phone "Progression" (96px) was painted over
   by "Linear progression" in a 37px box; min-width:auto — the flex default — is exactly the floor
   that case wants. A fixed 104px floor fixed that row and starved the rest: the backfill sheet's Date
   and Start-time rows have nothing but the title to give, so the floor shoved their fixed-width
   .timef off a 320px screen, and a short title like "Routine" (63px) took 104px away from the value
   beside it until "Freies Training" ellipsised. Asking only for the longest word takes nothing a row
   did not already owe the title. .lrow-t does not ellipsise on purpose — long settings titles wrap
   onto a second line — and the value next to it shrinks and ellipsises instead (below). */
.lrow-m{flex:1;min-width:auto;display:flex;flex-direction:column;gap:1px}
.lrow-t{font-size:17px;letter-spacing:-.012em;line-height:1.25}
.lrow-s{font-size:13px;color:var(--label-2);line-height:1.3}
/* Settings → Account ID: a machine string, so a machine face, small, and free to break anywhere
   rather than push the row past a phone's edge. */
.acct-id{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;overflow-wrap:anywhere}
/* flex:none would let a long value ("Follow the routine (Linear progression)") run straight
   through the title next to it, since the title's column only holds out for its longest word.
   Letting the value shrink and ellipsise instead keeps the two out of each other's way.
   The cap is max-width, not flex-basis: .lrow-m is flex:1, so its basis is 0 and it absorbs
   none of the shrink — a long value would otherwise take the whole row and squeeze the title
   to 21px. A basis of 45% fixes that but reserves the space even for "3", costing the title
   141px on the rows that need it most. A cap does neither. */
.lrow-v{color:var(--label-2);font-size:17px;letter-spacing:-.012em;flex:0 1 auto;max-width:55%;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align: end}
.lrow-c{color:var(--label);font-size:15px;flex:none;--icon-stroke:2.4}
.lrow-k{color:var(--acc);font-size:17px;flex:none;--icon-stroke:2.4}
.lrow.danger,.lrow.danger .lrow-t{color:var(--red)}
/* A warning under a settings row reads at subtitle size: a sentence, not a row title. */
.lrow.backup-lost .lrow-t{font-size:13px;line-height:1.3;color:var(--label-2);letter-spacing:0}
/* The Stats exercise name gets its own line so long names cannot run into the title.
   Two columns only — label/value on the left, chevron on the right — so this is for rows
   WITHOUT an icon. `.lrow-i` is not placed here; on a row that has one it would auto-place
   into the grid and push the layout around. Give it a leading column before using this
   anywhere else. */
.lrow.lrow-stack-value{
  display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-rows:auto auto;
}
.lrow.lrow-stack-value .lrow-m{grid-column:1;grid-row:1}
.lrow.lrow-stack-value .lrow-v{grid-column:1;grid-row:2;width:100%;max-width:none;text-align:start}
.lrow.lrow-stack-value .lrow-c{grid-column:2;grid-row:1 / span 2;align-self:center}
/* an (i) before a row's control: painted small and dim so the control stays the loud thing,
   but with a finger-sized target like everything else — the padding is the hit area and the
   vertical negative margin keeps it from making the row taller. The horizontal ones pull the
   hit area back under the row's own 12px gap instead of adding to it. */
.helpbtn{
  color:var(--label-3);font-size:16px;background:none;line-height:0;flex:none;
  margin-block:-11px;margin-inline:-4px -2px;padding:11px 4px;
}
.helpbtn:active{color:var(--label)}

/* -------------------------------------------------------------- buttons -- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:7px;width:100%;
  padding:14px 18px;border-radius:var(--r);font-size:17px;font-weight:600;
  letter-spacing:-.012em;background:var(--surface-2);color:var(--label);
  transition:transform var(--fast) var(--ease),background var(--fast),opacity var(--fast);
}
.btn .icn{font-size:19px}
.btn:active{transform:scale(.975)}
.btn.primary{background:var(--acc);color:var(--on-acc)}
.btn.primary:active{background:var(--acc-2)}
.btn.tinted{background:var(--acc-soft);color:var(--acc)}
.btn.danger{background:color-mix(in srgb,var(--red) 15%,transparent);color:var(--red)}
.btn.ghost,.btn.plain{background:none;color:var(--acc);font-weight:400}
.btn.ghost:active,.btn.plain:active{background:var(--surface-2)}
.btn.sm{padding:8px 14px;width:auto;font-size:15px;border-radius:var(--r-sm);gap:5px}
.btn.sm .icn{font-size:16px}
.btn.xs{padding:5px 10px;width:auto;font-size:13px;border-radius:7px;gap:4px}
.btn:disabled{opacity:.32;pointer-events:none}
.weekly-volume-toggle{padding-block:0}
.weekly-volume-toggle .icn{transition:transform var(--fast) var(--ease)}
.weekly-volume-toggle[aria-expanded="true"] .icn{transform:rotate(180deg)}

/* ----------------------------------------------------------- tab bar ----- */
/* Nearly opaque, and deliberately so. At the old 72% the page read straight through the bar
   and the labels had to compete with whatever was scrolling behind them — nobody needs to read
   the content under a navigation bar. 94% keeps a hint of movement behind the glass without
   letting text through. The blur is also down from 24px to 10px: this is a fixed element, so
   the WebView recomposites the blurred region on every scroll frame, and on Android that is
   the single most expensive thing on the screen. See @supports below for the no-blur case. */
#tabbar{
  position:fixed;inset-inline-start:0;inset-inline-end:0;bottom:0;z-index:50;
  display:flex;justify-content:space-around;align-items:flex-end;
  padding:7px 6px calc(6px + var(--sab));
  background:color-mix(in srgb,var(--bg-el) 94%,transparent);
  -webkit-backdrop-filter:saturate(180%) blur(10px);backdrop-filter:saturate(180%) blur(10px);
  border-top:var(--hair) solid var(--sep-op);
}
:root[data-theme="light"] #tabbar{background:rgba(249,249,251,.94)}
#tabbar button{
  flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;
  color:var(--label-3);font-size:10px;font-weight:500;letter-spacing:.005em;padding-top:3px;
  transition:color var(--fast);
}
/* The inactive tint is semi-transparent, so wherever two strokes cross inside an
   icon (the calendar's hangers over its frame, the divider's caps on its sides)
   the alpha doubled up and the icon read as a stack of loose layers. Carrying the
   fade on the <svg> instead of in the stroke colour flattens the icon first and
   fades it as one shape — and it does that for every icon, not just this one. */
#tabbar button .icn{font-size:25px;--icon-stroke:1.65;color:var(--label)}
#tabbar button.on{color:var(--acc)}
#tabbar button.on .icn{--icon-stroke:2;color:inherit;opacity:1}
/* A connection problem while the banner is switched off (Settings → Show connection status):
   a dot on the Home tab and on the Settings gear. */
#tabbar .tab-ic{position:relative;display:inline-flex}
.tab-dot{
  position:absolute;top:-1px;right:-3px;width:8px;height:8px;border-radius:50%;
  background:var(--orange);box-shadow:0 0 0 2px var(--bg-el);pointer-events:none;
}
.iconbtn .tab-dot{top:5px;right:5px;box-shadow:0 0 0 2px var(--bg)}
#tabbar button.start{margin-top:-24px}
#tabbar button.start .cir{
  width:52px;height:52px;border-radius:50%;background:var(--acc);color:var(--on-acc);
  display:flex;align-items:center;justify-content:center;position:relative;
  box-shadow:0 6px 18px -4px color-mix(in srgb,var(--acc) 55%,transparent);
  transition:transform var(--fast) var(--ease);
}
#tabbar button.start:active .cir{transform:scale(.93)}
#tabbar button.start .cir .icn{font-size:26px;--icon-stroke:2;color:inherit;opacity:1}
#tabbar button.start span{color:var(--acc);font-weight:600}
#tabbar button.start .tab-time{font-variant-numeric:tabular-nums}
/* `.cir` is itself a <span>, so the label rules above and below reach it too and
   outrank the `.cir` block — which is how the running state ended up painting an
   orange play glyph onto an orange disc. Both states restate their own colour.
   The disc is orange in either theme, so its glyph is black, not `--on-acc`. */
#tabbar button.start .cir{color:var(--on-acc)}
#tabbar button.start.rec .cir{background:var(--orange);color:#000;box-shadow:0 6px 18px -4px color-mix(in srgb,var(--orange) 55%,transparent)}
#tabbar button.start.rec span{color:var(--orange)}
#tabbar button.start.rec .cir::after{
  content:'';position:absolute;inset:0;border-radius:50%;
  border:2px solid var(--orange);animation:ping 1.9s var(--ease) infinite;
}
@keyframes ping{0%{transform:scale(1);opacity:.7}100%{transform:scale(1.45);opacity:0}}

/* NOTE on every backdrop-filter pair in this file: the -webkit- line comes first and the
   standard property last, which is the order a prefixed/standard pair wants — whichever is
   written last wins in the cascade, and that should be the standard name.

   Where backdrop-filter is genuinely unsupported there is no glass either — only the alpha,
   and the page shows through unblurred, which is the worst of both. Older Android WebViews
   never had it, and "reduce transparency" accessibility settings drop it. Those surfaces go
   fully opaque instead. Keep this in sync with the rules above. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  #tabbar{background:var(--bg-el)}
  :root[data-theme="light"] #tabbar{background:#f9f9fb}
  #timer{background:var(--surface)}
  #toast{background:var(--surface-2)}
}

/* ------------------------------------------------------------ week strip -- */
.week{display:flex;justify-content:space-between;gap:4px}
.wday{
  flex:1;text-align:center;padding:8px 2px 9px;border-radius:10px;
  background:none;border:none;transition:background var(--fast);
}
.wday .lbl{font-size:11px;color:var(--label-3);font-weight:500;text-transform:uppercase;letter-spacing:.03em}
.wday .num{font-size:17px;font-weight:400;margin:4px 0;letter-spacing:-.012em}
.wday .dot{width:5px;height:5px;border-radius:50%;background:transparent;margin:0 auto}
.wday .dot.plan{background:var(--label-3)}
.wday .dot.ovr{background:var(--orange)}
.wday .dot.done{background:var(--acc)}
/* a day off with a note on it (#261, lib/day-notes.js): the same colour on every calendar */
.wday .dot.noted{background:var(--indigo)}
.wday:active{background:var(--surface-2)}
.wday.today .num{
  background:var(--acc);color:var(--on-acc);font-weight:600;
  width:31px;height:31px;border-radius:50%;display:flex;align-items:center;
  justify-content:center;margin:2px auto;
}

/* ----------------------------------------------------------------- list -- */
.list{display:flex;flex-direction:column;gap:8px}
.item{
  display:flex;align-items:center;gap:12px;background:var(--surface);
  border-radius:var(--r-card);padding:10px 12px;min-height:60px;
  transition:background var(--fast);text-align: start;width:100%;
}
.item:active{background:var(--surface-2)}
.routine-list{position:relative}
/* One column at every width (#277), whatever page it is on. The desktop layout puts the #app
   lists outside a .narrow page in two columns, and before the .narrow rule below won on
   specificity this list sat in that grid on a computer, while the reorder measures its rows top
   to bottom and draws the drop line across the whole list: an exercise dragged just below the
   next one in its column landed at the end. Supersets ("the exercise above") read as a column too. */
#app .list.routine-list{display:flex;flex-direction:column}
.routine-drag-row{touch-action:pan-y;-webkit-touch-callout:none;position:relative;transition:transform var(--fast) var(--ease)}
.routine-list.is-reordering{user-select:none;-webkit-user-select:none}
.routine-list.is-reordering .routine-drag-row{transition:none}
.routine-drag-row.is-dragging{z-index:2;pointer-events:none}
.routine-drag-row.is-dragging .item{box-shadow:0 10px 28px rgba(0,0,0,.22);background:var(--surface-2)}
.routine-drop-indicator{
  position:absolute;left:6px;right:6px;height:3px;border-radius:99px;
  background:var(--acc);transform:translateY(-50%);pointer-events:none;z-index:3;
}
/* The drag handle (#277, #114). A long press anywhere on a row reorders it on every device,
   but nothing on screen said so, and with a mouse nobody holds a row to find out. So a pointer
   device gets the grip it looks for, with a grab cursor, and pressing it picks the row up at
   once (RoutineEdit.jsx). A touch screen keeps the long press and the row's full width. */
.routine-grip{
  display:none;flex:none;align-self:stretch;align-items:center;margin:-10px -6px -10px -4px;padding:0 4px;
  color:var(--label-3);font-size:16px;cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none;
}
@media (hover:hover) and (pointer:fine){
  .routine-grip{display:flex}
  .routine-grip:hover{color:var(--label-2)}
}
.routine-list.is-reordering,.routine-list.is-reordering *{cursor:grabbing}
.thumb{width:50px;height:50px;border-radius:9px;object-fit:cover;background:#fff;flex:none}
/* exercise pictures are decoration inside tappable rows/cards: no save-image callout on
   a long press and no drag ghost when a scroll starts on the picture */
.thumb,.exmedia img,.exmedia video{-webkit-touch-callout:none;-webkit-user-drag:none;user-select:none;-webkit-user-select:none}
.thumb-x{
  display:flex;align-items:center;justify-content:center;font-size:21px;
  background:var(--surface-2);color:var(--label-2);
}
.item .tt{font-size:16px;font-weight:400;line-height:1.25;letter-spacing:-.009em}
.item .ss{color:var(--label-2);font-size:13px;margin-top:2px}
.item .grow{flex:1;min-width:0}
/* A row's opener as a real button (the routine editor's exercise name), so a row holding other
   buttons is no button itself. Reads as the plain text it replaced. */
.item .item-open{display:flex;flex-direction:column;justify-content:center;align-self:stretch;text-align:start}
.item .item-open>span{display:block}
.chev{color:var(--label);flex:none;font-size:15px;--icon-stroke:2.4}
.tag{
  display:inline-flex;align-items:center;gap:3px;background:var(--surface-2);
  color:var(--label-2);border-radius:6px;padding:3px 7px;font-size:12px;
  font-weight:500;text-transform:capitalize;
}
.tag.acc{background:var(--acc-soft);color:var(--acc)}
/* Left / Right on a per-side row: one width for both (#322). The two hidden words share the
   visible one's grid cell, so the pill is as wide as the longer word. */
.tag.sidepill{display:inline-grid;justify-items:center;flex:none}
.tag.sidepill>span,.tag.sidepill::before,.tag.sidepill::after{grid-area:1/1}
.tag.sidepill::before{content:attr(data-l);visibility:hidden}
.tag.sidepill::after{content:attr(data-r);visibility:hidden}
/* favourite marker (issue #6): a small yellow star glued to the exercise name — distinct from
   the accent "chosen" tag at the row's end, which means "already in a routine or the log" */
.fav-star{color:var(--yellow);font-size:12px;display:inline-block;margin-inline-end:5px;vertical-align:-1px}
.fav-btn{margin-top:-4px;flex:none}
/* One long word (a German custom name) wraps instead of pushing the star off the screen. */
.exdetail-name{min-width:0;overflow-wrap:anywhere}
.fav-btn.on{color:var(--yellow)}
/* Exercise cards (S.exerciseView 'cards', ExerciseViewToggle): the Library, the picker and the
   muscle explorer as a grid of pictures with the name underneath — two across on a phone, more
   as the page or sheet widens. The id-prefixed selectors outrank the desktop rules further down
   that make every #app list two columns and every sheet list one. The catalogue pictures are
   180px, which is why a card stays near that size instead of filling a phone's width. */
.list.ex-grid,#app .list.ex-grid,#modal-root .list.ex-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
.ex-grid .item{position:relative;flex-direction:column;align-items:stretch;gap:8px;padding:8px}
.ex-grid .item .thumb{width:100%;height:auto;aspect-ratio:1;border-radius:12px}
.ex-grid .item .thumb-x{font-size:36px}
.ex-grid .item .tt{font-size:15px}
/* The badges ride on the picture's top corners and "+ Plan" spans the foot of the card. The
   picker's "+" carries an inline padding/margin that widens its hit area in a row; on a card it
   sits on the picture, so those give way here. */
.ex-grid .item>.tag{position:absolute;top:14px;inset-inline-start:14px}
.ex-grid .item:not(.ex-new)>.chev{position:absolute;top:14px;inset-inline-end:14px;width:32px;height:32px;margin:0 !important;padding:7px !important;border-radius:50%;background:var(--surface)}
.ex-grid .item>.btn{width:100%}
/* "Create your own exercise" stays a row across the top of the grid */
.ex-grid .item.ex-new{grid-column:1/-1;flex-direction:row;align-items:center;gap:12px;padding:10px 12px}
.ex-grid .item.ex-new .thumb{width:50px;height:50px;border-radius:9px}
.ex-grid .item.ex-new .thumb-x{font-size:21px}
/* The list/cards button (ExerciseViewToggle) at the end of the search field, as tall as it and
   dressed like it; pressed while the cards are on. */
.search-row{display:flex;gap:8px}
.search-row>.search{flex:1;min-width:0}
.search-row>.ex-view-btn{width:46px;height:auto;border-radius:10px;background:var(--surface-2);font-size:20px}
:root[data-theme="light"] .search-row>.ex-view-btn{background:rgba(118,118,128,.12)}
.search-row>.ex-view-btn.on,:root[data-theme="light"] .search-row>.ex-view-btn.on{background:var(--acc-soft);color:var(--acc)}

/* ---------------------------------------------------------------- chips -- */
.chips{
  display:flex;gap:7px;overflow-x:auto;padding:4px 4px 6px;   /* room for a chip's focus outline inside a sheet (#162) */
  scrollbar-width:none;
}
.chips::-webkit-scrollbar{display:none}
/* horizontal strips: keep a sideways overscroll from chaining to the page and leave a
   margin when a chip is scrolled into view. The chip row also claims the horizontal axis
   (touch-action:pan-x): on a phone a slightly diagonal swipe along the chips used to hand
   the gesture to the page, which scrolled down instead. The row is 32px tall, so losing a
   vertical scroll that happens to start on it costs nothing; the ~130px heatmap wrap keeps
   both axes, there the trade-off goes the other way. The chips contain both axes: once a
   strip is at its end, a diagonal drag must not chain into the page either. */
.chips,.hm-wrap{overscroll-behavior-x:contain;scroll-padding-inline:16px}
.chips{touch-action:pan-x;overscroll-behavior:contain}
/* The chip hit-area (.chip::before, inset:-6px) must not poke out of the strip vertically:
   overflow-x:auto makes overflow-y auto as well, and that 6px of positioned overflow gave
   the 32px row a 36px scroll height — a diagonal swipe, wheel or drag then scrolled the
   whole row up by 4px and left it there. Vertical slop is clipped by the strip anyway. */
.chips .chip::before{top:0;bottom:0}
.chip{
  flex:none;padding:6px 13px;border-radius:99px;background:var(--surface);
  font-size:14px;font-weight:400;color:var(--label);text-transform:capitalize;
  letter-spacing:-.006em;transition:background var(--fast),color var(--fast);
}
.chip:active{background:var(--surface-2)}
.chip.on{background:var(--acc);color:var(--on-acc);font-weight:500}
.chip.on .dim{color:color-mix(in srgb,var(--on-acc) 62%,transparent)}
/* A chip with an icon in front (the day note's Sick / Travelling / …): the icon is a block, so
   without this the label wrapped under it and every chip became a two-line button. One line
   each; a row wider than the sheet scrolls sideways like every .chips strip. */
.chip.ichip{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}
.chip .qstate{text-transform:none}
/* On a phone these rows scroll by swipe. On a pointer device lib/hchips.js lets you
   click-drag anywhere on the row to pull it along; a grab cursor on rows that actually
   overflow (data-xscroll, set by hchips.js on hover) is the hint. */
@media (hover:hover) and (pointer:fine){
  .chips[data-xscroll]{cursor:grab}
}
/* while a drag is in flight: no text selection, grabbing cursor everywhere (the `*` rule
   outweighs button{cursor:pointer} so it holds even over a chip) */
body.chips-dragging{cursor:grabbing;user-select:none;-webkit-user-select:none}
body.chips-dragging *{cursor:inherit}

/* =========================== CONTROLS ==================================== */

/* --- text field --- */
.field,.input{
  width:100%;background:var(--surface);border:none;border-radius:var(--r);
  padding:13px 15px;font-size:17px;letter-spacing:-.012em;outline:none;
  transition:box-shadow var(--fast);
}
.field::placeholder,.input::placeholder{color:var(--label-3)}
.field:focus,.input:focus{box-shadow:inset 0 0 0 2px var(--acc)}
.field.area,textarea.input{min-height:92px;resize:vertical;line-height:1.42}
.measure-date{display:block;margin-bottom:14px}
.measure-date>span,.measure-field>span:first-child{display:block;color:var(--label-2);font-size:13px;margin:0 2px 6px}
.measure-date .field{background:var(--surface-2)}
.measure-form{display:grid;grid-template-columns:1fr 1fr;gap:11px 10px}
.measure-field{min-width:0}
.measure-input{height:47px;background:var(--surface-2);border-radius:var(--r);display:flex;align-items:center;padding:0 12px;gap:6px}
.measure-input .num{width:100%;min-width:0;background:transparent;border:0;outline:none;font-size:18px;font-weight:500;color:var(--label);padding:0}
.measure-input i{font-style:normal;color:var(--label-3);font-size:13px}
.measure-options{margin:12px 0 16px}
.measure-select{margin-bottom:10px;background:var(--surface-2)}
.measure-preview{display:flex;flex-wrap:wrap;gap:6px 12px;margin-top:11px;padding-top:10px;border-top:1px solid var(--sep);font-size:12px;color:var(--label-2)}
.measure-preview b{color:var(--label);font-weight:500}
.measure-home-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.measure-home-grid button{background:var(--surface-2);border-radius:10px;padding:10px;text-align:left;min-width:0}
.measure-home-grid span,.measure-home-grid i{display:block;font-size:11px;color:var(--label-2);font-style:normal;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.measure-home-grid b{display:block;font-size:16px;margin:3px 0;white-space:nowrap}
.measure-empty{width:100%;display:flex;align-items:center;justify-content:space-between;text-align:left;color:var(--label-2);font-size:13px;gap:10px}
.measure-empty .icn{flex:none}
.exnote{
  background:var(--surface);border-radius:var(--r);padding:13px 15px;
  line-height:1.45;white-space:pre-wrap;font-size:15px;margin-bottom:10px;color:var(--label-2);
}

/* --- search field --- */
.searchf{position:relative;display:flex;align-items:center}
.searchf .lead{
  position:absolute;inset-inline-start:11px;font-size:16px;color:var(--label-3);
  pointer-events:none;--icon-stroke:2.1;z-index:1;
}
.searchf .field{padding-inline-start:35px;padding-inline-end:36px;background:var(--surface-2);border-radius:10px}
:root[data-theme="light"] .searchf .field{background:rgba(118,118,128,.12)}
.searchf .clear{
  position:absolute;inset-inline-end:8px;width:22px;height:22px;border-radius:50%;
  background:var(--label-3);color:var(--bg);display:flex;align-items:center;
  justify-content:center;font-size:11px;--icon-stroke:3;
}
/* legacy .search wrapper */
.search{position:relative}
.search .input{padding-inline-start:35px;background:var(--surface-2);border-radius:10px}
:root[data-theme="light"] .search .input{background:rgba(118,118,128,.12)}
.search>svg{
  position:absolute;inset-inline-start:11px;top:50%;transform:translateY(-50%);z-index:1;
  width:16px;height:16px;stroke:var(--label-3);fill:none;stroke-width:2.1;stroke-linecap:round;
}
/* The live result count (Library and the exercise picker, from GitLab !31): a pill at the end
   of the field while a search or filter narrows the list. The field makes room for it only while
   it is there, so a long placeholder is not cut short for nothing. */
.search.has-count .input{padding-inline-end:62px}
.search .search-count{
  position:absolute;inset-inline-end:9px;top:50%;transform:translateY(-50%);
  font-size:12px;font-weight:600;line-height:1.4;color:var(--label-2);
  background:var(--surface-3);padding:2px 8px;border-radius:99px;
  font-variant-numeric:tabular-nums;pointer-events:none;user-select:none;
}
/* The × that empties the search (Library and the exercise picker), the same round button as
   SearchField's, at the end of the field; the count moves in beside it while both show. */
.search .clear{
  position:absolute;inset-inline-end:8px;top:50%;transform:translateY(-50%);z-index:1;
  width:22px;height:22px;border-radius:50%;
  background:var(--label-3);color:var(--bg);display:flex;align-items:center;
  justify-content:center;font-size:11px;--icon-stroke:3;
}
.search.has-clear .input{padding-inline-end:36px}
.search.has-count.has-clear .input{padding-inline-end:90px}
.search.has-clear .search-count{inset-inline-end:36px}

/* --- switch --- */
.sw{
  width:51px;height:31px;border-radius:99px;background:var(--surface-3);
  flex:none;padding:2px;display:flex;align-items:center;
  transition:background var(--med) var(--ease);
}
.sw .knob{
  width:27px;height:27px;border-radius:50%;background:#fff;
  box-shadow:0 3px 8px rgba(0,0,0,.15),0 1px 1px rgba(0,0,0,.16);
  transition:transform var(--med) var(--ease),width var(--fast) var(--ease);
}
.sw.on{background:var(--acc)}
/* On an accent nearly as light as the knob (an own white or pale yellow, the gold preset) the
   knob gets a hairline ring so it does not vanish into the track. lib/accent.js sets --knob-ring
   for an own colour; it is transparent everywhere else. */
.sw.on .knob{box-shadow:0 0 0 1px var(--knob-ring,transparent),0 3px 8px rgba(0,0,0,.15),0 1px 1px rgba(0,0,0,.16)}
.sw.on .knob{transform:translateX(20px)}
.sw:active .knob{width:31px}
.sw.on:active .knob{transform:translateX(16px)}
.sw:disabled{opacity:.4}

/* --- segmented control --- */
/* flex, not grid: an absolutely-positioned child of a GRID container is placed
   into a grid area, so the sliding thumb resolved top/bottom against the wrong
   box and overflowed the track. A flex container leaves abspos children on the
   padding box, which is what the thumb needs. */
.seg{
  position:relative;display:flex;
  background:var(--surface-3);border-radius:9px;padding:2px;isolation:isolate;
}
.seg>button{flex:1 1 0;min-width:0}
:root[data-theme="light"] .seg{background:rgba(118,118,128,.12)}
.seg-sel{
  position:absolute;z-index:0;top:2px;bottom:2px;inset-inline-start:2px;
  width:calc((100% - 4px)/var(--n));
  transform:translateX(calc(100% * var(--i)));
  background:var(--surface);border-radius:7px;
  box-shadow:0 3px 8px rgba(0,0,0,.12),0 1px 1px rgba(0,0,0,.06);
  transition:transform var(--med) var(--ease);
}
:root[data-theme="light"] .seg-sel{background:#fff}
.seg button{
  position:relative;z-index:1;padding:6px 8px;font-size:14px;font-weight:500;
  letter-spacing:-.006em;color:var(--label);display:flex;align-items:center;
  justify-content:center;gap:5px;min-height:30px;transition:opacity var(--fast);
}
.seg button .icn{font-size:16px}
.seg button:not(.on){color:var(--label-2);font-weight:400}
.seg button:active{opacity:.5}
/* sitting in a list row: shrink to content instead of filling the row. The control never
   shrinks below its labels, so a row whose title and control do not both fit — most translated
   packs at 390px: "Übungs-Animationen" against "Voll · Klein · Aus" — wraps the control onto its
   own line under the title instead of painting it over the title. */
.seg-inline{flex:none;min-width:132px;max-width:100%;margin-inline-start:auto}
.lrow:has(.seg-inline){flex-wrap:wrap}
.lrow:has(.seg-inline) .lrow-m{min-width:min(150px,100%)}
.seg-range{margin-bottom:10px}
/* Every segment as wide as the widest label (QA 10-05: with flex:1 1 0 the track was sized by the
   labels' sum but split evenly, so "Compact" / "Versteckt" got less than its own width and
   touched the segment's edge). A grid of equal 1fr columns sizes each to the widest one, so the
   thumb (100%/n) still lines up; the sliding thumb is absolutely positioned with no grid
   placement, so its box is the track's padding box, as in the flex version. */
.seg-inline{display:grid;grid-auto-flow:column;grid-auto-columns:1fr}
.seg-inline button{padding:5px 12px;font-size:13px;min-height:28px}
.seg-inline button span{white-space:nowrap}
.seg-inline button .icn{font-size:14px}

/* native time input, restyled to sit in a row like a value label */
.timef{
  background:var(--surface-2);border:none;border-radius:8px;padding:6px 10px;
  font-size:16px;color:var(--label);outline:none;font-variant-numeric:tabular-nums;
}
/* A date or time field is as wide as the browser draws it and will not give way, and neither will
   the title beside it once that is down to its longest word: the Polish "Godzina rozpoczęcia" plus
   the clock leaves a 320px row 17px short, and the field then hangs over the edge of the card with
   its picker icon. Such a row wraps the field onto a line of its own under the title instead, still
   flush right — the same way a row carrying an inline segmented control does. */
.lrow:has(.timef){flex-wrap:wrap}
.lrow>.timef{margin-inline-start:auto}
.timef::-webkit-calendar-picker-indicator{filter:invert(.5)}
:root[data-theme="light"] .timef::-webkit-calendar-picker-indicator{filter:none}

/* --- stepper --- */
.stp-w{display:flex;flex-direction:column;gap:6px;min-width:0}
.stp-l{font-size:13px;color:var(--label-2);text-align:center}
.stp{
  display:flex;align-items:center;background:var(--surface-2);
  border-radius:10px;overflow:hidden;min-width:0;
}
.stp button{
  width:40px;height:44px;flex:none;color:var(--label);font-size:16px;
  display:flex;align-items:center;justify-content:center;
  transition:background var(--fast);--icon-stroke:2.2;
}
.stp button:active{background:var(--surface-3)}
.stp .val{
  flex:1;min-width:0;display:flex;align-items:baseline;justify-content:center;gap:3px;padding:0 2px;
  align-self:stretch;
}
.stp .val i{font-style:normal;font-size:12px;color:var(--label-2);flex:none}
.stp .num{
  width:100%;min-width:0;text-align:center;background:none;border:none;outline:none;
  font-size:17px;font-weight:500;letter-spacing:-.012em;padding:0;
  -moz-appearance:textfield;appearance:textfield;
  /* the field is the whole height of the stepper, not just its 18px text line, so a tap
     anywhere between the two buttons focuses it */
  height:100%;min-height:40px;cursor:text;
}
.stp .num::-webkit-inner-spin-button{display:none}
.stp.invalid{box-shadow:inset 0 0 0 1px var(--red)}

/* --- slider --- */
.sld{position:relative;height:32px;display:flex;align-items:center;cursor:pointer;touch-action:none}
.sld-track{
  position:absolute;inset-inline-start:0;inset-inline-end:0;height:6px;border-radius:99px;
  background:var(--surface-3);overflow:hidden;
}
.sld-fill{display:block;height:100%;background:var(--acc);border-radius:99px}
.sld-knob{
  position:absolute;width:26px;height:26px;border-radius:50%;background:#fff;
  transform:translateX(-50%);
  box-shadow:0 4px 10px rgba(0,0,0,.22),0 1px 2px rgba(0,0,0,.16);
  transition:transform var(--fast) var(--ease);
}
.sld.dragging .sld-knob{transform:translateX(-50%) scale(1.14)}

/* --- checkbox --- */
.chk,.ck{
  width:30px;height:30px;border-radius:50%;flex:none;position:relative;
  display:flex;align-items:center;justify-content:center;
  background:none;box-shadow:inset 0 0 0 1.8px var(--label-4);color:transparent;
  transition:background var(--fast),box-shadow var(--fast),color var(--fast),transform var(--fast);
}
.chk .icn{font-size:16px;--icon-stroke:2.6}
/* 30px is the right look next to a stepper but a poor target: extend the tap area to 44px
   without painting it; the left side stays shorter so the stepper's + button keeps its edge */
.chk::before,.ck::before{content:'';position:absolute;inset:-7px -7px -7px -4px}
.chk.on{background:var(--acc);box-shadow:none;color:var(--on-acc)}
.chk:active,.ck:active{transform:scale(.9)}
.ck svg{
  width:16px;height:16px;stroke:transparent;fill:none;stroke-width:2.6;
  stroke-linecap:round;stroke-linejoin:round;transition:stroke var(--fast);
}
.ck.on{background:var(--acc);box-shadow:none}
.ck.on svg{stroke:var(--on-acc)}

/* ------------------------------------------------------------- workout --- */
/* Workout header. In list mode it is pinned: pulled up over #app's top padding so it sits flush
   with the screen edge from the first pixel, and given the page's own ground plus a light blur so
   the rows scrolling underneath read as underneath. The connection indicator, when there is
   one, sits over its top (--conn) and its content starts below it. */
.whdr.stick{
  position:sticky;top:0;z-index:20;
  margin:calc(-1 * (var(--sat) + var(--conn) + 8px)) calc(-1 * var(--pad)) 6px;
  padding:calc(var(--sat) + var(--conn) + 8px) var(--pad) 0;
  background:color-mix(in srgb,var(--bg) 94%,transparent);
  -webkit-backdrop-filter:saturate(180%) blur(10px);backdrop-filter:saturate(180%) blur(10px);
}
.whdr.stick .hdr{margin-bottom:12px}
.whdr.stick .wprog{margin-bottom:10px}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .whdr.stick{background:var(--bg)}
}
/* The workout's own header (v1.3.11): ⌄ (or the editor's ✕), the name with the clock and set
   count, ⋯, and a labelled Finish pill. The name block takes what is left and truncates; the pill
   never shrinks. */
.hdr.whdr-bar{align-items:center;gap:8px;margin:4px 0 12px}
.whdr-mid{flex:1;min-width:0;text-align:center}
.whdr-name{font-weight:600;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* The clock and set count may take a second line: in ru/uk the count is the long half and a
   one-line ellipsis cut exactly the 0/19 the line is there for. */
.whdr-mid .sub{font-size:13px;margin-top:1px;font-variant-numeric:tabular-nums;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden;overflow-wrap:anywhere;text-wrap:balance}
.btn.pill.whdr-finish{width:auto;flex:none;padding:8px 16px;border-radius:99px;font-size:15px;max-width:40%}
/* Animations set to Small: the picture beside the exercise name, with an expand badge. */
.wthumb{position:relative;flex:none;width:48px;height:48px;border-radius:10px;overflow:hidden;padding:0}
.wthumb .thumb{width:48px;height:48px;border-radius:10px}
.wthumb .thumb-x{background:var(--surface-3)}
.wthumb-x{
  position:absolute;inset-inline-end:3px;bottom:3px;width:16px;height:16px;border-radius:5px;
  display:flex;align-items:center;justify-content:center;font-size:10px;
  background:rgba(0,0,0,.55);color:#fff;--icon-stroke:2.4;
}
.exhd{align-items:center}
.wprog{height:4px;background:var(--surface-2);border-radius:99px;overflow:hidden;margin-bottom:16px}
.wprog i{display:block;height:100%;background:var(--acc);border-radius:99px;transition:width var(--med) var(--ease)}
.exmedia{position:relative;border-radius:var(--r-lg);overflow:hidden;background:#fff;margin-bottom:12px}
.exmedia img{width:100%;height:320px;object-fit:contain;display:block}
.exmedia .gifhint,.exmedia .giftoggle{
  position:absolute;bottom:9px;display:inline-flex;align-items:center;gap:4px;
  background:rgba(0,0,0,.45);color:#fff;border-radius:99px;padding:5px 11px;
  font-size:12px;font-weight:500;
  -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
}
.exmedia .gifhint{inset-inline-end:9px}
.exmedia .giftoggle{inset-inline-start:9px}
.exmedia .gifhint .icn,.exmedia .giftoggle .icn{font-size:13px}
/* superset: both exercises are on screen together, so the animations have to give way —
   at 180px you had to scroll a full screen between two exercises done back-to-back */
.exmedia.compact img{height:120px}
.exmedia.mini img{height:84px}
/* both the animation and the still failed to load: a neutral tile instead of the browser's
   broken-image glyph; same heights as the picture so the card does not jump. tap retries. */
.exmedia.broken{background:var(--surface-2)}
.exmedia-x{width:100%;height:320px;display:flex;align-items:center;justify-content:center;color:var(--label-3);font-size:34px}
.exmedia.compact .exmedia-x{height:120px}
.exmedia.mini .exmedia-x{height:84px}
/* a custom exercise's own video (components/CustomMedia.jsx): the same box as a picture, on black
   so a clip that does not fill it is letterboxed like any player */
.exmedia video{width:100%;height:320px;object-fit:contain;display:block;background:#000}
.exmedia.compact video{height:120px}
.exmedia.mini video{height:84px}
.exmedia:has(> video){background:#000}
/* a catalogue animation: drawn on white, like the stills next to it */
.exmedia video.catvid,.exmedia:has(> video.catvid){background:#fff}

/* the link to a video or a guide (#246): a quiet action tile under the picture, or in its place.
   A <button>, so the reset of the browser's button look comes first. */
.exlink-card{
  display:flex;align-items:center;gap:12px;width:100%;text-align:start;font:inherit;border:0;
  background:var(--surface);border-radius:var(--r-card);
  padding:10px 14px;margin-bottom:12px;
  cursor:pointer;color:var(--label);
  transition:background var(--fast),transform var(--fast);
  user-select:none;-webkit-user-select:none;
}
.exlink-card:active{background:var(--surface-2);transform:scale(.98)}
.exlink-icon{
  width:34px;height:34px;border-radius:9px;
  display:flex;align-items:center;justify-content:center;
  background:var(--surface-2);color:var(--label);font-size:18px;flex:none;
}
.exlink-meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.exlink-title{
  font-size:15px;font-weight:500;color:var(--label);line-height:1.25;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
/* a host name reads left to right in every language; text-align keeps it on the reading side */
.exlink-sub{
  font-size:12px;color:var(--label-2);line-height:1.2;text-align:start;unicode-bidi:plaintext;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.exlink-arr{color:var(--label-3);font-size:14px;flex:none}
.exlink-card.mini{padding:6px 12px;gap:10px}
.exlink-card.mini .exlink-icon{width:28px;height:28px;font-size:15px;border-radius:7px}
.exlink-card.mini .exlink-title{font-size:13px}

/* the editor's field (components/CustomMediaField.jsx): the row, a 36px thumb of the draft, the
   buttons beside it, the notes under it and the link input */
.cmf{margin-top:8px}
.cmf .lrow{padding-inline:4px}
.cmf-act{display:flex;align-items:center;gap:6px;flex:none}
.cmf-thumb .thumb{width:36px;height:36px;border-radius:8px}
.cmf-thumb .thumb-x{font-size:16px}
.cmf-sub{display:inline-flex;align-items:center;gap:4px}
.cmf-sub .icn{font-size:13px}
.cmf-note{margin:0 4px 6px;line-height:1.35}
.cmf-link{margin-top:4px}
/* A logged workout's photos and videos (WorkoutMedia.jsx): a grid of posters with an Add tile,
   and the full-screen viewer they open in. Logical properties only, so RTL mirrors by itself. */
.wmedia{margin:4px 0 12px}
.wmedia-h{margin:0 0 6px}
.wmedia-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(72px,1fr));gap:8px}
.wmedia-item,.wmedia-add{
  position:relative;aspect-ratio:1;border:0;padding:0;border-radius:10px;overflow:hidden;
  background:var(--surface-2);color:var(--label-2);cursor:pointer;font:inherit;
}
.wmedia-item .thumb{width:100%;height:100%;border-radius:0;display:block}
.wmedia-item .thumb-x{font-size:22px}
.wmedia-kind{
  position:absolute;inset-inline-end:5px;bottom:5px;width:22px;height:22px;border-radius:99px;
  display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.55);color:#fff;font-size:12px;
}
.wmedia-add{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;
  color:var(--acc);font-size:11px;line-height:1.2;text-align:center;padding:6px;
  border:1.5px dashed var(--sep)}
.wmedia-add .icn{font-size:20px}
.wmedia-add:disabled{opacity:.5;cursor:default}
.wmedia-note{margin:6px 2px 0;line-height:1.35}
.wrow-media{display:inline-flex;align-items:center;gap:3px;font-size:12px;color:var(--label-2);flex:none}

.pp-selected{outline:3px solid var(--acc);outline-offset:-3px}
.pp-check{
  position:absolute;inset-inline-end:5px;top:5px;width:20px;height:20px;border-radius:99px;
  display:flex;align-items:center;justify-content:center;background:var(--acc);color:#fff;font-size:11px;
}
.pp-cta{position:sticky;bottom:calc(var(--sab) + 10px);display:flex;justify-content:center;margin-top:14px}
.pp-compare{position:relative;flex:1;min-height:0;overflow:hidden;background:#000;touch-action:none}
.pp-img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#000;
  -webkit-user-select:none;user-select:none;
}
.pp-handle{
  position:absolute;inset-block:0;transform:translateX(-50%);width:0;
  display:flex;align-items:center;justify-content:center;pointer-events:none;
}
.pp-handle::before{content:'';position:absolute;inset-block:0;width:2px;background:#fff;box-shadow:0 0 0 1px rgba(0,0,0,.3)}
.pp-grip{width:36px;height:36px;border-radius:99px;background:#fff;box-shadow:0 1px 6px rgba(0,0,0,.4)}
.pp-label{
  position:absolute;bottom:14px;font-size:12px;font-weight:600;color:#fff;
  background:rgba(0,0,0,.55);padding:4px 9px;border-radius:99px;pointer-events:none;
}
.pp-label-before{inset-inline-start:10px}
.pp-label-after{inset-inline-end:10px}
.wrow-media .icn{font-size:13px}
.mviewer{
  position:absolute;inset:0;background:#000;color:#fff;display:flex;flex-direction:column;
  padding:var(--sat) 0 var(--sab);animation:pop-fade .2s var(--ease);
}
@keyframes pop-fade{from{opacity:0}to{opacity:1}}
.mviewer-bar{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;flex:none}
.mviewer .iconbtn{color:#fff;background:rgba(255,255,255,.12)}
.mviewer-count{font-size:14px;font-variant-numeric:tabular-nums;opacity:.85}
.mviewer-stage{flex:1;min-height:0;display:flex}
.exmedia.viewer{flex:1;min-width:0;margin:0;border-radius:0;background:#000;display:flex;align-items:center;justify-content:center}
.exmedia.viewer img,.exmedia.viewer video{width:100%;height:100%;max-height:100%;object-fit:contain;background:#000}
.exmedia.viewer .exmedia-x{height:100%;color:rgba(255,255,255,.5)}
.mviewer-nav{display:flex;justify-content:space-between;padding:8px 12px;flex:none}
[dir="rtl"] .mviewer-nav .icn{transform:scaleX(-1)}
.mviewer-ask{flex:none;padding:12px 16px;background:rgba(255,255,255,.08)}
.mviewer-ask-t{font-weight:600;margin-bottom:10px}
.mviewer-ask-b{display:flex;gap:8px;justify-content:flex-end}

.sethead{
  display:flex;align-items:center;gap:8px;padding:0 0 6px;
  font-size:11px;color:var(--label-3);font-weight:500;text-transform:uppercase;letter-spacing:.045em;
}
.sethead .n-sp{width:24px;flex:none}
.sethead .w-sp{flex:1.4;text-align:center}
.sethead .r-sp{flex:1;text-align:center}
.sethead .eff-sp{text-align:center}
.sethead .ck-sp{width:30px;flex:none}
.setrow{display:flex;align-items:center;gap:8px;padding:7px 0;position:relative}
.setrow+.setrow::before{
  content:'';position:absolute;inset-inline-start:32px;inset-inline-end:0;top:0;height:var(--hair);background:var(--sep);
}
.setph{margin:8px 0 2px;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--dim);display:flex;align-items:center;gap:10px}
.setph::after{content:'';flex:1;height:1px;background:var(--sep)}
.setsep{height:1px;background:var(--sep);margin:9px 0}
/* the per-side wrapper carries its own .n (the set number in front of the L/R rows) */
.setrow .n,.setrow-side>.n{
  width:24px;height:24px;border-radius:50%;background:var(--surface-2);color:var(--label-2);
  display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:500;flex:none;
  position:relative;padding:0;border:0;font-family:inherit;cursor:pointer;
  transition:background var(--fast),color var(--fast);
}
.setrow .stp.w{flex:1.4;min-width:0}
.setrow .stp.r{flex:1;min-width:0}
/* The number outranks the buttons in every set row, not only the ones with an effort column
   that #235 covered: a 320 px phone left 0–6 px for the reps number and showed 102.5 as "102",
   and the cardio row's 10.5 km/h read "10" at 368 px (QA C2, C21, C24). The field reserves the
   widest value it shows at its font — "227.5" kg, "100" reps or "10.5" km/h (`--num`, incl.
   the field's 4 px padding and 2 px against font rounding) — and the two buttons give up width
   first, down to an 18 px floor, before anything is clipped. The cell itself is never narrower
   than number + two floors, so the row's flex hands a starving cell width from the others
   instead of clipping its buttons. */
.setrow .stp button{width:32px;height:40px}
/* Only the weight/reps buttons: `.effcell-stp` is a `.stp` too, and its value is a `<button>`,
   so a rule on `.setrow .stp button` would outrank `.effcell-stp .val` and stop the value from
   filling the cell (it bunched left with dead space next to it at desktop widths). */
.setrow .stp.w button,.setrow .stp.r button{flex:0 1 auto;min-width:var(--btn-floor)}
.setrow .stp.w,.sethead .w-sp{--num:48px}
.setrow .stp.r,.sethead .r-sp{--num:39px}
.setrow .stp.w,.setrow .stp.r,.sethead .w-sp,.sethead .r-sp{--btn-floor:18px;min-width:calc(var(--num) + 2*var(--btn-floor))}
/* no buttons (Workout controls → steppers off): the number alone is the floor. The header
   carries the same `plain` so its columns freeze where the cells do. */
.setrow .stp.plain,.sethead.plain .w-sp,.sethead.plain .r-sp{--btn-floor:0px}
.setrow .stp.w .val,.setrow .stp.r .val{min-width:var(--num)}
/* A timed hold logs the seconds in the first column and the load in the second, so the two
   columns swap roles: the load carries "227.5" and needs the wider reserve and the bigger share,
   the seconds column "120". Without the swap the load input was 35 px for a 41.7 px "102.5" at
   368 and 412 px — "102.5 kg" read "102.!" — while the seconds column sat on 67 px for "120". */
.setrow.timed .stp.w,.sethead.timed .w-sp{flex:1;--num:39px}
.setrow.timed .stp.r,.sethead.timed .r-sp{flex:1.4;--num:48px}
/* The effort column is a single tap-to-pick button, not a stepper. It is a direct flex child
   of the row (like the .stp cells), carrying its own column width via flex. `align-self` and
   a fixed height stop the row from stretching it taller than the weight/reps cells; it is NOT
   inside `.stp`, so the stepper's 32px fixed button width (which wrapped "RIR") never applies. */
/* flex-basis:0 so the button's width comes from its flex share, not its text, and the column
   never jumps between an empty "RIR" and a logged "2". The unrated state uses `.is-empty`, not
   the generic `.empty` (which is a full-page empty-state with 44px padding — sharing that name
   inflated the cell to ~90px). */
.setrow .effcell,.sethead .eff-sp{flex:.8 1 0;min-width:0}
/* The whole effort column — empty cell, logged cell and header label alike, so the row does not
   jump when a rating is added and the label does not drift — never goes below what the logged
   stepper needs at its floor: 28 px value + two 16 px buttons + the 1.5 px borders. The row's
   flex hands the difference back to weight and reps, whose own buttons shrink for it. */
.setrow .effcell,.setrow .effcell-stp,.setrow.eff3 .effcell-stp,.sethead .eff-sp{min-width:64px}
.effcell{
  box-sizing:border-box;height:44px;border-radius:10px;
  background:var(--surface-2);border:1.5px solid transparent;
  color:var(--label);font-size:15px;font-weight:600;letter-spacing:-.01em;
  font-variant-numeric:tabular-nums;white-space:nowrap;line-height:1;
  display:flex;align-items:center;justify-content:center;
  transition:background var(--fast),transform var(--fast),border-color var(--fast);
}
.effcell:active{transform:scale(.94)}
.effcell.is-empty{color:var(--label-2);font-weight:500;font-size:12px;letter-spacing:.02em}
/* a logged rating becomes a −/value/+ stepper, tinted by its effort band: the band colour
   fills the cell at low opacity (set inline via color-mix), borders it, and colours the value.
   the +/− buttons step 0.5 on the profile's scale; the value opens the picker. it carries the
   same column flex as the plain .effcell so the row does not jump when a rating is added. */
.setrow .effcell-stp,.setrow.eff3 .effcell-stp{
  height:44px;border-radius:10px;border:1.5px solid transparent;
  transition:background var(--fast),border-color var(--fast);
}
.effcell-stp button{color:inherit}
.effcell-stp>button[aria-label="Decrease"],
.effcell-stp>button[aria-label="Increase"]{width:26px;height:44px;font-size:15px;opacity:.85;flex:0 1 auto;min-width:16px}
/* the value is the tap target for the picker, so it keeps a 28 px finger's width for "8.5" and
   the −/+ shrink first — on a 368 px phone it had collapsed to 4 px under its own digits, and a
   tap on the number stepped the rating instead of opening the picker (QA C22) */
.effcell-stp .val{
  flex:1;min-width:28px;display:flex;align-items:center;justify-content:center;
  font-size:15px;font-weight:600;letter-spacing:-.01em;
  font-variant-numeric:tabular-nums;line-height:1;align-self:stretch;color:inherit;
}
.effcell-stp .val:active{background:var(--surface-3)}
/* the effort cell needs less room now (one short value, no +/- buttons), so weight/reps keep
   more of the row on narrow phones than they did when three steppers had to share it */
.setrow .effcell-stp,.setrow.eff3 .effcell-stp{flex:.8 1 0}
.setrow.eff3 .effcell-stp{flex:.7 1 0}
.setrow.eff3,.sethead.eff3{gap:6px}
.setrow.eff3 .stp.w,.sethead.eff3 .w-sp{flex:1.4}
.setrow.eff3 .stp.r,.sethead.eff3 .r-sp{flex:1.1}
.setrow.eff3 .effcell,.sethead.eff3 .eff-sp{flex:.7 1 0}
.setrow.eff3 .stp.w button,.setrow.eff3 .stp.r button{width:26px}
.setrow.eff3 .stp .num{font-size:14px}
/* the same reserved widths as a plain row's, at the smaller font */
.setrow.eff3 .stp.w,.sethead.eff3 .w-sp{--num:41px}
.setrow.eff3 .stp.r,.sethead.eff3 .r-sp{--num:30px}
.setrow.eff3 .effcell-stp>button[aria-label="Decrease"],
.setrow.eff3 .effcell-stp>button[aria-label="Increase"]{width:22px}
.setrow.eff3 .effcell-stp .val{font-size:14px}
/* effort quick picker sheet: colour-coded preset buttons, hardest (purple) first */
/* effort picker: the ⋯-menu list with the value in the icon square, tinted per band; the
   exact-value row carries the same stepper as the logged cell in the set row. */
.effpick.menu-list .menu-item .effpick-n{
  background:var(--bc);color:#fff;font-size:13px;font-weight:700;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;
}
.effpick .menu-item.on{background:color-mix(in srgb,var(--bc) 14%,var(--surface))}
.effpick .menu-on.is-on{color:var(--bc)}
.effpick .effpick-free{padding-inline-end:8px}
.effpick .effpick-free .stp{flex:none;width:148px}
.effpick .effpick-free .stp .num{font-size:17px;font-weight:600}
.effpick .effpick-free .stp .num::placeholder{color:var(--label-3);font-weight:500}
.setrow.done .n{background:var(--acc);color:var(--on-acc)}
.setrow.done{opacity:.45}

/* Swipe on sets (v1.3.11, components/SwipeRow.jsx). The row slides over two panes: Copy at the
   start of the line, Delete at its end, each as wide as the swipe has gone and zero wide at rest.
   pan-y leaves vertical scroll to the browser and hands sideways moves to the row. */
.swrow{position:relative;overflow:hidden;border-radius:10px;touch-action:pan-y;-webkit-touch-callout:none}
.swrow.dragging{-webkit-user-select:none;user-select:none}
/* Plan's lists (v1.3.11): the row is a whole .item card, so the swipe clips to the card's corners */
.swrow.swrow-item{border-radius:var(--r-card)}
.swfront{position:relative;z-index:1;background:var(--surface)}
/* Its own layer only while it moves: on every row it was a layer per set (#431). */
.swrow.dragging .swfront,.swrow.go-del .swfront,.swrow.go-cp .swfront{will-change:transform}
.swpane{position:absolute;top:0;bottom:0;width:0;overflow:hidden;z-index:0}
.swpane.del{inset-inline-end:0;background:color-mix(in srgb,var(--red) 22%,var(--surface))}
.swpane.cp{inset-inline-start:0;background:var(--acc-soft)}
.swpane button{position:absolute;top:0;bottom:0;width:76px;padding:0;color:#fff;transition:width .16s var(--ease)}
.swpane.del button{inset-inline-end:0;background:var(--red)}
.swpane.cp button{inset-inline-start:0;background:var(--acc);color:var(--on-acc)}
.swpane.armed button{width:100%}
.swpane .lab{position:absolute;top:0;bottom:0;width:76px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;font-size:12px}
.swpane.del .lab{inset-inline-start:0}
.swpane.cp .lab{inset-inline-end:0}
.swpane .lab .icn{font-size:21px;transform:scale(.8);transition:transform .16s var(--ease)}
.swpane .lab b{font-weight:500}
.swpane.armed .lab .icn{transform:scale(1)}
.swpane.armed .lab b{font-weight:800}
/* The last set cannot go: the delete side shows grey and never fills. */
.swpane.del.dis{background:var(--surface-2)}
.swpane.del.dis button{background:var(--surface-3);color:var(--label-3)}
.swpane.del.dis.armed button{width:76px}
.swrow.flash .swfront{animation:swflash .4s ease}
@keyframes swflash{0%,35%{background:color-mix(in srgb,var(--acc) 28%,var(--surface))}100%{background:var(--surface)}}
.swrow.flash-red .swfront{animation:swflashr .5s ease}
@keyframes swflashr{0%,35%{background:color-mix(in srgb,var(--red) 22%,var(--surface))}100%{background:var(--surface)}}
/* The first-use hint under the row it nudged. */
.swhint{display:flex;justify-content:center;padding:4px 0 6px}
.swhint button{display:inline-flex;align-items:center;gap:8px;background:var(--acc-soft);color:var(--acc);border-radius:99px;padding:7px 12px;font-size:13px;font-weight:600;animation:swhintin .3s var(--ease);text-align:start}
.swhint .icn{font-size:15px;flex:none}
.swhint .icn:last-child{opacity:.7}
@keyframes swhintin{from{opacity:0;transform:translateY(-4px)}}
.vh{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* one-sided (unilateral) sets (issue #60): the set number sits beside a two-row L/R stack, each
   side its own weight/reps/effort + done tick. The two inner rows reuse .setrow so their columns
   line up with the header exactly like a straight set — only the leading .n circle is replaced by
   a small L/R badge of the same width. */
.setrow-side{display:flex;align-items:center;gap:8px;padding:4px 0;position:relative}
.setrow-side+.setrow-side::before{content:'';position:absolute;inset-inline-start:32px;inset-inline-end:0;top:0;height:var(--hair);background:var(--sep)}
.setrow-side>.n{align-self:center}
.setrow-side .side-rows{flex:1;min-width:0;display:flex;flex-direction:column}
/* inner side rows: no leading number circle (the badge takes its place), no top hairline between
   the two sides of the same set — they read as one unit. */
.setrow-side .setrow.side{padding:4px 0}
.setrow-side .setrow.side+.setrow.side::before{display:none}
.sidetag{
  width:24px;height:24px;border-radius:8px;background:var(--surface-2);color:var(--label-3);
  display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;flex:none;
}
/* A one-sided row with an effort column is the tightest line in the app: set number, L/R badge,
   two steppers, effort, tick. On a narrow phone the reps stepper's two buttons took all of its
   width and the number between them was gone (issue #235, 368 px). The number outranks the
   buttons here: slimmer buttons, tighter gaps, and reps gets the same share as the weight. */
.setrow-side .setrow.side.eff3{gap:4px}
.setrow-side .setrow.side.eff3 .stp.r{flex:1.4}
.setrow-side .setrow.side.eff3 .effcell,.setrow-side .setrow.side.eff3 .effcell-stp{flex:.6 1 0}
.setrow-side .setrow.side.eff3 .stp.w button,.setrow-side .setrow.side.eff3 .stp.r button{width:20px}
/* The header is laid out like a straight row, but in front of an L/R row's weight cell sit the
   set number, its gap AND the side badge, so over per-side rows the labels stood 17–23 px left
   of their cells (QA C5). `.per-side` (set by Workout.jsx) widens the number slot by the badge
   and takes the badge's gap as its own, so header and rows share one column track; with an
   effort column it also mirrors the side rows' own shares (reps 1.4, effort .6). */
.sethead.per-side .n-sp{width:56px}
.sethead.per-side.eff3{gap:4px}
.sethead.per-side.eff3 .r-sp{flex:1.4}
.sethead.per-side.eff3 .eff-sp{flex:.6 1 0}
@media (max-width:400px){
  /* the same squeeze on a plain row, where the list view's own inset costs the last pixels */
  .setrow.eff3 .stp.w button,.setrow.eff3 .stp.r button{width:21px}
  .setrow.eff3 .stp.r,.sethead.eff3 .r-sp{flex:1.25}
  .setrow-side{gap:5px}
  /* and on every L/R row, with or without an effort column — the default profile (no effort,
     buttons on) lost the per-side reps number the same way at 320 and 368 px (QA C2, C3) */
  .setrow-side .setrow.side{gap:4px}
  .setrow-side .setrow.side .sidetag{width:20px}
  .setrow-side .setrow.side .stp .num{font-size:14px}
  .setrow-side .setrow.side .stp.w,.sethead.per-side .w-sp{--num:41px}
  .setrow-side .setrow.side .stp.r,.sethead.per-side .r-sp{--num:30px}
  .setrow-side .side-rows .setrow.side .stp.w button,.setrow-side .side-rows .setrow.side .stp.r button{width:18px}
  /* number 24 + gap 5 + badge 20; the row's 4 px gap is the header's too */
  .sethead.per-side{gap:4px}
  .sethead.per-side .n-sp{width:49px}
}
/* The effort column steps down twice on the way to a narrow screen, not once. Its −/+ are gone
   below 360 px (see the 359 px query), and from 366 px an L/R row carries them at full size in a
   sets card two insets deep — in a list unit, or in a superset card. In between it could do
   neither: the 64 px the pill needs for a 28 px value and two 16 px button floors left the row
   6, 5, 4, 3, 2 and 1 px wider than its card's content box at 360–365 px, so its done tick sat
   in the card's side padding. The −/+ give way there too, to a 12 px floor — they are what the
   cell sheds first, a pixel further down they go altogether — and the row keeps everything that
   carries meaning: the rating stays a 28 px tap target, the weight and reps columns keep their
   numbers, their buttons and their floors, and the header keeps its single line ("WEIGHT (KG)"
   has 1.5 px to spare in a 77 px column and would wrap if the weight column paid instead).
   An L/R row in a superset in the list view has no −/+ to shrink below 430 px; the rule for it
   below is more specific and keeps the cell at 34 px. */
@media (min-width:360px) and (max-width:365px){
  .setrow.side .effcell,.setrow.side .effcell-stp,.sethead.per-side .eff-sp{min-width:56px}
  .setrow.side .effcell-stp>button[aria-label="Decrease"],
  .setrow.side .effcell-stp>button[aria-label="Increase"]{min-width:12px}
}
/* An L/R row with an effort column inside a superset in the list view sits behind three insets
   (unit, superset card, sets card). It used to drop the RIR −/+ here and hand the width to an
   oversized weight cell — but the rating is the field you reach for most on a unilateral set, so
   it keeps the same −/+ stepper a normal row has, just shrunk, and the weight cell pays for it
   instead. The effort cell reserves its 28 px value plus two 12 px button floors (≈54 px); the
   weight column gives up the matching width (a per-side weight is one limb's load, so its 48 px
   reserve was generous), so the row's total is unchanged and everything still fits at 412 px. */
@media (max-width:429px){
  .wl-unit .ss-card .setrow.side .effcell-stp>button[aria-label="Decrease"],
  .wl-unit .ss-card .setrow.side .effcell-stp>button[aria-label="Increase"]{min-width:12px}
  .wl-unit .ss-card .setrow.side .effcell,.wl-unit .ss-card .setrow.side .effcell-stp,.wl-unit .ss-card .sethead.per-side .eff-sp{min-width:54px}
  /* reclaim the +/-'s room from the weight column: a slimmer number reserve and tighter button
     floors, the same shape the 320 px step-down below already gives weight on these rows */
  .wl-unit .ss-card .setrow.side.eff3 .stp.w:not(.plain),.wl-unit .ss-card .sethead.per-side.eff3:not(.plain) .w-sp{--btn-floor:12px}
  .wl-unit .ss-card .setrow.side.eff3 .stp.w,.wl-unit .ss-card .sethead.per-side.eff3 .w-sp{--num:33px}
  .wl-unit .ss-card .setrow.side.eff3 .stp.w button{width:18px}
}
/* Below 360 px a row with an effort column cannot hold three numbers and six buttons: with the
   set number, tick and gaps gone a card leaves ~178 px, and 102.5 / 12 / 8.5 at their floors need
   ~197. The effort −/+ give way there on every row — the value stays (26 px for "8.5" plus the
   border), still opens the picker on tap, and the picker's exact row steps in halves as before;
   weight and reps keep their buttons. */
@media (max-width:359px){
  .setrow .effcell-stp>button[aria-label="Decrease"],.setrow .effcell-stp>button[aria-label="Increase"]{display:none}
  .setrow .effcell,.setrow .effcell-stp,.setrow.eff3 .effcell-stp,.sethead .eff-sp,
  .wl-unit .ss-card .setrow.side .effcell,.wl-unit .ss-card .setrow.side .effcell-stp,.wl-unit .ss-card .sethead.per-side .eff-sp{min-width:29px}
  .setrow .effcell-stp .val{min-width:26px}
  /* an L/R row with an effort column is still over by 14 px in a card at 320 px (badge 20, 77 + 66 +
     29 for the three cells, tick 30, four gaps): one notch further there — 16 px button floors
     and a 13 px number — and it fits with two pixels to spare. Not with the buttons off (`plain`),
     where the number alone is the floor. */
  .setrow-side .setrow.side.eff3 .stp.w:not(.plain),.setrow-side .setrow.side.eff3 .stp.r:not(.plain),.sethead.per-side.eff3:not(.plain) .w-sp,.sethead.per-side.eff3:not(.plain) .r-sp{--btn-floor:16px}
  .setrow-side .setrow.side.eff3 .stp .num{font-size:13px}
  .setrow-side .setrow.side.eff3 .stp.w,.sethead.per-side.eff3 .w-sp{--num:38px}
  .setrow-side .setrow.side.eff3 .stp.r,.sethead.per-side.eff3 .r-sp{--num:28px}
  /* A weighted timed hold has one control more than a straight row (the play button before the
     tick) and was 15 px over at 320 px with the full-size floors: seconds ("120") and the load
     ("102.5") at the 14 px size and 16 px button floors, as an effort row does. */
  .setrow.timed .stp .num{font-size:14px}
  .setrow.timed .stp.w,.sethead.timed .w-sp{--num:30px}
  .setrow.timed .stp.r,.sethead.timed .r-sp{--num:41px}
  .setrow.timed .stp.w:not(.plain),.setrow.timed .stp.r:not(.plain),.sethead.timed:not(.plain) .w-sp,.sethead.timed:not(.plain) .r-sp{--btn-floor:16px}
}
.setrow.side.done .sidetag{background:var(--acc);color:var(--on-acc)}
.setrow.side.done{opacity:.45}

/* drop-set drops / rest-pause bursts riding on a work row, editable like the row itself
   (issue: drop-sets & rest-pause) */
.subrow{display:flex;align-items:center;gap:6px;padding-block:3px;padding-inline:32px 0}
/* Under an L/R row the set number already sits outside the .side-rows box, so the indent that
   tucks a straight row's drops under its number would be paid twice — at 320 px both number
   fields of a per-side drop collapsed to 0 px (QA C3). The label starts under the badge instead. */
.setrow-side .side-rows .subrow{padding-inline-start:0}
.subrow .subn{font-size:11px;color:var(--label-3);flex:none;width:46px}
/* the number outranks the buttons here as in the row above: room for "227.5" (a drop's weight)
   or "100" (the second mini is always reps) at 13 px, and the buttons shrink to 16 px first */
.subrow .stp.mini{flex:1;--num:38px;min-width:calc(var(--num) + 32px)}
.subrow .stp.mini+.stp.mini{--num:28px}
.subrow .stp.mini button{width:26px;height:30px;flex:0 1 auto;min-width:16px}
.subrow .stp.mini .val{min-width:var(--num)}
.subrow .stp.mini .num{font-size:13px;min-height:30px}
.subrow .iconbtn{width:26px;height:26px;font-size:11px;flex:none}
/* label 46, two minis at their floors 70 + 60, the ✕ and three gaps: 252 px with the indent — the
   whole row at 320 px. The label starts under the number circle's inner edge there instead. */
@media (max-width:359px){
  .subrow,.plateline{padding-inline-start:24px}
}
/* Plate line under a set row (Workout.jsx loadLine): the stack for this row's weight, what is
   missing from the inventory, and what to strip/add from the row above. */
.plateline{display:flex;align-items:center;gap:6px;padding-block:0 5px;padding-inline:32px 0;font-size:12px;color:var(--dim);line-height:1.3}
.plateline .icn{font-size:12px;flex:none;opacity:.8}
.plateline .short{color:var(--orange)}
.plateline .moves{margin-inline-start:auto;color:var(--label-3);font-variant-numeric:tabular-nums;white-space:nowrap}
/* The number is the set's menu (drop / burst / remove); a 24px circle needs a finger-sized hit area. */
.setrow .n::before{content:'';position:absolute;inset:-10px}
/* A set taken to failure: a small "F" on the number's corner, in the colour the effort scale
   gives "nothing left" (effort.js EFFORT_BANDS). */
.setrow .n .failmark,.setrow-side>.n .failmark{position:absolute;top:-5px;inset-inline-end:-6px;min-width:13px;height:13px;padding:0 2px;border-radius:7px;background:var(--purple);color:#fff;font-size:9px;font-weight:700;line-height:13px;text-align:center;pointer-events:none}
/* No +/- buttons: the number field takes the whole cell and is the tap target. */
.stp.plain{grid-template-columns:1fr}
.stp.plain .val{width:100%}
.stp.plain .num{width:100%;text-align:center}
/* --- menu sheet (the workout's "more" menus) --- */
.menu-list .menu-item{min-height:52px;padding:8px 12px}
.menu-list .menu-item .lrow-i{width:32px;height:32px;font-size:16px;background:var(--surface-2);color:var(--label)}
.menu-list .menu-item.danger .tt,.menu-list .menu-item.danger .lrow-i{color:var(--red)}
.menu-list .menu-item.disabled{opacity:.4;pointer-events:none}
.menu-on{color:transparent;font-size:16px;flex:none}
.menu-on.is-on{color:var(--acc)}
/* A grouped menu (menuSheet's `sections`): groups sit apart, each with a small header, so a long
   list reads as four short ones. */
.menu-group.grouped+.menu-group.grouped{margin-top:14px}
.menu-group.grouped .menu-list{gap:0;background:var(--surface);border-radius:var(--r-card);overflow:hidden}
.menu-group.grouped .menu-item{border-radius:0;position:relative}
.menu-group.grouped .menu-item+.menu-item::before{
  content:'';position:absolute;top:0;inset-inline-start:56px;inset-inline-end:0;height:var(--hair);background:var(--sep-op);
}
.menu-sec{font-size:13px;font-weight:600;color:var(--label-2);padding:0 6px 6px;letter-spacing:.005em}
.menu-list .menu-item.accent .lrow-i{background:var(--acc);color:var(--on-acc)}
.menu-chev{color:var(--label-3);font-size:14px;flex:none}
[dir="rtl"] .menu-chev{transform:scaleX(-1)}
.setextra{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 8px 32px}
.setextra .chip.add{display:inline-flex;align-items:center;gap:4px;color:var(--acc)}
.setextra .chip.add svg{width:13px;height:13px}
.setextra .chip.add:disabled{opacity:.35}
/* why the next target is what it is (issue #17) */
.progline{
  width:100%;display:flex;align-items:flex-start;gap:7px;margin:6px 0 2px;padding:0;
  text-align: start;font-size:13px;line-height:1.38;color:var(--acc);
}
.progline.warn{color:var(--yellow)}
.progline svg{flex:none;margin-top:2px}
/* "Last time" / "Best set" under the plan line (#173): a button that switches between the two,
   looking like the plain line it replaced, with a small icon to say it can be tapped. */
.refline{width:100%;display:flex;align-items:flex-start;gap:6px;margin-bottom:4px;padding:0;text-align:start}
.refline svg{flex:none;margin-top:2px;font-size:12px}
/* an exercise kept out of progression for this session, with its undo */
.noprog{display:flex;align-items:center;gap:7px;margin:0 0 6px;font-size:13px;line-height:1.38;color:var(--orange)}
.noprog svg{flex:none}
.noprog .chip{margin-inline-start:auto;padding:3px 11px;font-size:13px;color:var(--orange);background:color-mix(in srgb,var(--orange) 14%,transparent)}

/* start a timed set (issue #16) — sits where you'd otherwise reach for the checkbox */
.setrow .setgo{
  width:30px;height:30px;border-radius:50%;flex:none;
  display:flex;align-items:center;justify-content:center;
  background:var(--surface-2);color:var(--acc);font-size:14px;
  transition:background var(--fast),opacity var(--fast);
}
.setrow .setgo:disabled{opacity:.3;cursor:default}
.setrow .setgo:not(:disabled):active{background:var(--surface-3)}
/* exercise-config sheet: three steppers side by side on narrow phones */
.cfgrow{gap:8px}
.cfgrow>div{flex:1;min-width:0}
.cfgrow .stp button{width:30px}
.cfgrow .stp-l{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* The double-progression row holds four (Step / Reps from / Reps up to / Deload 1RM). Each stepper
   already spends 64px on its two buttons, so four abreast left the input 1px at 320 and 13px at
   368 — "1.25" showed as "1.2", "90" as "9(". Below 480px the row wraps into two pairs instead;
   from there up four abreast still leaves the input 40px or more. The basis is half the row minus
   half the gap, so each line takes exactly two. */
@media (max-width:479px){
  .cfgrow.cfgrow-4{flex-wrap:wrap}
  .cfgrow.cfgrow-4>div{flex-basis:calc(50% - 4px)}
}

/* --- rest timer --- */
/* One row: the clock (a button, tap to set the time left), then the controls; the progress is a
   thin line along the top edge, so it takes no width from them. On the workout screen, which has
   no tab bar (body.no-tabbar, App.jsx), the bar is docked to the bottom edge; anywhere else it
   sits just above the tab bar. */
#timer{
  position:fixed;inset-inline-start:12px;inset-inline-end:12px;bottom:calc(88px + var(--sab));z-index:60;
  /* nearly opaque: the set rows underneath were reading through the old 82% and made the
     clock hard to pick out mid-set. The blur stays, so it still sits above the page. */
  background:color-mix(in srgb,var(--surface) 97%,transparent);
  -webkit-backdrop-filter:saturate(180%) blur(10px);backdrop-filter:saturate(180%) blur(10px);
  border-radius:var(--r-lg);padding:12px 12px 10px;overflow:hidden;
  display:flex;align-items:center;gap:10px;
  box-shadow:0 12px 34px -8px rgba(0,0,0,.55);
  animation:slideup var(--med) var(--ease);
}
@keyframes slideup{from{transform:translateY(16px);opacity:0}to{transform:none;opacity:1}}
#timer .bar{position:absolute;top:0;inset-inline-start:0;inset-inline-end:0;height:3px;background:var(--surface-3)}
#timer .bar i{display:block;height:100%;background:var(--acc);transition:width 1s linear}
/* The clock is what the bar is for, so it never gives up width: the buttons do (Skip is the one that
   gives: in a long language on a narrow phone it becomes an icon, RestTimer.jsx useSkipFits). The label under the
   clock fills the clock's width but does not widen it. */
#timer .tclock{flex:none;min-width:0;display:flex;flex-direction:column;align-items:flex-start;text-align:start;padding:2px 0}
#timer .t{font-size:28px;font-weight:600;line-height:1.1;letter-spacing:-.026em;font-variant-numeric:tabular-nums;white-space:nowrap}
/* The rest clock is as wide as the wider of its two faces, the time and Ready: the face not
   showing sits in a hidden zero-height line (data-alt), so turning Ready does not move the
   buttons beside it. */
#timer.rest .t{display:flex;flex-direction:column}
#timer.rest .t::after{content:attr(data-alt);height:0;overflow:hidden;visibility:hidden}
#timer .lbl{
  font-size:12px;color:var(--label-2);width:0;min-width:100%;margin-top:1px;
  display:inline-flex;align-items:center;gap:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
#timer .lbl .icn{font-size:10px;flex:none;opacity:.8}
#timer .acts{display:flex;align-items:center;justify-content:flex-end;gap:6px;flex:1 1 auto;min-width:0}
#timer .acts .btn{padding-top:11px;padding-bottom:11px}   /* sweaty hands, mid-set */
#timer .acts .adj{padding-inline:9px;gap:2px}
#timer .acts .btn.plain{background:var(--surface-2);color:var(--label)}
#timer .acts .btn.plain:active{background:var(--surface-3)}
#timer .acts .btn{flex:none}
#timer .acts .skip{min-width:min(70px,100%);flex:0 1 auto}
#timer .acts .skip > span{display:grid;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Skip and Dismiss stacked in one cell, the one not in use hidden, so the button keeps one width
   between counting and Ready; so does the pause slot, kept invisible once the rest is over. */
#timer .acts .skip > span > span{grid-area:1/1;min-width:0;overflow:hidden;text-overflow:ellipsis}
#timer .acts .skip .off,#timer .acts .pause-slot{visibility:hidden}
/* Hidden copies of both labels, at their natural width, that RestTimer measures to decide
   whether Skip shows its label or only its icon (skip-forward, or × once Ready). */
#timer .acts .skip{position:relative}
#timer .acts .skip > .skip-probe{position:absolute;inset-inline-start:0;top:0;visibility:hidden;pointer-events:none;display:block;overflow:visible;width:max-content}
#timer .acts .skip > .skip-probe > span{display:block;width:max-content;white-space:nowrap}
#timer .acts .skip.icon-only{min-width:0}
/* A paused rest reads as held: the clock and the bar step back until it runs again. */
#timer.rest.paused .t,#timer.rest.paused .bar{opacity:.5}
/* work timer (issue #16): the same bar doing the opposite job — outlined and tinted so a
   glance tells "you are holding this" apart from "you are recovering". */
#timer.working{box-shadow:inset 0 0 0 var(--hair) var(--acc),0 12px 34px -8px rgba(0,0,0,.55)}
#timer.working .t{color:var(--acc)}
/* The label is the exercise name, handed in already cased (exerciseNameText): a capitalize here
   would title-case German's own casing too. */
#timer.working .lbl{display:block}
/* A hold's label is the exercise name, so there the clock side takes the room and its two
   buttons keep theirs; the time itself still never shrinks (the label adds no minimum). */
#timer.working .tclock{flex:1 1 0;min-width:auto}
#timer.working .acts{flex:none}
/* Docked on the workout screen: edge to edge, flush with the bottom, the home indicator's inset
   inside it. */
body.no-tabbar #timer{
  inset-inline-start:0;inset-inline-end:0;bottom:0;
  border-radius:var(--r-xl) var(--r-xl) 0 0;padding:14px 14px calc(10px + var(--sab));
  box-shadow:0 -10px 30px -14px rgba(0,0,0,.55);
}
body.no-tabbar #timer.working{box-shadow:inset 0 var(--hair) 0 var(--acc),0 -10px 30px -14px rgba(0,0,0,.55)}
@media (max-width:399px){
  #timer .acts{gap:4px}
  #timer .acts .adj{padding-inline:6px;gap:1px}
  #timer .acts .pause,#timer .acts .pause-slot{padding-inline:10px}
  #timer .acts .skip{padding-inline:10px}
}
@media (max-width:359px){
  body.no-tabbar #timer,#timer{padding-inline:10px}
  #timer .t{font-size:25px}
  #timer .acts .skip{min-width:0}
}

/* ---------------------------------------------------------------- modal -- */
#modal-root{position:fixed;inset:0;z-index:100;display:none}
#modal-root.open{display:block}
.mback{
  position:absolute;inset:0;background:rgba(0,0,0,.4);
  -webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);
  touch-action:none;overscroll-behavior:none;
}
.sheet{
  position:absolute;inset-inline-start:0;inset-inline-end:0;bottom:0;background:var(--bg-el);
  border-radius:var(--r-xl) var(--r-xl) 0 0;
  padding:8px 18px calc(20px + var(--sab));
  /* A tall sheet stops short of the status bar / Dynamic Island: every cap carries the top
     safe-area inset, which the layout viewport does not (viewport-fit=cover). */
  max-height:90vh;max-height:min(90vh,calc(100dvh - var(--sat) - 8px - var(--native-kb,0px)));
  overflow-y:auto;scroll-padding-bottom:calc(20px + var(--sab));
  animation:sheetup .34s var(--ease);
  overscroll-behavior:contain;touch-action:pan-y;will-change:transform;
}
/* A sheet with a field (.picker-search) or marked .kb-lift (the connect sheet) stands on the
   keyboard instead of letting the system scroll the pinned page to reach the field: see
   lib/use-sheet-keyboard.js for the two variables. */
.sheet:has(.picker-search),.sheet:has(.kb-lift){
  bottom:max(var(--picker-keyboard-bottom,0px),var(--native-kb,0px));
  /* with the keyboard up the sheet is capped by the visual viewport — which still starts
     under the notch, hence the inset here too */
  max-height:min(90vh,calc(100dvh - var(--sat) - 8px - var(--native-kb,0px)),calc(var(--picker-visual-height,100vh) - var(--sat) - 8px));
}
/* Android 15: the window is not resized for the soft keyboard, the app passes the height it
   covers as --native-kb instead (MainActivity.keyboardScript, lib/native-keyboard.js). Every
   sheet stands on the keyboard, and its foot drops the navigation-bar inset, which the keyboard
   already covers. Older Android versions resize the window and never set the variable. */
:root[data-native-kb] .sheet{
  bottom:max(var(--picker-keyboard-bottom,0px),var(--native-kb,0px));
  padding-bottom:20px;scroll-padding-bottom:20px;
}
:root[data-theme="light"] .sheet{background:var(--bg)}
@keyframes sheetup{from{transform:translateY(100%)}to{transform:none}}
.sheet .grab{width:36px;height:5px;border-radius:99px;background:var(--label-4);margin:6px auto 14px}
.sheet h3{font-size:20px;font-weight:600;letter-spacing:-.021em;margin-bottom:14px}
.picker-search{
  position:sticky;top:0;z-index:2;margin-bottom:0;padding-bottom:10px;
  background:var(--bg-el);
}
:root[data-theme="light"] .picker-search{background:var(--bg)}
.center{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:min(84vw,300px);background:var(--bg-el);border-radius:var(--r-lg);padding:20px;
  max-height:calc(100dvh - 2*var(--sat) - 24px);overflow-y:auto;
  box-shadow:0 20px 60px -12px rgba(0,0,0,.5);
  animation:pop .28s var(--ease);
}
:root[data-theme="light"] .center{background:#fff}
/* a dialog with a field centres in what the Android 15 keyboard leaves */
:root[data-native-kb] .center{
  top:calc((100% - var(--native-kb)) / 2);
  max-height:calc(100dvh - var(--native-kb) - 2*var(--sat) - 24px);
}
@keyframes pop{from{transform:translate(-50%,-50%) scale(.94);opacity:0}to{transform:translate(-50%,-50%) scale(1);opacity:1}}

/* --- RIR/RPE conversion table (effort help sheet) ---
   The two scales only click when you see them side by side, so the numbers get fixed-width
   tabular columns you can read straight down, and the words take whatever is left. Built out
   of the same grouped-list parts as the rest of the app (surface fill, inset hairlines) rather
   than a real <table>, so it inherits the card look and wraps properly on a narrow phone. */
.efftbl{background:var(--surface);border-radius:var(--r-lg);overflow:hidden;margin:14px 0 12px}
.efftbl .r{display:flex;align-items:center;gap:10px;padding:10px 14px;position:relative}
.efftbl .r+.r::before{
  content:'';position:absolute;inset-inline-start:14px;inset-inline-end:0;top:0;height:var(--hair);background:var(--sep);
}
.efftbl .n{
  width:36px;flex:none;text-align:center;font-size:17px;font-weight:600;
  letter-spacing:-.02em;font-variant-numeric:tabular-nums;
}
.efftbl .f{flex:1;min-width:0;font-size:15px;color:var(--label-2);line-height:1.3}
/* the header names the columns without competing with them */
.efftbl .hd{background:var(--surface-2);padding-top:8px;padding-bottom:8px}
.efftbl .hd .n,.efftbl .hd .f{
  font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;
  color:var(--label-3);
}
/* the row a working set usually lands on — the anchor the others are read against */
.efftbl .r.on{background:var(--acc-soft)}
.efftbl .r.on .n{color:var(--acc)}
.efftbl .r.on .f{color:var(--label)}

/* --- glyph picker (replaces the emoji grid) --- */
.glyph-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:9px}
.glyph-cell{
  aspect-ratio:1;border-radius:13px;background:var(--surface);
  display:flex;align-items:center;justify-content:center;font-size:24px;
  color:var(--label);transition:background var(--fast),color var(--fast),transform var(--fast);
}
.glyph-cell:active{transform:scale(.92)}
.glyph-cell.on{background:var(--acc);color:var(--on-acc)}

/* --- big weight readout --- */
.bwstep{display:flex;align-items:center;justify-content:center;gap:18px;margin:14px 0 6px}
.bw-pm{
  width:46px;height:46px;border-radius:50%;background:var(--surface);flex:none;
  color:var(--label);display:flex;align-items:center;justify-content:center;font-size:19px;
  transition:background var(--fast),transform var(--fast);--icon-stroke:2.2;
}
.bw-pm:active{background:var(--surface-2);transform:scale(.92)}
.bw-read{
  font-size:52px;font-weight:600;letter-spacing:-.035em;min-width:158px;text-align:center;
  font-variant-numeric:tabular-nums;line-height:1;
}
.bw-read .u{font-size:19px;color:var(--label-2);font-weight:400;letter-spacing:-.01em}
.bw-read .num{
  display:inline-block;vertical-align:baseline;background:none;border:none;outline:none;
  padding:0;margin:0;color:inherit;font:inherit;letter-spacing:inherit;
  font-variant-numeric:tabular-nums;text-align:center;cursor:text;
  -moz-appearance:textfield;appearance:textfield;
}

/* --------------------------------------------------------------- charts -- */
.chart{width:100%;overflow:hidden}
.chart svg{display:block;width:100%;height:auto}
.chart-i{position:relative;touch-action:pan-y}
/* left/top are written by LineChart after measuring the label, so it can never be
   pushed under the `.chart` clip at either edge — hence no offset here. */
.ctip{
  position:absolute;left:0;top:0;background:var(--surface-2);
  border-radius:8px;padding:4px 10px;font-size:12px;font-weight:500;white-space:nowrap;
  max-width:calc(100% - 8px);overflow:hidden;text-overflow:ellipsis;
  pointer-events:none;box-shadow:0 6px 18px -4px rgba(0,0,0,.4);
}

/* --- activity heatmap --- */
.hm-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;padding-bottom:2px}
.hm-wrap::-webkit-scrollbar{display:none}
.hm-months{display:flex;margin-inline-start:28px;margin-bottom:5px}
.hm-months span{flex:none;width:14px;font-size:11px;color:var(--label-3);white-space:nowrap;overflow:visible}
.hm-body{display:flex}
.hm-days{display:flex;flex-direction:column;gap:3px;flex:none;width:28px}
.hm-days span{height:11px;font-size:10px;color:var(--label-2);line-height:11px}
.hm-grid{display:flex;gap:3px}
.hm-col{display:flex;flex-direction:column;gap:3px}
.hm-c{width:11px;height:11px;border-radius:3px;background:var(--surface-2);flex:none}
.hm-c.l1{background:color-mix(in srgb,var(--acc) 30%,var(--surface-2))}
.hm-c.l2{background:color-mix(in srgb,var(--acc) 55%,var(--surface-2))}
.hm-c.l3{background:color-mix(in srgb,var(--acc) 78%,var(--surface-2))}
.hm-c.l4{background:var(--acc)}
.hm-c.today{box-shadow:0 0 0 1.5px var(--acc)}
.hm-c.future{opacity:.3}
.hm-c.noted{box-shadow:inset 0 0 0 1.5px var(--indigo)}
.hm-legend{
  display:flex;align-items:center;gap:4px;justify-content:flex-end;margin-top:9px;
  font-size:11px;color:var(--label-3);
}
.hm-legend .hm-c{width:10px;height:10px}

/* "Share as image" (#453): the card as it will be sent, at the sheet's width. The placeholder
   holds a portrait card's shape so nothing below it jumps when the picture lands. */
.share-card{margin:0 auto 14px;max-width:420px}
.share-card img{display:block;width:100%;max-height:56vh;object-fit:contain;border-radius:16px;box-shadow:0 6px 24px rgba(0,0,0,.28)}
.share-card-ph{aspect-ratio:4/5;max-height:56vh;margin:0 auto;border-radius:16px;background:var(--surface-2);display:flex;align-items:center;justify-content:center;text-align:center;padding:16px}
/* --- muscle map --- */
/* Reuses the .hm-c level ramp above, so one accent scale means one thing app-wide. */
/* An untrained muscle is derived from the text colour rather than a fixed surface, so
   the body keeps the same contrast in both themes — pinned to --surface-2 it was all
   but invisible on a white card. */
.bodymap{
  display:flex;gap:6px;justify-content:center;align-items:flex-start;
  --bm-base:color-mix(in srgb,var(--label) 11%,var(--surface));
}
.bm-v{flex:1;min-width:0;height:auto;max-height:min(46vh,340px)}
.bm-ph{height:200px}                       /* holds the space while the geometry loads */
.bm-sil{fill:color-mix(in srgb,var(--label) 18%,var(--surface))}
.bm-m{fill:var(--bm-base);transition:fill var(--med) var(--ease)}
.bm-m.l1{fill:color-mix(in srgb,var(--acc) 32%,var(--bm-base))}
.bm-m.l2{fill:color-mix(in srgb,var(--acc) 56%,var(--bm-base))}
.bm-m.l3{fill:color-mix(in srgb,var(--acc) 78%,var(--bm-base))}
.bm-m.l4{fill:var(--acc)}
/* Fatigue view: attention ramp - fatigued muscles highlighted (owner tweak 2026-08-06) */
.hm-fatigue .bm-m.l4{fill:var(--red)}
.hm-fatigue .bm-m.l3{fill:var(--orange)}
.hm-fatigue .bm-m.l2{fill:var(--yellow)}
.hm-fatigue .bm-m.l1{fill:color-mix(in srgb,var(--yellow) 32%,var(--bm-base))}
.hm-fatigue .hm-c.l4{background:var(--red)}
.hm-fatigue .hm-c.l3{background:var(--orange)}
.hm-fatigue .hm-c.l2{background:var(--yellow)}
/* Strength view: top band pops gold (owner tweak 2026-08-06) */
.hm-strength .bm-m.l4{fill:var(--yellow);filter:drop-shadow(0 0 1.5px var(--yellow))}
.hm-strength .hm-c.l4{background:var(--yellow)}

.bm-m.sel{stroke:var(--label);stroke-width:7}
.bodymap.tappable .bm-m{cursor:pointer}
.bodymap.tappable .bm-m:focus-visible{outline:none;stroke:var(--label);stroke-width:7;filter:drop-shadow(0 0 2px var(--surface))}
/* A hairline in the surface colour separates neighbouring muscles; the artwork has no
   outline path of its own, so without this the body reads as one flat blob. */
.bm-v path{stroke:var(--surface);stroke-width:2.5;stroke-linejoin:round}

.mrow{display:flex;align-items:center;gap:9px;padding:5px 0}
.mrow .nm{flex:1;min-width:0;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.mrow .bar{width:74px;height:5px;border-radius:3px;background:var(--surface-2);flex:none;overflow:hidden}
.mrow .bar i{display:block;height:100%;background:var(--acc);border-radius:3px}
.mrow .v{font-size:12px;color:var(--label-2);min-width:52px;text-align: end;flex:none}
/* The Week view's "done/planned · %" (#573): wider than a set count, and as wide on every row, so
   the bars line up the way they do in 30d, 90d and All. */
.mrow .v.cmp{min-width:92px;font-variant-numeric:tabular-nums;white-space:nowrap}
/* A headline number inside a card — tile weight without a tile around it, for the pair of
   readings at the top of the effort card. */
.stat-v{font-size:26px;font-weight:600;letter-spacing:-.026em;line-height:1.1}
.mchips{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px}
.mchip{font-size:12px;padding:4px 9px;border-radius:99px;background:var(--surface-2);color:var(--label-2)}
.mchip.miss{background:color-mix(in srgb,var(--orange) 16%,transparent);color:var(--orange)}
button.mchip{border:none;font:inherit;font-size:12px;cursor:pointer}

/* --- stat tiles --- */
.tiles{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px}
.tile{background:var(--surface);border-radius:var(--r-card);padding:14px}
.tile .l{
  color:var(--label-2);font-size:13px;font-weight:400;letter-spacing:-.004em;
  text-transform:none;display:flex;align-items:center;gap:5px;
}
.tile .l .icn{font-size:14px;color:var(--label)}
.tile .v{font-size:26px;font-weight:600;letter-spacing:-.026em;margin-top:5px;line-height:1.1}
.tile.tappable:active{background:var(--surface-2)}

/* --- PR badge --- */
.pr{
  display:inline-flex;align-items:center;gap:4px;
  background:color-mix(in srgb,var(--yellow) 18%,transparent);color:var(--yellow);
  border-radius:6px;padding:3px 8px;font-size:12px;font-weight:500;
}
.pr .icn{font-size:13px}

/* --- toast --- */
#toast{
  position:fixed;left:50%;transform:translateX(-50%) translateY(6px);
  bottom:calc(96px + var(--sab));z-index:200;
  background:color-mix(in srgb,var(--surface-2) 88%,transparent);
  -webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px);
  /* 22px, not a pill radius: one line (about 41px high) still reads as a pill, and a toast that
     wraps to four lines is a rounded box instead of a circle over the page. */
  border-radius:var(--r-xl);padding:11px 20px;font-size:15px;font-weight:500;
  box-shadow:0 10px 30px -6px rgba(0,0,0,.5);
  opacity:0;pointer-events:none;transition:opacity var(--med),transform var(--med) var(--ease);
  /* As wide as its text up to 88vw. Without a width, left:50% left it half the screen to wrap
     in, so a long message stood as a narrow column. */
  width:max-content;max-width:88vw;text-align:center;
}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
/* With an action (Undo): the message, then the button at the end, and the toast takes taps. */
#toast.has-action{pointer-events:auto;display:flex;align-items:center;gap:6px;padding:4px 6px 4px 6px;text-align:start}
#toast.has-action .toast-msg{flex:1;min-width:0;padding:7px 0}
#toast .toast-act{flex:none;display:inline-flex;align-items:center;gap:5px;color:var(--acc);font-weight:700;padding:8px 12px;border-radius:14px;min-height:36px}
#toast .toast-act:active{background:color-mix(in srgb,var(--label) 10%,transparent)}
#toast .toast-act .icn{font-size:16px}
#toast .toast-x{flex:none;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;color:var(--label-2)}
#toast .toast-x:active{background:color-mix(in srgb,var(--label) 10%,transparent)}
#toast .toast-x .icn{font-size:15px}
body.no-tabbar #toast{bottom:calc(24px + var(--sab))}
body.no-tabbar.resting #toast{bottom:calc(100px + var(--sab))}

/* ---------------------------------------------------------------- misc --- */
.empty{text-align:center;color:var(--label-2);padding:44px 20px;font-size:15px;line-height:1.45}
.empty .ico{font-size:34px;color:var(--label-2);margin-bottom:12px;display:flex;justify-content:center}
.steps-list{
  padding-inline-start:18px;color:var(--label-2);font-size:15px;line-height:1.5;
  display:flex;flex-direction:column;gap:9px;
}
.steps-list::marker{color:var(--label-3)}

/* --- month calendar --- */
.cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:5px;margin-top:10px}
.cal-h{text-align:center;font-size:11px;color:var(--label-3);font-weight:500;text-transform:uppercase;padding:2px 0}
.cal-d{
  aspect-ratio:1;border-radius:10px;background:var(--surface);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  font-weight:400;font-size:15px;color:var(--label);transition:background var(--fast);
}
.cal-d:active{background:var(--surface-2)}
.cal-d.has{background:var(--acc-soft);color:var(--acc)}
.cal-d.today{box-shadow:inset 0 0 0 1.8px var(--acc)}
.cal-d i{width:5px;height:5px;border-radius:50%;background:transparent}
.cal-d i.done{background:var(--acc)}
.cal-d i.plan{background:var(--label-3)}
.cal-d i.ovr{background:var(--orange)}
.cal-d i.noted{background:var(--indigo)}
.cal-legend{display:flex;gap:14px;justify-content:center;margin-top:12px;font-size:12px;color:var(--label-3)}
.cal-legend i{display:inline-block;width:6px;height:6px;border-radius:50%;margin-inline-end:5px}

/* --- accent swatches --- */
.swatches{display:flex;gap:12px;flex-wrap:wrap}
.swatch{width:32px;height:32px;border-radius:50%;flex:none;position:relative;transition:transform var(--fast)}
.swatch:active{transform:scale(.9)}
/* The ring takes no taps: over the own colour it lay on the colour input, so the picker never opened. */
.swatch.on::after{content:'';position:absolute;inset:-4px;border-radius:50%;box-shadow:0 0 0 2px var(--label);pointer-events:none}
/* The user's own colour (Settings → Accent color): a rainbow ring until there is one, then filled
   with it. The native colour input lies over it, invisible, so the tap lands on the input itself. */
.swatch-own{display:inline-flex;align-items:center;justify-content:center;font-size:15px;--icon-stroke:2.4}
.swatch-own.unset{
  background:radial-gradient(circle,var(--surface) 0 52%,transparent 54%),
    conic-gradient(#ff453a,#ff9f0a,#ffd60a,#30d158,#40c8e0,#0a84ff,#bf5af2,#ff375f,#ff453a);
  color:var(--label-2);
}
.swatch-input{position:absolute;inset:0;width:100%;height:100%;opacity:0;border:0;padding:0;margin:0;border-radius:50%;cursor:pointer;-webkit-appearance:none;appearance:none}
.swatch-note{margin-top:-4px}

/* --- supersets --- */
.ss-label{
  font-size:12px;font-weight:600;letter-spacing:-.004em;text-transform:none;
  color:var(--acc);margin-block:8px 5px;margin-inline-start:4px;display:flex;align-items:center;gap:5px;
}
.ss-label .icn{font-size:13px}
/* The routine editor's label opens the superset's name and rest (#292). */
.ss-label-btn{background:none;border:0;padding:4px 6px;margin-inline-start:-2px;border-radius:8px;cursor:pointer;font:inherit;font-size:12px;font-weight:600;min-height:28px;max-width:100%}
.ss-label-btn:active{background:var(--surface-2)}
.ss-label-btn .ss-label-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ss-label-btn .dim{font-weight:500;white-space:nowrap}
/* A1, A2…: a member's place in the round (#293). */
.ss-pos{flex:none;min-width:26px;height:22px;padding:0 5px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;font-size:11.5px;font-weight:700;color:var(--acc);background:var(--acc-soft, var(--surface-2))}
.item.in-ss{box-shadow:inset 3px 0 0 var(--acc)}
/* A past workout's detail: each exercise opens its history, and a superset's exercises share the
   routine editor's label and bar. */
.wd-ex{margin-bottom:12px;cursor:pointer;border-radius:10px}
.wd-ex>.grow{flex:1;min-width:0}
.wd-ex:active{background:var(--surface-2)}
.wd-ss{box-shadow:inset 3px 0 0 var(--acc);padding-inline-start:11px;margin-bottom:12px}
.wd-ss .ss-label{margin:0 0 8px}
.wd-ss .wd-ex:last-child{margin-bottom:0}
.ss-card{
  border-radius:var(--r-lg);padding:14px;
  background:var(--surface);box-shadow:inset 0 0 0 1.5px var(--acc-line);
}
.workout-swipe-surface{touch-action:pan-y}
.workout-swipe-stack{position:relative;isolation:isolate;overflow:hidden}
.workout-swipe-stack>.workout-swipe-card,.workout-swipe-stack>.workout-swipe-preview{min-width:0}
.workout-swipe-card{position:relative;z-index:2;background:var(--bg)}
.workout-swipe-preview{position:absolute;z-index:1;top:0;inset-inline-start:0;width:100%;pointer-events:none;background:var(--bg)}
/* A superset nests a card inside the page's own gutter, so its inset is paid twice. On a
   narrow phone that left the weight and reps steppers too tight to show a three-digit
   number — reported as "the reps textbox becomes too small when superset on mobile". The
   card only gives up its own padding; the rows inside keep their spacing.
   The give-back runs to 404 px rather than 400, because it may not end before the full insets
   fit: at 401 px they snapped back to 16 + 14 + 14 per side and took 48 px off the row while the
   screen had gained one, so a plain row with an effort column in a superset in the list view —
   285 px of cells in a 281 px card — was 4, 3, 2 and 1 px wider than its card's content box at
   401–404 px and its done tick sat in the card's side padding. 405 px is the width where those
   285 px and the three full insets first add up to the screen, so that is where it ends. */
@media (max-width:404px){
  .ss-card{padding:10px 8px}
  /* The sets card nested in a superset card or a list unit pays a second inset, and in a superset
     in the list view a third; with an L/R row's set number, badge, two steppers, effort and tick
     it is the line that decides whether the numbers fit. 8 px each, and the superset card inside
     a unit keeps 4 (none below 360), so every set row has the same 256 px at 320 as in a plain
     card, and 296–304 at 368. */
  .wl-unit .card,.ss-card .card{padding-inline:8px}
  .wl-unit .ss-card{padding-inline:4px}
}
@media (max-width:359px){
  .wl-unit .ss-card{padding-inline:0}
}
.ss-hd{
  font-size:12px;font-weight:600;color:var(--acc);margin-bottom:12px;
  text-align:center;display:flex;align-items:center;justify-content:center;gap:5px;
}
.ss-ex{position:relative}
.ss-amp{text-align:center;font-size:15px;font-weight:600;color:var(--acc);margin:14px 0 8px}

/* --- workout list view: every unit stacked, the current one outlined --- */
/* minmax(0, …): a grid's default `auto` track is at least as wide as its widest content, so a set
   row whose cells have floors (see .setrow .stp) would widen the whole unit past the screen and
   push the ticks off it. Capped, a row that does not fit overflows its own card instead. */
.workout-list{display:grid;grid-template-columns:minmax(0,1fr);gap:14px}
.wl-unit{
  border-radius:var(--r-lg);padding:14px 14px 4px;
  background:var(--surface-2);
}
/* scrolled to when the list opens (#224): clear the sticky header above it. Workout.jsx
   measures the header (--whdr-h on .workout-list, safe area included) because it grows when
   the session name wraps; the fallback is the height of a one-line header. */
.wl-unit{scroll-margin-top:calc(var(--whdr-h, var(--sat) + var(--conn) + 80px) + 16px)}
.wl-unit.cur{box-shadow:inset 0 0 0 1.5px var(--acc-line)}
.wl-hd{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}
.wl-summary{display:grid;gap:8px;padding-bottom:10px}
/* the folded unit is a button (a tap opens it again): full width, text where it always sat */
button.wl-summary{width:100%;padding:0 0 10px;text-align:start;border-radius:var(--r-sm)}

/* --- the chip per exercise at the top of a running workout (#323) --- */
/* One numbered chip per unit, filled as far as it is done: an outline waiting, half full started,
   full when every work set is ticked, so the state reads without the colour. The current one has
   the accent ring. Every chip is the same height whatever its state, and the row scrolls sideways
   (.chips), so nothing here can move the page while you type. 28px chips with the strip's own
   padding keep the row at about the height of the line it replaces. */
.chips.wchips{gap:6px;padding:4px 5px 5px;margin-bottom:2px}   /* room for the current chip's ring: the strip clips */
.wchip{
  flex:none;position:relative;display:inline-flex;align-items:center;justify-content:center;gap:2px;
  min-width:28px;height:28px;padding:0 8px;border-radius:99px;
  font-size:13px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--label-2);
  background:var(--surface);box-shadow:inset 0 0 0 1.5px var(--sep);
}
.wchip .icn{width:12px;height:12px}
.wchip.partial{background:linear-gradient(to top,color-mix(in srgb,var(--orange) 45%,var(--surface)) 50%,var(--surface) 50%);box-shadow:inset 0 0 0 1.5px var(--orange);color:var(--label)}
.wchip.done{background:color-mix(in srgb,var(--green) 70%,var(--surface));box-shadow:none;color:var(--label)}
.wchip.cur{box-shadow:0 0 0 2px var(--bg),0 0 0 3.5px var(--acc);color:var(--label)}
.wchip.partial.cur{box-shadow:inset 0 0 0 1.5px var(--orange),0 0 0 2px var(--bg),0 0 0 3.5px var(--acc)}
/* part of the give-back above — one edge for all three insets of a row in the list view */
@media (max-width:404px){
  .wl-unit{padding:10px 8px 2px}
}

/* --- home: today's session row --- */
.today-row{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  margin-top:12px;padding:12px 14px;border-radius:var(--r);background:var(--surface-2);
  transition:background var(--fast);width:100%;text-align: start;cursor:pointer;
}
.today-row:active{background:var(--surface-3)}
.today-row .lbl2{font-size:11px;font-weight:500;letter-spacing:.03em;text-transform:uppercase;color:var(--label-3)}
.today-row .ttl{
  font-weight:500;font-size:17px;line-height:1.25;text-transform:capitalize;
  letter-spacing:-.012em;margin-top:2px;
  /* two lines before the ellipsis: "Leg Day — in progress" grows past one line in hu/tr/th/pt-BR */
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;
}

/* Only where a pointer really hovers — on a phone :hover sticks to the row after the tap.
   Lighter than :active, which goes all the way to --surface-3, so the two stay distinct. */
@media (hover:hover){
  .today-row:hover{background:color-mix(in srgb,var(--surface-2),var(--surface-3) 60%)}
  :root[data-theme="light"] .today-row:hover{background:color-mix(in srgb,var(--surface-2),var(--surface-3) 85%)}
}

/* --------------------------------------------------- desktop (≥1000px) --- */
.cols{display:block}
@media (min-width:1000px){
  #app{max-width:1080px;padding-top:calc(var(--conn) + 32px);padding-bottom:calc(128px + var(--sab))}
  .conn-row{max-width:640px}   /* the width of the .narrow pages below it */
  body.resting #app{padding-bottom:calc(220px + var(--sab))}
  body.no-tabbar #app{padding-bottom:calc(40px + var(--sab))}
  body.no-tabbar.resting #app{padding-bottom:calc(130px + var(--sab))}
  .workout-end-spacer{height:40px}
  .cols{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}
  .cols>*{min-width:0}
  /* Two columns on the full-width pages (Library, History, Stats, Plan, Muscles). A .narrow page
     is a single 640px column and keeps its lists in one too. The #app in that selector is what
     lets it win: `.narrow .list` alone lost to `#app .list` on specificity, so Home, Workout,
     Admin and the rest showed their lists two-up on a computer. Sheets live in #modal-root,
     outside #app, and are one column at every width. */
  #app .list{display:grid;grid-template-columns:1fr 1fr;gap:8px}
  #modal-root .list,#app .narrow .list{display:flex;flex-direction:column}
  .narrow{max-width:640px;margin:0 auto}
  .tiles{grid-template-columns:repeat(4,1fr)}
  .cols .tiles,.center .tiles{grid-template-columns:1fr 1fr}   /* a 300px dialog has no room for four */
  #tabbar{
    left:50%;right:auto;bottom:16px;transform:translateX(-50%);width:520px;
    border:var(--hair) solid var(--sep-op);border-radius:var(--r-xl);
    padding:7px 10px 9px;box-shadow:0 12px 40px -10px rgba(0,0,0,.5);
  }
  #tabbar button.start{margin-top:-26px}
  .sheet{left:50%;right:auto;margin-left:-320px;width:640px;border-radius:var(--r-xl) var(--r-xl) 0 0}
  .center{width:340px}
  #timer{left:50%;right:auto;margin-left:-260px;width:520px;bottom:112px}
  body.no-tabbar #timer{left:50%;right:auto;bottom:16px;border-radius:var(--r-xl);padding-bottom:10px;box-shadow:0 12px 40px -10px rgba(0,0,0,.5)}
  .exmedia img,.exmedia video{height:380px}
  .hm-wrap{display:flex;flex-direction:column;align-items:center}
}

/* ------------------------------------------------------- gym check-in -- */
/* Mobile-only feature (views/CheckIn.jsx): saved membership codes shown as QR, swiped
   through horizontally. The rail is a scroll-snap track — one card per viewport, no JS
   carousel — with a trailing "+" card to add another. */
.ci-rail{
  display:flex;gap:14px;overflow-x:auto;scroll-snap-type:x mandatory;
  -webkit-overflow-scrolling:touch;scrollbar-width:none;
  padding:4px 0 8px;margin:0 -16px;padding-left:16px;padding-right:16px;
}
.ci-rail::-webkit-scrollbar{display:none}
.ci-card{
  scroll-snap-align:center;flex:0 0 100%;box-sizing:border-box;
  background:var(--surface);border-radius:var(--r-xl);padding:22px 18px;
  display:flex;flex-direction:column;align-items:center;gap:14px;
  transition:transform var(--fast),box-shadow var(--fast),opacity var(--fast);
}
/* header row: edit — title — trash. The two icon buttons are equal fixed widths so the title,
   flexed between them, stays optically centered on the card no matter how long it is. */
.ci-card-hd{display:flex;align-items:center;gap:8px;width:100%}
.ci-card-btn{width:30px;height:30px;font-size:14px;flex:none}
.ci-card .ci-label{flex:1;min-width:0;font-size:19px;font-weight:600;letter-spacing:-.014em;text-align:center;overflow-wrap:anywhere}
.ci-card .ci-value{font-size:14px;color:var(--label-2);word-break:break-all;text-align:center;font-variant-numeric:tabular-nums}
/* reorder row under a card (only shown with >1 card): ◀ position ▶ to nudge the card one slot
   along the rail. A plain, reliable control — no drag gesture to fight the horizontal scroll. */
.ci-reorder{display:flex;align-items:center;justify-content:center;gap:14px;margin-top:2px}
.ci-reorder .ci-pos{font-size:13px;color:var(--label-2);font-variant-numeric:tabular-nums;min-width:44px;text-align:center}
.ci-reorder .iconbtn:disabled{opacity:.3;pointer-events:none}
/* The QR itself: lean-qr paints 1 module per pixel, so scale it up with the browser's
   nearest-neighbour so every module stays a crisp square instead of a blurred blob. White
   quiet-zone padding is baked in by the renderer; the plate underneath just frames it. */
.ci-qr-plate{background:#fff;border-radius:var(--r-lg);padding:16px}
.qr-canvas{display:block;image-rendering:pixelated;image-rendering:crisp-edges;transition:opacity var(--fast)}
/* the trailing add card — a big + on a dashed outline, same footprint as a real card */
.ci-add{
  scroll-snap-align:center;flex:0 0 100%;box-sizing:border-box;
  min-height:280px;border-radius:var(--r-xl);
  border:2px dashed var(--surface-3);background:none;color:var(--label-2);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;
}
.ci-add:active{background:var(--surface);transform:scale(.99)}
.ci-add .icn{font-size:40px;--icon-stroke:1.8}
.ci-add .ci-add-t{font-size:16px;font-weight:500}
/* dots under the rail — which card you're on. Purely a hint; the rail itself is the control. */
.ci-dots{display:flex;justify-content:center;gap:7px;margin-top:4px}
.ci-dot{width:7px;height:7px;border-radius:50%;background:var(--surface-3);transition:background var(--fast),transform var(--fast)}
.ci-dot.on{background:var(--acc);transform:scale(1.15)}
/* browser camera sheet (components/CameraScan.jsx): the video fills a portrait plate, a dashed
   square in the middle says "put the code here" — decoding reads the whole frame regardless. */
.cam-wrap{position:relative;width:100%;aspect-ratio:3/4;max-height:58vh;border-radius:var(--r-lg);overflow:hidden;background:#000}
.cam-wrap video{width:100%;height:100%;object-fit:cover;display:block}
.cam-frame{position:absolute;inset:0;margin:auto;width:62%;aspect-ratio:1;border:2px dashed rgba(255,255,255,.75);border-radius:14px;pointer-events:none}
/* ---------------------------------------------------------------- Plan -- */
/* Schedule | Routines (views/Plan.jsx). A settings-like page, one column at every width. */
.plan-views{margin-bottom:18px}
.plan-views button{min-height:34px;font-size:15px}
.plan .plan-mode .lrow-t{white-space:nowrap}
/* The control takes the rest of the row when it fits, and a full line of its own when it does
   not, rather than squeezing "Fixed Week" onto two lines. */
.plan-mode .seg-inline{flex:1 1 200px;min-width:0}
.plan-mode .seg-inline button{white-space:nowrap}
.plan-mode-locked{margin-inline-start:auto;text-align:end}
.plan-sec-h{margin:22px 0 8px;gap:8px;flex-wrap:wrap}
/* Long words (German "Hinzufügen") push the loop's buttons off a 320 px screen; they drop
   to their own line instead and stay on the trailing edge. */
.plan-sec-h>.plan-sec-acts{margin-inline-start:auto}
.plan-sec-h h4.sec{margin:0}
.plan-routines-bar{margin-top:0}
.plan-textbtn{color:var(--acc);font-size:16px;padding:6px 4px;position:relative}
.plan-textbtn::before{content:'';position:absolute;inset:-6px}
.plan .sect-f{margin:0 0 4px}
.plan .list+.sect-f{padding-top:8px}
/* a weekday: short day name, the routine's glyph (a moon on a rest day), what is on it */
.plan-day-n{min-width:30px;flex:none;white-space:nowrap;font-size:15px;font-weight:600;color:var(--label-2)}
.plan-day-n.today{color:var(--acc)}
/* Arabic names the whole day: one column wide enough for the longest keeps the glyphs aligned */
[dir="rtl"] .plan-day-n{min-width:88px}
.item .lrow-i.rest{background:var(--surface-3);color:var(--label-2)}
/* the loop: a numbered position, lit when up next, a tick once done this round */
.plan-order{
  width:24px;height:24px;border-radius:50%;flex:none;display:grid;place-items:center;
  font-size:13px;font-weight:600;color:var(--label-2);background:var(--surface-3);--icon-stroke:2.6;
}
.plan-order.next{background:var(--acc);color:var(--on-acc)}
.rotation-row.is-done .tt{color:var(--label-2)}
/* Edit mode, as on an iPhone: a red minus at the start, a handle at the end */
.plan-minus{
  width:24px;height:24px;border-radius:50%;flex:none;display:grid;place-items:center;
  background:var(--red);color:#fff;font-size:14px;--icon-stroke:2.6;position:relative;
}
.plan-minus::before{content:'';position:absolute;inset:-10px}
.plan-handle{
  flex:none;align-self:stretch;display:flex;align-items:center;padding:0 6px;margin:-10px -6px -10px 0;
  color:var(--label-3);font-size:18px;cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none;
}
.plan-handle:active{cursor:grabbing;color:var(--label-2)}
.plan [data-reorder-row]{transition:transform var(--fast) var(--ease)}
.plan-link{margin-top:24px}
.plan-empty{padding:28px 20px}
/* Stats: the card Plan links to clears the sticky banners when it is scrolled to */
#weekly-volume{scroll-margin-top:calc(var(--sat) + var(--conn) + 96px)}

/* ------------------------------------------------- duration wheel picker -- */
/* components/DurationWheel.jsx: two scroll-snap wheels under one highlight band. --dw-item is
   WHEEL_ITEM there. The band sits behind the rows; the rows fade and lean back toward the edges
   (painted from JS while scrolling), and the mask softens the top and bottom rows further. */
.dw{--dw-item:40px;position:relative;display:flex;justify-content:center;height:calc(var(--dw-item) * 5);
  margin:4px auto 16px;max-width:320px;user-select:none;-webkit-user-select:none;touch-action:pan-y}
.dw-band{position:absolute;inset-inline:6px;top:calc(var(--dw-item) * 2);height:var(--dw-item);border-radius:10px;
  background:color-mix(in srgb,var(--label) 10%,transparent);pointer-events:none}
.dw-col{position:relative;flex:1;min-width:0;outline:none;border-radius:10px}
.dw-col:focus-visible::after{content:'';position:absolute;inset-inline:8px;top:calc(var(--dw-item) * 2);height:var(--dw-item);
  border-radius:10px;box-shadow:0 0 0 2px var(--acc);pointer-events:none}
.dw-scroll{height:100%;overflow-y:scroll;overflow-x:hidden;scroll-snap-type:y mandatory;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;scrollbar-width:none;perspective:420px;
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 34%,#000 66%,transparent 100%);
  mask-image:linear-gradient(to bottom,transparent 0,#000 34%,#000 66%,transparent 100%)}
.dw-scroll::-webkit-scrollbar{display:none}
.dw-pad{height:calc(var(--dw-item) * 2)}
.dw-it{height:var(--dw-item);scroll-snap-align:center;display:flex;align-items:center;justify-content:flex-end;
  padding-inline-end:calc(50% + 2px);font-size:22px;font-variant-numeric:tabular-nums;color:var(--label-2);
  cursor:pointer;transform-origin:50% 50%;will-change:transform,opacity}
.dw-it.on{color:var(--label);font-weight:600}
.dw-it.out{color:var(--label-4)}
.dw-unit{position:absolute;top:calc(var(--dw-item) * 2);height:var(--dw-item);display:flex;align-items:center;
  inset-inline-start:calc(50% + 8px);font-size:17px;font-weight:600;color:var(--label);pointer-events:none}
.dw-read{text-align:center;font-size:34px;font-weight:700;letter-spacing:-.02em;font-variant-numeric:tabular-nums;margin:-4px 0 2px}
.dw-foot{text-align:center;margin:-6px 0 14px;line-height:1.4}
@media (hover:hover){.dw-it:hover{color:var(--label)}}

/* ------------------------------------------------------- Settings (v1.3.11) -- */
/* A sub-page's nav bar: the back button names where it goes, the title sits in the middle.
   The back column is at least as wide as its label ("Einstellungen" stays whole), and the two
   side columns share what is left equally, so the title is centred whenever it can be and moves
   over when the label needs the room. Only a label past half the screen is cut. */
.sp-nav{display:grid;grid-template-columns:minmax(max-content,1fr) minmax(0,max-content) minmax(0,1fr);align-items:center;column-gap:6px;
  min-height:44px;margin:4px -6px 10px}
.sp-back{display:inline-flex;align-items:center;gap:2px;color:var(--acc);font-size:17px;padding:6px;border-radius:10px;
  min-width:0;max-width:50vw;justify-self:start}
.sp-back span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sp-back .icn{font-size:22px;--icon-stroke:2.2;flex:none}
/* A long title ("Alertas do temporizador", "Megjelenés és Kezdőlap") takes a second line rather
   than lose its end to an ellipsis next to a whole back label. */
.sp-title{text-align:center;font-weight:600;font-size:17px;line-height:1.2;margin:0;overflow:hidden;overflow-wrap:anywhere;-webkit-hyphens:auto;hyphens:auto;
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;text-wrap:balance}
.sp-root-title{font-size:34px;line-height:1.06;font-weight:700;letter-spacing:-.028em;margin:2px 0 10px}
@media (max-width:420px){.sp-root-title{font-size:30px}}
.sp-search{margin-bottom:18px}
/* the account card on top of the root: a big round avatar, the name, one line under it */
.lrow.sp-acct{padding-block:12px;gap:14px}
.sp-avatar{width:56px;height:56px;border-radius:50%;flex:none;display:grid;place-items:center;font-size:30px;
  background:linear-gradient(160deg,var(--surface-3),var(--surface-2));color:var(--label-2)}
.sp-acct .lrow-t{font-size:19px;font-weight:600}
.sp-avatar.on{background:var(--acc-soft);color:var(--acc);font-size:24px;font-weight:700}
.sp-version{text-align:center;margin-top:4px;line-height:1.6}
/* The root rows' previews ("2:00 de descanso · Tarjetas", "Som · Vibração") are two or three facts, and a
   one-line ellipsis cut all but the first. They wrap instead, up to three lines, and never below
   their longest word (min-content, since overflow:hidden would make auto mean 0). On the narrowest phones there is no room beside
   the title at all, so the preview moves under it, like a subtitle. */
.lrow.sp-root-row .lrow-v{white-space:normal;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3;
  min-width:min-content;max-width:62%;line-height:1.25;text-wrap:balance}
@media (max-width:360px){
  .lrow.sp-root-row:has(.lrow-v){display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;column-gap:12px;row-gap:1px}
  .lrow.sp-root-row:has(.lrow-v) .lrow-i{grid-column:1;grid-row:1 / span 2}
  .lrow.sp-root-row:has(.lrow-v) .lrow-m{grid-column:2;grid-row:1}
  .lrow.sp-root-row .lrow-v{grid-column:2;grid-row:2;min-width:0;max-width:none;text-align:start;font-size:15px;-webkit-line-clamp:2;line-clamp:2}
  .lrow.sp-root-row:has(.lrow-v) .lrow-c{grid-column:3;grid-row:1 / span 2}
}
.lrow.dis .lrow-i,.lrow.dis .lrow-t,.lrow.dis .sw{opacity:.45}
/* a row a search result jumped to: a short accent wash that fades out */
.lrow.sp-flash,.btn.sp-flash{animation:spflash 1.8s ease}
@keyframes spflash{0%,45%{background:var(--acc-soft)}100%{background:transparent}}
.sp-empty{color:var(--label-2);text-align:center;padding:28px 12px;line-height:1.4}

/* ----------------------------------------------------------- RTL (Arabic) -- */
/* Directional icons flip: back chevrons point right, trailing chevrons point left. */
[dir="rtl"] [data-icon="chevronLeft"],
[dir="rtl"] [data-icon="chevronRight"]{transform:scaleX(-1)}
/* Segmented control: flex order reverses, so the sliding indicator moves the other way. */
[dir="rtl"] .seg-sel{transform:translateX(calc(-100% * var(--i)))}
/* Numbers and dates inside Arabic text still read left-to-right. */
[dir="rtl"] .num,[dir="rtl"] input[type="number"]{direction:ltr}
/* Superset edge stripe points at the inline start (the right, in RTL). */
[dir="rtl"] .item.in-ss,[dir="rtl"] .wd-ss{box-shadow:inset -3px 0 0 var(--acc)}
/* Slider: the thumb is offset from inline-start, which is the right edge in RTL, so the
   half-width centering shift points the other way. */
[dir="rtl"] .sld-knob{transform:translateX(50%)}
[dir="rtl"] .sld.dragging .sld-knob{transform:translateX(50%) scale(1.14)}
/* Switch: the knob rests at inline-start (the right, in RTL), so "on" travels the other
   way — the physical +20px would push it out of the track. */
[dir="rtl"] .sw.on .knob{transform:translateX(-20px)}
[dir="rtl"] .sw.on:active .knob{transform:translateX(-16px)}
/* Chronological grids keep their left-to-right order: heatmap weeks and calendar
   weekdays would otherwise mirror. */
[dir="rtl"] .hm-wrap,[dir="rtl"] .cal-grid{direction:ltr}
/* Tight tracking is tuned for Latin; Arabic is a connected script and gets none. */
[dir="rtl"] *{letter-spacing:0}
.exfix-link{display:inline-flex;align-items:center;gap:5px;margin:14px 0 4px;color:var(--label-3);text-decoration:none}
.list .similar-label{margin:16px 2px 6px;grid-column:1/-1}

/* The edge swipe back (lib/edge-swipe-back.js): the page follows the finger, the one it goes
   back to slides in underneath with a shade over it. The sliding page's own ground reaches the
   screen's edges (--eb-*: how far #app's padding and margins leave it short of them). */
.edge-back-shade{position:fixed;inset:0;background:#000;opacity:.35;pointer-events:none;z-index:1}
.edge-back-page{position:relative;z-index:2;will-change:transform}
.edge-back-page::before{
  content:'';position:absolute;z-index:-1;pointer-events:none;background:var(--bg);
  top:calc(-1 * var(--eb-t,0px));bottom:calc(-1 * var(--eb-b,0px));
  left:calc(-1 * var(--eb-l,0px));right:calc(-1 * var(--eb-r,0px));
  box-shadow:0 0 28px rgba(0,0,0,.28);
}


  #boot{position:fixed;inset:0;z-index:2147483000;display:grid;place-items:center;background:#0c0e12;transition:opacity .22s ease-out}
  #boot.out{opacity:0;pointer-events:none}
  #boot svg{width:112px;height:auto;overflow:visible}
  #boot rect,#boot ellipse{transform-box:fill-box;transform-origin:center}
  #boot .bar{animation:boot-bar .32s cubic-bezier(.3,.7,.4,1) both}
  #boot .pl{animation:boot-plate .28s cubic-bezier(.3,.7,.4,1.3) both}
  #boot .pr{animation:boot-plate-r .28s cubic-bezier(.3,.7,.4,1.3) both}
  #boot .in{animation-delay:.22s}
  #boot .outer{animation-delay:.36s}
  #boot .db{animation:boot-rep 1.25s ease-in-out .75s infinite}
  #boot ellipse{fill:#a3e635;opacity:.12;animation:boot-shadow 1.25s ease-in-out .75s infinite}
  @keyframes boot-bar{from{transform:scaleX(0)}}
  @keyframes boot-plate{from{transform:translateX(-40px);opacity:0}}
  @keyframes boot-plate-r{from{transform:translateX(40px);opacity:0}}
  @keyframes boot-rep{50%{transform:translateY(-26px)}}
  @keyframes boot-shadow{50%{transform:scaleX(.72);opacity:.05}}
  @media (prefers-reduced-motion:reduce){
    #boot .bar,#boot .pl,#boot .pr,#boot ellipse{animation:none}
    #boot .db{animation:boot-breathe 1.6s ease-in-out infinite}
    @keyframes boot-breathe{50%{opacity:.55}}
  }

html,body{overflow:hidden!important;scrollbar-width:none}*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important}