/**
 * JPJR Fitness Pro — design tokens and base app shell.
 *
 * Palette, type scale, and button geometry are taken from the live target site
 * (the Divi "Personal Trainer Home" layout) so the app is visually continuous
 * with the marketing pages around it.
 *
 * Everything is scoped under `.fitpro-app`. Divi applies `!important` rules to
 * headings and `.et_pb_button` globally; without scoping plus a local reset,
 * those bleed straight into the application UI.
 */

/*
 * Light and dark, in one declaration each.
 *
 * `light-dark()` picks between two values from the element's computed
 * `color-scheme`, so the theme switch is a single property on the app container
 * and every token stays defined in exactly one place. The alternative — a whole
 * second palette under `[data-fp-theme="dark"]` and a third copy inside
 * `prefers-color-scheme` for the "match my device" case — is three lists that
 * have to be kept in step, and the one that drifts is the one nobody is looking
 * at.
 *
 * **Every token is declared twice: plain first, then `light-dark()`.** A browser
 * without `light-dark()` drops the second declaration and keeps the first, so it
 * gets a working light theme. Without that fallback the token would be
 * *undefined*, and an undefined custom property does not fall back to anything —
 * it kills the whole declaration that used it, silently. That is bug 47, and it
 * would take out the entire interface rather than one card.
 *
 * ## The accent is set from PHP, not here
 *
 * `--fp-accent`, `--fp-accent-rgb` and `--fp-accent-contrast` are written inline
 * on the app container by `Support\Appearance`, so the trainer's brand colour is
 * correct in the very first painted frame — including on the boot screen, which
 * renders before any JavaScript exists. The values below are the fallback for a
 * context that renders the app markup without that attribute.
 *
 * Everything else is derived with `color-mix()` rather than stored, so changing
 * the accent changes all of it and nothing can go stale.
 */
/*
 * The accent, and everything derived from it, on the app container.
 *
 * **Not on `:root`, and that placement is the whole trick.** A custom property
 * that refers to another is resolved *where it is declared*, not where it is
 * used — so `--fp-accent-dark: color-mix(..., var(--fp-accent), ...)` written on
 * `:root` computes against `:root`'s accent and then inherits downward as a
 * fixed colour. The inline `--fp-accent` that PHP writes on `.fitpro-app` sits
 * below that and never reaches it.
 *
 * The symptom when this was on `:root`: picking teal turned the tabs, the tints
 * and the hero gradient teal while every primary button stayed orange — a
 * half-rebranded interface, nothing in the console, and both declarations
 * reading perfectly correctly on their own.
 *
 * `--fp-accent` itself is repeated here as the fallback for a context that
 * renders this markup without the inline attribute.
 */
.fitpro-app,
.fitpro-gate {
	/* Overridden inline per install; see Support\Appearance. */
	--fp-accent: #ff4c00;
	--fp-accent-rgb: 255, 76, 0;

	/* Readable text on top of the accent — computed from its luminance. */
	--fp-accent-contrast: #ffffff;

	/*
	 * The accent, dark enough to carry that text.
	 *
	 * `--fp-accent-contrast` picks *which* letter goes on the accent and is a
	 * 3.0 floor, which is WCAG AA for large text — and nothing filled with the
	 * accent contains large text. The primary button is 13px at weight 800, so
	 * the threshold that applies is 4.5, and white on the brand orange scores
	 * 3.34. Raising that floor instead would have put black labels on orange,
	 * teal and green, repainting the brand to fix a number.
	 *
	 * So the background moves rather than the letter. `Appearance::fill()`
	 * darkens the accent by the smallest amount that clears 4.5 — `#ff4c00`
	 * becomes `#d64000`, the same orange a shade deeper — and hands it over
	 * inline. Five of the eight presets are unchanged by it.
	 *
	 * Declared here with the accent as its value so it is never *undefined*: an
	 * undeclared custom property does not degrade, it destroys the whole
	 * declaration that used it (bug 47), which here would mean a colourless
	 * button on any page the inline style did not reach.
	 */
	--fp-accent-fill: var(--fp-accent);

	/*
	 * Hover, and the faint wash behind selected things. Derived, never stored.
	 *
	 * Hover darkens the **fill**, not the accent, or a primary button would get
	 * lighter on hover wherever the two differ — which is backwards, and was the
	 * case while this read `--fp-accent`.
	 */
	--fp-accent-dark: color-mix(in srgb, var(--fp-accent-fill) 86%, #000);
	--fp-accent-soft: color-mix(in srgb, var(--fp-accent) 8%, transparent);

	/*
	 * Retired names, kept pointing at the new ones.
	 *
	 * `--fp-orange` was named for the colour rather than the role, which was
	 * accurate right up until the colour became the trainer's to choose — at
	 * which point a rule reading `var(--fp-orange)` and painting something blue
	 * is a small lie repeated in every stylesheet. Nothing uses these now; they
	 * stay because an undefined token fails silently and catastrophically
	 * (bug 47), and one missed reference should not be able to do that.
	 */
	--fp-orange: var(--fp-accent);
	--fp-orange-dark: var(--fp-accent-dark);
	--fp-orange-soft: var(--fp-accent-soft);
}

/*
 * Everything that does not depend on the accent stays on `:root`, so it is also
 * in force outside the app container — `body.fitpro-fullscreen` paints the
 * page behind the shell with `--fp-surface` and would otherwise have nothing.
 */
:root {
	/*
	 * Navy is chrome, not a surface: the top bar and the sidebar are dark in both
	 * themes by design, so this barely moves. It deepens a little in dark mode so
	 * the chrome still separates from the page behind it.
	 */
	--fp-navy: #262d3f;
	--fp-navy: light-dark(#262d3f, #171b24);

	/*
	 * The identity disc — a client's initials, a workout's exercise count.
	 *
	 * It was `--fp-navy` on `--fp-inverse`: a dark circle with white letters,
	 * which is crisp on a white card and **invisible on a dark one**. `--fp-navy`
	 * itself goes *darker* in dark mode, correctly, because it is a backdrop
	 * colour — so the disc ended up at 1.21:1 against the card behind it,
	 * measured. Present, correct, and impossible to see.
	 *
	 * So it inverts: a light disc with dark letters once the page is dark. Not
	 * pure white, which reads as a hole punched in the card at 42px across — a
	 * step below it, which still separates from the surface by better than 9:1
	 * while the letters on it stay above 12:1.
	 *
	 * Its own token pair rather than flipping `--fp-navy`, which is also the
	 * hero gradient's second stop and several backdrops; inverting it globally
	 * would turn those inside out to fix a circle.
	 */
	--fp-disc: #262d3f;
	--fp-disc: light-dark(#262d3f, #d7dbe2);
	--fp-disc-ink: #ffffff;
	--fp-disc-ink: light-dark(#ffffff, #171b24);
	--fp-navy-soft: #38383f;
	--fp-navy-soft: light-dark(#38383f, #2a2e38);

	/* Text */
	--fp-heading: #333333;
	--fp-heading: light-dark(#333333, #e9ebee);
	--fp-body: #666666;
	--fp-body: light-dark(#666666, #b2b8c1);
	--fp-muted: #8b9199;
	--fp-muted: light-dark(#8b9199, #868d97);
	--fp-inverse: #ffffff;

	/* Surfaces */
	--fp-surface: #fbfcfd;
	--fp-surface: light-dark(#fbfcfd, #12141a);
	--fp-card: #ffffff;
	--fp-card: light-dark(#ffffff, #1b1e26);
	--fp-line: #e6e9ee;
	--fp-line: light-dark(#e6e9ee, #2b303a);
	--fp-line-strong: #d3d8e0;
	--fp-line-strong: light-dark(#d3d8e0, #3a404b);

	/*
	 * State. The dark values are lighter, not the same hue dimmed: a colour that
	 * reads as "green" against white is close to black against #1b1e26, and the
	 * whole point of these is that they are recognisable before they are read.
	 */
	--fp-success: #1a7f37;
	--fp-success: light-dark(#1a7f37, #4ade80);
	--fp-warning: #bf8700;
	--fp-warning: light-dark(#bf8700, #fbbf24);
	--fp-danger: #d1242f;
	--fp-danger: light-dark(#d1242f, #f87171);

	/*
	 * Wash-and-ink pairs for the callout blocks — errors, warnings, a passing
	 * connection test, the score bands. These were fifteen hard-coded pastels
	 * scattered through the file, each of which turns into a glowing white slab
	 * on a dark card. Named here so dark mode is one edit rather than fifteen.
	 */
	--fp-danger-wash: #fff5f5;
	--fp-danger-wash: light-dark(#fff5f5, #2a1618);
	--fp-danger-ink: #7d1a20;
	--fp-danger-ink: light-dark(#7d1a20, #fca5a5);
	--fp-warning-wash: #fffaf0;
	--fp-warning-wash: light-dark(#fffaf0, #2a2312);
	--fp-warning-ink: #7a5600;
	--fp-warning-ink: light-dark(#7a5600, #fcd34d);
	--fp-success-wash: #f2fbf4;
	--fp-success-wash: light-dark(#f2fbf4, #13251a);
	--fp-success-ink: #14532d;
	--fp-success-ink: light-dark(#14532d, #86efac);

	/* The loading shimmer, which is two greys and therefore two very different greys. */
	--fp-skeleton: #eef1f5;
	--fp-skeleton: light-dark(#eef1f5, #232833);
	--fp-skeleton-shine: #f7f9fb;
	--fp-skeleton-shine: light-dark(#f7f9fb, #2c323e);

	/* Behind a sheet or a lightbox. Heavier in dark mode, or the dimming barely registers. */
	--fp-scrim: rgba(38, 45, 63, 0.55);
	--fp-scrim: light-dark(rgba(38, 45, 63, 0.55), rgba(0, 0, 0, 0.72));
	--fp-scrim-strong: rgba(38, 45, 63, 0.92);
	--fp-scrim-strong: light-dark(rgba(38, 45, 63, 0.92), rgba(0, 0, 0, 0.93));

	/* Type */
	--fp-font-heading: "Oswald", "Helvetica Neue", Helvetica, Arial, sans-serif;
	--fp-font-body: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;

	/* Space */
	--fp-space-1: 4px;
	--fp-space-2: 8px;
	--fp-space-3: 12px;
	--fp-space-4: 16px;
	--fp-space-5: 24px;
	--fp-space-6: 32px;
	--fp-space-7: 48px;
	--fp-space-8: 64px;

	/* Geometry */
	--fp-radius: 8px;
	--fp-radius-lg: 16px;
	--fp-radius-pill: 100px;
	--fp-shadow: 0 1px 2px rgba(38, 45, 63, 0.06), 0 4px 16px rgba(38, 45, 63, 0.06);
	--fp-shadow-lg: 0 8px 32px rgba(38, 45, 63, 0.12);

	/*
	 * Minimum tap target. Non-negotiable on the in-workout set logger, which is
	 * operated one-handed, mid-set, with sweaty hands.
	 */
	--fp-tap-min: 44px;

	/*
	 * Destructive. Named rather than inlined because it appears on a button, a
	 * border and a wash, and three hand-picked reds that nearly match read as a
	 * rendering fault rather than as a colour.
	 */
	--fp-danger-soft: rgba(198, 40, 40, 0.07);
	--fp-danger-soft: light-dark(rgba(198, 40, 40, 0.07), rgba(248, 113, 113, 0.12));

	/* Chrome heights, used to offset scroll containers. */
	--fp-topbar-h: 56px;
	--fp-tabbar-h: 60px;

	/*
	 * How far down the viewport our own chrome starts.
	 *
	 * Zero normally; the WordPress admin bar's height when one is on screen and
	 * fixed. Every sticky offset in the app is expressed against this rather
	 * than against a literal, because there are two of them — the top bar and,
	 * on desktop, the sidebar below it — and they have to agree. They did not:
	 * the sidebar stuck at 56px from the top of the viewport while the bar
	 * itself stuck at 88px, so the sidebar's first 32px sat *inside* the header
	 * band and, having the higher z-index, painted over it on every scroll.
	 */
	--fp-chrome-top: 0px;

	/*
	 * The four macros.
	 *
	 * Yellow, green, red and blue as asked for, each darkened until it passes
	 * against white -- a literal yellow is around 1.1:1 on this background, which
	 * is a glyph you can see only if you already know it is there. These are the
	 * same four hues at a legible luminance.
	 *
	 * Named for the macro, not the colour. `--fp-macro-fat` survives somebody
	 * deciding fat should be orange; `--fp-red` does not, and every call site
	 * would then read the wrong word.
	 *
	 * Colour is never the only signal: the four glyphs are four different shapes,
	 * so the row still separates for a reader who cannot tell red from green.
	 */
	--fp-macro-calories: #1c6fd4;
	--fp-macro-calories: light-dark(#1c6fd4, #60a5fa);
	--fp-macro-protein: #b07a00;
	--fp-macro-protein: light-dark(#b07a00, #fbbf24);
	--fp-macro-carbs: #1a7f37;
	--fp-macro-carbs: light-dark(#1a7f37, #4ade80);
	--fp-macro-fat: #d1242f;
	--fp-macro-fat: light-dark(#d1242f, #f87171);

	/*
	 * A recipe's tags, one hue per kind: when you eat it, where it is from, who
	 * can eat it. The ink is the colour; the pill behind it is the same colour
	 * at a low mix, so each pair stays legible in both themes without a second
	 * set of tokens. Not the macro hues, which already mean protein and carbs on
	 * the same card.
	 */
	--fp-tag-meal: #4f46e5;
	--fp-tag-meal: light-dark(#4f46e5, #a5b4fc);
	--fp-tag-cuisine: #52606d;
	--fp-tag-cuisine: light-dark(#52606d, #cbd2d9);
	--fp-tag-diet: #047857;
	--fp-tag-diet: light-dark(#047857, #6ee7b7);

	/*
	 * What an achievement is worth: bronze, silver, gold.
	 *
	 * Ink, not the disc. The strip's discs are solid fills carrying a white
	 * glyph and need a colour dark enough for that; a word needs one that reads
	 * on a wash of itself, which in dark mode has to go the other way. Two roles
	 * of one idea, so they are deliberately not one value -- forcing a single
	 * token would give a dark-mode disc a light fill under a white icon.
	 *
	 * The light-mode three are the inks this file already chose for these tiers
	 * before the discs went solid.
	 */
	--fp-tier-bronze: #a1642a;
	--fp-tier-bronze: light-dark(#a1642a, #d9a26a);
	--fp-tier-silver: #5d6671;
	--fp-tier-silver: light-dark(#5d6671, #cbd2d9);
	--fp-tier-gold: #8a6508;
	--fp-tier-gold: light-dark(#8a6508, #e2b44a);
	/* Cyan, not the calories blue beside it: 5.4:1 on white, and a shape of its own. */
	--fp-macro-water: #0e7490;
	--fp-macro-water: light-dark(#0e7490, #67e8f9);

	/*
	 * Stacking order, named once.
	 *
	 * The top bar must win over the navigation, because on desktop the two
	 * overlap by design at the moment the page scrolls. Bare numbers in two
	 * distant blocks are exactly how that got the wrong way round.
	 */
	--fp-z-nav: 20;
	--fp-z-topbar: 30;
	/* Over the sheet's 100 — a question raised while a sheet is open has to be
	   readable, and behind it would be worse than not asking. */
	--fp-z-modal: 120;
}

/* ---------------------------------------------------------------------------
 * Fonts
 *
 * Self-hosted woff2 rather than the Google Fonts CDN: it is one less external
 * dependency, it keeps working behind a firewall, and it avoids shipping
 * visitor IPs to a third party. `font-display: swap` means text is readable
 * immediately rather than invisible while the face downloads.
 * ------------------------------------------------------------------------ */

@font-face {
	font-family: "Oswald";
	font-style: normal;
	font-weight: 200 700;
	font-display: swap;
	src: url("../fonts/oswald-variable.woff2") format("woff2-variations");
}

@font-face {
	font-family: "Open Sans";
	font-style: normal;
	font-weight: 300 800;
	font-display: swap;
	src: url("../fonts/open-sans-variable.woff2") format("woff2-variations");
}

/* ---------------------------------------------------------------------------
 * Scoped reset
 * ------------------------------------------------------------------------ */

.fitpro-app {
	font-family: var(--fp-font-body);
	font-size: 15px;
	line-height: 1.65;
	color: var(--fp-body);
	background: var(--fp-surface);
	text-align: left;
	box-sizing: border-box;

	/* Divi sets a max-width and centring on .entry-content children. */
	width: 100%;
	max-width: none;
}

.fitpro-app *,
.fitpro-app *::before,
.fitpro-app *::after {
	box-sizing: inherit;
}

/*
 * ...and the chain breaks at every `<details>`, which is not obvious and was
 * costing 32px a row.
 *
 * `box-sizing` is not an inherited property; the rule above only works because
 * every element in the tree says `inherit`. Chrome now puts a UA pseudo-element,
 * `::details-content`, between a `<details>` and its children — and a `*`
 * selector cannot match a pseudo-element, so that one link keeps the initial
 * `content-box` and **everything inside the disclosure inherits it**.
 *
 * Measured on the phases card: a row inside an open fold computed
 * `content-box`, added its 16px of padding on each side to a width that was
 * already the full column, and came out 1128px inside a 1094px list — hanging
 * 21px past the stage above it, which is what "there is a lot of bleeding" was.
 * Stating it here took the same row to 1094.
 *
 * **Its own rule, never appended to the list above.** One unknown selector
 * invalidates the whole list it is in, so adding it there would drop the
 * box-sizing reset for the entire app on any browser that has not got this
 * pseudo-element yet. Alone, it is simply ignored there — and on those browsers
 * nothing sits between, so there was no break to repair.
 */
.fitpro-app ::details-content {
	box-sizing: inherit;
}

/*
 * Lists, against Divi.
 *
 * Divi styles content lists with
 * `#left-area ul, .entry-content ul, .et-l--body ul, …` — and `#left-area` is
 * an **ID**, so it outranks any class-based selector we can write, however many
 * classes we stack. Specificity is not a contest we can win here; `!important`
 * is, and this is the one place the app uses it, scoped to its own subtree.
 *
 * Divi injects exactly three things — a disc marker, a `1em` left pad plus
 * `23px` bottom pad, and a `26px` line-height — and all three are neutralised
 * here rather than in each of the dozen component lists, so a list added later
 * is right by default. Without it every `<ul>` in the app grows bullets and a
 * phantom indent: the scorecard headers were the visible symptom, but the task
 * list, the form library, the consent list and the meal plan were all affected.
 *
 * `<ol>` keeps its numbers: `.fitpro-steps-list` is exercise instructions, where
 * the numbering is the content. Only the padding and line-height are reset for
 * it, and it re-declares its own indent immediately after.
 */
.fitpro-app ul,
.fitpro-app ol {
	padding: 0 !important;
	line-height: inherit !important;
}

.fitpro-app ul {
	list-style: none !important;
}

/* Restated because the reset above zeroes the padding these rely on. */
.fitpro-app .fitpro-steps-list {
	padding-left: 20px !important;
}

.fitpro-app h1,
.fitpro-app h2,
.fitpro-app h3,
.fitpro-app h4 {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	color: var(--fp-heading);
	text-transform: uppercase;
	margin: 0;
	padding: 0;
	line-height: 1.15;
}

/*
 * The reference design pairs very large Oswald with ~0.18em tracking. At
 * 320px that overflows badly, so both size AND letter-spacing scale together --
 * clamping only the font size leaves the tracking to blow the line out.
 */
.fitpro-app h1 {
	font-size: clamp(28px, 7vw, 56px);
	letter-spacing: clamp(2px, 0.9vw, 10px);
}

.fitpro-app h2 {
	font-size: clamp(22px, 5vw, 40px);
	letter-spacing: clamp(1.5px, 0.7vw, 7px);
}

.fitpro-app h3 {
	font-size: clamp(16px, 3vw, 22px);
	letter-spacing: clamp(1px, 0.4vw, 3px);
}

/* The "eyebrow" label above a heading in the reference layout. */
.fitpro-app .fitpro-eyebrow {
	font-family: var(--fp-font-heading);
	font-weight: 500;
	font-size: 12px;
	letter-spacing: 5px;
	text-transform: uppercase;
	color: var(--fp-accent);
	margin: 0 0 var(--fp-space-2);
	overflow-wrap: anywhere;
}

.fitpro-app p {
	margin: 0 0 var(--fp-space-4);
}

.fitpro-app a {
	color: var(--fp-accent);
	text-decoration: none;
}

.fitpro-app a:hover {
	color: var(--fp-accent-dark);
}

.fitpro-app :focus-visible {
	outline: 3px solid var(--fp-accent);
	outline-offset: 2px;
	border-radius: 4px;
}

/* ---------------------------------------------------------------------------
 * A client's block, as a calendar
 * ------------------------------------------------------------------------ */

/*
 * Scrolls sideways on a narrow screen rather than disappearing.
 *
 * Seven columns do not fit 320px and never will, and the alternative — hiding
 * the grid below a breakpoint the way the card/table swaps do — needs a card
 * layout to swap *to*. There is one: the list, which is a tap away. So the grid
 * scrolls and nobody is left with nothing.
 */
.fitpro-app .fitpro-cal-wrap {
	overflow-x: auto;
	margin-top: var(--fp-space-3);
	-webkit-overflow-scrolling: touch;
}

.fitpro-app .fitpro-cal {
	width: 100%;
	min-width: 560px;
	border-collapse: collapse;
	table-layout: fixed;
}

.fitpro-app .fitpro-cal th {
	padding: 6px 4px;
	color: var(--fp-muted);
	font-family: var(--fp-font-body);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	text-align: center;
}

.fitpro-app .fitpro-cal th[scope='row'] {
	width: 34px;
	text-align: center;
}

.fitpro-app .fitpro-cal__wk {
	font-size: 10px;
}

.fitpro-app .fitpro-calday {
	height: 62px;
	padding: 5px 6px;
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	vertical-align: top;
	font-size: 12px;
}

.fitpro-app .fitpro-calday__date {
	display: block;
	color: var(--fp-muted);
	font-size: 11px;
	font-weight: 700;
}

/*
 * Two lines and then an ellipsis. A session name is often longer than a seventh
 * of the width, and letting it wrap freely makes one busy week twice the height
 * of the others — which reads as a mistake rather than as a full week.
 */
.fitpro-app .fitpro-calday__what {
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
	color: var(--fp-heading);
	line-height: 1.25;
}

.fitpro-app .fitpro-calday--rest .fitpro-calday__what {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-calday--workout {
	background: var(--fp-accent-soft);
}

.fitpro-app .fitpro-calday__done {
	color: var(--fp-success);
	font-weight: 700;
}

/*
 * The day's corner holds the circle-plus that adds a task. Positioned, so it
 * takes no line of its own; the date leaves room for it.
 */
.fitpro-app .fitpro-calday {
	position: relative;
}

.fitpro-app .fitpro-cal--editable .fitpro-calday__date {
	padding-right: 22px;
}

.fitpro-app .fitpro-calday.is-past .fitpro-calday__date {
	opacity: 0.7;
}

.fitpro-app .fitpro-calday__add {
	position: absolute;
	top: 2px;
	right: 2px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: none;
	color: var(--fp-muted);
	cursor: pointer;
}

.fitpro-app .fitpro-calday__add:hover,
.fitpro-app .fitpro-calday__add:focus-visible {
	color: var(--fp-accent);
	background: var(--fp-accent-soft);
}

/* A session's name, one after another when a day holds two. */
.fitpro-app .fitpro-cal--dated .fitpro-calday__what + .fitpro-calday__what {
	margin-top: 2px;
}

/* The day's tasks, as their icons in a row. */
.fitpro-app .fitpro-calday__tasks {
	display: flex;
	flex-wrap: wrap;
	gap: 3px;
	margin: 4px 0 0 !important;
	padding: 0 !important;
	list-style: none !important;
}

.fitpro-app .fitpro-daytask-chip {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	padding: 0;
	border: 1px solid var(--fp-accent);
	border-radius: 6px;
	background: var(--fp-card);
	color: var(--fp-accent);
	cursor: default;
}

.fitpro-app button.fitpro-daytask-chip {
	cursor: pointer;
}

/* Done: filled, so the state is a shape and not only a colour. */
.fitpro-app .fitpro-daytask-chip.is-done {
	border-color: var(--fp-success);
	background: var(--fp-success);
	color: var(--fp-card);
}

/* Done recedes; today is the one cell asking to be found. */
.fitpro-app .fitpro-calday.is-done .fitpro-calday__what {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-calday.is-today {
	outline: 2px solid var(--fp-accent);
	outline-offset: -2px;
}

/*
 * The exact date under a relative one, in the scorecard's tooltip.
 *
 * Quieter and smaller than the sentence above it: the relative phrase is what
 * the coach reads, and this is the thing they check when the phrase is not
 * enough. Not italic-as-emphasis — `<em>` because it is a gloss on the sentence
 * before it, and the font does the rest.
 */
.fitpro-app .fitpro-score__when {
	display: block;
	margin-top: 2px;
	color: var(--fp-muted);
	font-size: 11px;
	font-style: normal;
}

/* ---------------------------------------------------------------------------
 * Bookable time slots
 * ------------------------------------------------------------------------ */

/*
 * A pill per bookable hour, on the client's booking screen.
 *
 * 44px minimum: this is a phone-first flow and a mis-tap books the wrong hour.
 */
.fitpro-app .fitpro-timeslot {
	min-height: var(--fp-tap-min);
	min-width: 88px;
	padding: 10px 14px;
	border: 2px solid var(--fp-line-strong);
	border-radius: var(--fp-radius-pill);
	background: var(--fp-card);
	color: var(--fp-heading);
	font-family: var(--fp-font-body);
	font-size: 14px;
	font-weight: 700;
	cursor: pointer;
	transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.fitpro-app .fitpro-timeslot:hover:not(.is-picked) {
	border-color: var(--fp-accent);
	color: var(--fp-accent);
}

.fitpro-app .fitpro-timeslot.is-picked {
	border-color: var(--fp-accent-fill);
	background: var(--fp-accent-fill);
	/* Never a literal white: the contrast against an arbitrary brand colour is
	   computed server-side, and this is the one place it has to be believed. */
	color: var(--fp-accent-contrast);
}

.fitpro-app .fitpro-timeslot__places {
	display: block;
	font-size: 11px;
	font-weight: 400;
}

/* ---------------------------------------------------------------------------
 * Switches
 * ------------------------------------------------------------------------ */

/*
 * A real checkbox wearing a switch.
 *
 * The input is not `display: none` — it is visually hidden and still focusable,
 * so keyboard tabbing, the focus ring, and the announcement a checkbox already
 * carries all keep working. A `div` with a click handler has to reimplement
 * every one of those and usually reimplements none.
 */
.fitpro-app .fitpro-switch {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	cursor: pointer;
}

/*
 * Auto-adjusting macros in Settings: who it is for, under the switch. Inert and
 * greyed while the switch is off, rather than hidden, so the choice a coach
 * made is still there to read when they turn it back on.
 */
.fitpro-app .fitpro-automacros__who {
	margin-top: var(--fp-space-4);
}

.fitpro-app .fitpro-automacros__label {
	margin: 0 0 var(--fp-space-2);
	font-weight: 600;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-automacros__who--inert {
	opacity: 0.55;
	pointer-events: none;
}

/* Greyed and inert: the setting it would change is decided somewhere else, and the card says where. */
.fitpro-app .fitpro-switch--disabled {
	cursor: not-allowed;
	opacity: 0.55;
}

/*
 * Label first, switch at the far right.
 *
 * `order` rather than reordering the markup, because the DOM order is the
 * reading order a screen reader gets and the input has to stay adjacent to the
 * track it styles — `input:checked + .fitpro-switch__track` is a sibling
 * selector, and moving either would break it silently.
 */
.fitpro-app .fitpro-switch__label {
	order: -1;
	flex: 1;
	min-width: 0;
}

.fitpro-app .fitpro-switch__track {
	order: 1;
}

.fitpro-app .fitpro-switch input {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	border: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
}

.fitpro-app .fitpro-switch__track {
	position: relative;
	flex: none;
	width: 40px;
	height: 22px;
	border-radius: var(--fp-radius-pill);
	background: var(--fp-line-strong);
	transition: background-color 0.18s ease;
}

.fitpro-app .fitpro-switch__knob {
	position: absolute;
	top: 3px;
	left: 3px;
	width: 16px;
	height: 16px;
	border-radius: 50%;
	background: var(--fp-card);
	transition: transform 0.18s ease;
}

.fitpro-app .fitpro-switch input:checked + .fitpro-switch__track {
	background: var(--fp-accent);
}

.fitpro-app .fitpro-switch input:checked + .fitpro-switch__track .fitpro-switch__knob {
	transform: translateX(18px);
}

/*
 * The focus ring goes on the track, since the input it belongs to is the thing
 * nobody can see. Without this the control is keyboard-operable and gives no
 * sign of where the keyboard is.
 */
.fitpro-app .fitpro-switch input:focus-visible + .fitpro-switch__track {
	outline: 2px solid var(--fp-accent);
	outline-offset: 2px;
}

.fitpro-app .fitpro-switch__label {
	font-weight: 700;
	color: var(--fp-heading);
}

/* The head is the row the switch sits on; it has to span it to push right. */
.fitpro-app .fitpro-threshold__head .fitpro-switch {
	width: 100%;
}

/* Off is quieter, not struck through: it is a setting, not a failure. */
.fitpro-app .fitpro-threshold--muted .fitpro-switch__label {
	color: var(--fp-muted);
}

/* ---------------------------------------------------------------------------
 * A client's run through a program
 * ------------------------------------------------------------------------ */

/*
 * A list, not the coach's seven-column grid.
 *
 * The grid answers "what does this block look like", which is a question about
 * the *shape* of a programme and needs the week laid out side by side. A client
 * is asking "what am I doing, and when" — a question with an order to it, which
 * is a list. It also survives a 320px phone without a horizontal scroller,
 * which the grid only manages by being one.
 */
.fitpro-app .fitpro-runlist {
	list-style: none;
	margin: var(--fp-space-3) 0 0;
	padding: 0;
}

.fitpro-app .fitpro-runlist__row {
	display: flex;
	align-items: baseline;
	gap: var(--fp-space-3);
	padding: 10px 0;
	border-bottom: 1px solid var(--fp-line);
	font-size: 14px;
}

.fitpro-app .fitpro-runlist__row:last-child {
	border-bottom: 0;
}

.fitpro-app .fitpro-runlist__date {
	flex: none;
	min-width: 96px;
	color: var(--fp-muted);
	font-size: 13px;
}

.fitpro-app .fitpro-runlist__what {
	flex: 1;
	min-width: 0;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-runlist__done {
	flex: none;
	color: var(--fp-success);
	font-size: 12px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 1px;
}

/*
 * Start, or resume, from the block itself.
 *
 * A client can now open any session in a programme on any day, so this list is
 * twenty-eight rows with a control on each of the training ones. The button
 * keeps the 44px floor and gives up its width instead: the pill's 32px of
 * horizontal padding and 3px of tracking are right for a card's primary action
 * and wrong repeated down a list, where at 320px they would leave about forty
 * pixels for the name of the workout.
 *
 * The row wraps rather than overflowing. Bug 52's family: a date at 96px, a
 * title that will not shrink below its content and a button that will not
 * shrink at all is more than a phone has, and the failure mode is the whole
 * page sliding sideways rather than this row looking cramped.
 *
 * `align-self` because the row is `align-items: baseline` — right for a date
 * beside a title, and it would hang a button off the baseline of its own
 * label.
 */
.fitpro-app .fitpro-runlist__row {
	flex-wrap: wrap;
}

.fitpro-app .fitpro-runlist__go {
	flex: none;
	margin-left: auto;
	align-self: center;
}

.fitpro-app .fitpro-runlist__go .fitpro-btn {
	padding: 10px 18px;
	font-size: 12px;
	letter-spacing: 1.5px;
}

/*
 * The offer, above the block it is offering.
 *
 * Its own class rather than `.fitpro-actions`, which right-aligns — that is
 * where a form's Save belongs, and this is a sentence followed by the one thing
 * to do about it, which reads left to right like everything else on the card.
 */
.fitpro-app .fitpro-prompt-actions {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--fp-space-2);
	margin: var(--fp-space-3) 0;
}

/* Done recedes; today is the only row that asks for attention. */
.fitpro-app .fitpro-runlist__row.is-done .fitpro-runlist__what {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-runlist__row.is-today {
	border-left: 3px solid var(--fp-accent);
	margin-left: calc(-1 * var(--fp-space-3));
	padding-left: calc(var(--fp-space-3) - 3px);
}

.fitpro-app .fitpro-runlist__row.is-today .fitpro-runlist__what {
	font-weight: 700;
}

/* ---------------------------------------------------------------------------
 * Buttons — pill geometry from the reference design
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-btn,
.fitpro-gate .fitpro-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--fp-space-2);
	min-height: var(--fp-tap-min);
	padding: 14px 32px;
	border: 2px solid transparent;
	border-radius: var(--fp-radius-pill);
	font-family: var(--fp-font-body);
	font-weight: 800;
	font-size: 13px;
	letter-spacing: 3px;
	text-transform: uppercase;
	cursor: pointer;
	transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease;
	text-align: center;
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-btn--primary,
.fitpro-gate .fitpro-btn--primary {
	background: var(--fp-accent-fill);
	border-color: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
}

.fitpro-app .fitpro-btn--primary:hover,
.fitpro-gate .fitpro-btn--primary:hover {
	background: var(--fp-accent-dark);
	border-color: var(--fp-accent-dark);
	color: var(--fp-accent-contrast);
}

.fitpro-app .fitpro-btn--secondary {
	background: var(--fp-card);
	border-color: var(--fp-accent);
	color: var(--fp-accent);
}

.fitpro-app .fitpro-btn--secondary:hover {
	background: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
}

.fitpro-app .fitpro-btn--ghost {
	background: transparent;
	border-color: var(--fp-line-strong);
	color: var(--fp-body);
}

.fitpro-app .fitpro-btn--ghost:hover {
	border-color: var(--fp-accent);
	color: var(--fp-accent);
}

.fitpro-app .fitpro-btn[disabled] {
	opacity: 0.5;
	cursor: not-allowed;
}

.fitpro-app .fitpro-btn--block {
	width: 100%;
}

/* ---------------------------------------------------------------------------
 * Cards
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-card {
	background: var(--fp-card);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius-lg);
	box-shadow: var(--fp-shadow);
	padding: var(--fp-space-5);
}

.fitpro-app .fitpro-card__title {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: 15px;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--fp-heading);
	margin: 0 0 var(--fp-space-3);
}

/* ---------------------------------------------------------------------------
 * App shell
 * ------------------------------------------------------------------------ */

/*
 * The full-screen template renders no theme chrome, so the page itself has to
 * be told to fill the window — a theme's body margin or a stray max-width would
 * otherwise leave the app floating in a gutter.
 *
 * Scoped to the class that template adds, so a site that opts back into its
 * theme header is untouched.
 */
body.fitpro-fullscreen {
	margin: 0;
	padding: 0;
	background: var(--fp-surface);
}

body.fitpro-fullscreen .fitpro-app {
	max-width: none;
	margin: 0;
}

/*
 * The WordPress admin bar overlays the top of the page, so our sticky chrome
 * has to start below it.
 *
 * Only above 782px. Core switches `#wpadminbar` to `position: absolute` at that
 * width, which means it scrolls away — reserving 46px for it there would leave
 * a permanent band of empty page above the app the moment you scrolled, which
 * is what the previous rule did.
 *
 * Set once, as the offset every sticky element reads. Two elements stick here
 * and they must agree; see --fp-chrome-top.
 */
@media (min-width: 783px) {
	body.fitpro-fullscreen.admin-bar .fitpro-app {
		--fp-chrome-top: 32px;
	}
}

/*
 * The theme switch, and the whole of it.
 *
 * `color-scheme` is what every `light-dark()` above reads, so setting this one
 * property is the entire mechanism — no second palette, no class toggling on
 * every element, and nothing for JavaScript to do after the page has painted.
 *
 * `light dark` on the default means "follow the device", which is what somebody
 * gets before they ever open the setting. It also tells the browser to render
 * its own furniture — scrollbars, form controls, the caret — to match, which is
 * the detail that separates a themed app from a light app with dark paint on it.
 *
 * The attribute is written by PHP at render time. Doing it from the app instead
 * would paint the page light and then flip it, which on a phone at night is a
 * flash in the face, and would leave the boot screen — which exists precisely
 * because the app has not started yet — permanently the wrong colour.
 */
.fitpro-app,
.fitpro-gate {
	color-scheme: light dark;
}

.fitpro-app[data-fp-theme='light'],
.fitpro-gate[data-fp-theme='light'] {
	color-scheme: light;
}

.fitpro-app[data-fp-theme='dark'],
.fitpro-gate[data-fp-theme='dark'] {
	color-scheme: dark;
}

/*
 * The page behind the app.
 *
 * The app fills the viewport, but the document underneath it is the theme's
 * white until something says otherwise — visible as a flash on load, behind an
 * overscroll bounce, and anywhere the shell does not reach.
 */
body.fitpro-fullscreen {
	background: var(--fp-surface);
}

.fitpro-app .fitpro-shell {
	display: flex;
	flex-direction: column;

	/*
	 * 100dvh, not 100vh. On iOS Safari 100vh includes the collapsing URL bar,
	 * so a full-height shell is taller than the visible viewport and the bottom
	 * tab bar sits permanently offscreen. The vh line is the fallback for
	 * browsers without dvh.
	 *
	 * **Minus whatever sits above us, or the page always scrolls.** WordPress
	 * puts `margin-top: 32px` on `html` to make room for the admin bar, and
	 * `100dvh` is the whole viewport regardless — so the document came out
	 * 32px taller than the window on *every* screen, whatever the content. The
	 * symptom is a scrollbar that is always present and reveals nothing, and a
	 * sidebar that creeps up by 32px before its sticky offset catches it. It
	 * looks like a content-length problem and is not: a page with two clients on
	 * it scrolled exactly as far as a page with two hundred.
	 *
	 * `--fp-chrome-top` is 0 without an admin bar, and 0 below 783px where core
	 * switches the bar to `position: absolute` and it scrolls away on purpose —
	 * so the subtraction only applies in the one case where those pixels are
	 * genuinely unreachable.
	 */
	min-height: 100vh;
	min-height: calc(100dvh - var(--fp-chrome-top));
}

.fitpro-app .fitpro-topbar {
	position: sticky;
	top: var(--fp-chrome-top);
	z-index: var(--fp-z-topbar);
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-3);
	height: var(--fp-topbar-h);
	/* No shrinking when the shell is a flex column and content is tall. */
	flex: none;
	padding: 0 var(--fp-space-4);
	background: var(--fp-navy);
	color: var(--fp-inverse);
}

.fitpro-app .fitpro-topbar__brand {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: 15px;
	letter-spacing: 3px;
	text-transform: uppercase;
	color: var(--fp-inverse);
	min-width: 0;
}

.fitpro-app .fitpro-topbar__title {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
 * The logo is a phone-only substitute for the name, so it is not drawn at all
 * on a desktop — where there is room for both and the name is what a reader
 * scans for.
 */
.fitpro-app .fitpro-topbar__logo {
	display: none;
	/*
	 * Bounded on both axes. Height is what keeps the bar 56px; the width cap is
	 * for the shape this cannot know — a square mark at 32px is 32px wide and a
	 * wordmark is four times that, and an unbounded one would push the mode
	 * switch and the way out off a 375px screen. `contain` so the cap never
	 * squashes it.
	 */
	max-height: 32px;
	max-width: 140px;
	width: auto;
	object-fit: contain;
	flex: none;
}

.fitpro-app .fitpro-topbar__mark {
	width: 10px;
	height: 24px;
	flex: none;
	background: var(--fp-accent);
	border-radius: 2px;
}

.fitpro-app .fitpro-topbar__actions {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
}

.fitpro-app .fitpro-badge {
	display: inline-block;
	padding: 3px 10px;
	border-radius: var(--fp-radius-pill);
	background: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
	font-family: var(--fp-font-heading);
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 2px;
	text-transform: uppercase;
	white-space: nowrap;
}

.fitpro-app .fitpro-badge--muted {
	background: rgba(255, 255, 255, 0.16);
}

.fitpro-app .fitpro-main {
	flex: 1;

	/*
	 * The flexbox trap, and it is load-bearing.
	 *
	 * This is a flex item inside `.fitpro-shell`, and a flex item defaults to
	 * `min-width: auto` — it refuses to shrink below its content's *min-content*
	 * width. So any single wide thing in the main column stops being clipped or
	 * scrolled and instead pushes the whole document sideways: the sidebar, the
	 * header and every other card slide right with it.
	 *
	 * A segmented control with eight options found it. Nothing before had,
	 * because the other wide things in this app — tables, the tab rail — all sit
	 * in their own `overflow-x: auto` wrapper, which reports a min-content of
	 * zero and hides the problem. `min-width: 0` is what makes those wrappers
	 * actually able to do their job.
	 */
	min-width: 0;
	padding: var(--fp-space-5) var(--fp-space-4);

	/*
	 * Reserve room for the fixed tab bar plus the iOS home indicator, or the
	 * last card sits underneath it and cannot be reached.
	 */
	padding-bottom: calc(var(--fp-tabbar-h) + env(safe-area-inset-bottom) + var(--fp-space-5));
}

/*
 * Full width by default.
 *
 * This was capped at 1100px, which is right for an article and wrong for a
 * dashboard: on a 1440px screen it left a third of the window empty while the
 * attention scorecard shrank its columns. The app is a tool, not prose, so it
 * takes the width it is given.
 *
 * Left as a variable rather than deleted, because the reasoning is not
 * universal — a trainer who wants the classic centred column can set
 * `--fp-app-max` on `.fitpro-app` and get it back without a patch.
 */
.fitpro-app .fitpro-container {
	width: 100%;
	max-width: var(--fp-app-max, none);
	margin: 0 auto;
}

/* ---------------------------------------------------------------------------
 * Bottom tab bar (mobile) → side rail (desktop)
 * ------------------------------------------------------------------------ */

/*
 * The bar is a wrapper holding the scrolling rail and two scroll hints, so the
 * hints can sit outside the scroller and stay put while it moves.
 */
.fitpro-app .fitpro-tabs-wrap {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: var(--fp-z-nav);
	display: flex;
	align-items: stretch;
	/* Positioning context for the overlaid carets. */
	isolation: isolate;
	background: var(--fp-card);
	border-top: 1px solid var(--fp-line);
	padding-bottom: env(safe-area-inset-bottom);
}

.fitpro-app .fitpro-tabs {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	/*
	 * The rail scrolls rather than overflowing the page. Tabs() already scrolls
	 * the active one into view, which only works if there is a scroll container
	 * to scroll — without this the bar simply grew past the viewport and took
	 * the whole document with it, so every screen scrolled sideways a few
	 * pixels. Six tabs happened to fit; the seventh did not.
	 */
	overflow-x: auto;
	overflow-y: hidden;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
	overscroll-behavior-x: contain;
}

.fitpro-app .fitpro-tabs::-webkit-scrollbar {
	display: none;
}

/*
 * One rule, not two.
 *
 * There used to be a second `.fitpro-tabs__item` block below this one setting
 * `flex: 1`, which silently won and undid the "never shrink" intent. The tabs
 * were pinned to a 64px box while their labels rendered at natural width, so
 * anything longer than about eight characters painted straight over its
 * neighbours: "ANTRENAMENTE" measured 90px inside a 64px tab. English escaped
 * only by luck — "WORKOUTS" is 63px in that same box, one pixel of slack.
 *
 * Tabs are now sized by their content, with a floor for the tap target. The rail
 * scrolls, which it was already built to do, and the carets say so.
 */
.fitpro-app .fitpro-tabs__item {
	flex: 0 0 auto;
	min-width: var(--fp-tap-min);
	max-width: 40vw;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 3px;
	min-height: var(--fp-tabbar-h);
	padding: 6px 12px;
	border: 0;
	background: none;
	color: var(--fp-muted);
	font-family: var(--fp-font-body);
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.6px;
	text-transform: uppercase;
	cursor: pointer;
}

/*
 * The label never wraps and never spills. `max-width` on the item caps a
 * pathological translation rather than letting one tab eat the rail; the
 * ellipsis is the fallback of last resort, not the normal case.
 */
/* How many are waiting in a section, on its icon: a phone's bar has no room beside the word. */
.fitpro-app .fitpro-tabs__icon {
	position: relative;
}

.fitpro-app .fitpro-tabs__count {
	position: absolute;
	top: -7px;
	right: -11px;
	min-width: 18px;
	height: 18px;
	padding: 0 5px;
	border-radius: 9px;
	background: var(--fp-danger);
	color: #fff;
	font-size: 11px;
	font-weight: 700;
	line-height: 18px;
	text-align: center;
	letter-spacing: 0;
}

.fitpro-app .fitpro-tabs__label {
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
 * Centre the row when the tabs do not fill it — a tablet, or a wide phone in
 * landscape, otherwise left-justifies eight tabs and leaves a third of the bar
 * empty on the right.
 *
 * Auto margins rather than `justify-content: center`, which is actively wrong on
 * a scroller: when the content *does* overflow, centring pushes it past both
 * edges and the left portion becomes unreachable, because `scrollLeft` cannot go
 * negative. Per the flexbox spec an overflowing box ignores its auto margins and
 * overflows in the end direction, so this centres when there is room and behaves
 * exactly like flex-start when there is not.
 */
.fitpro-app .fitpro-tabs__item:first-child {
	margin-inline-start: auto;
}

.fitpro-app .fitpro-tabs__item:last-child {
	margin-inline-end: auto;
}

.fitpro-app .fitpro-tabs__item[aria-current="page"] {
	color: var(--fp-accent);
}

/*
 * Icons inherit the tab's colour, so the active state needs no separate rule.
 * Bootstrap's are filled and Lucide's are stroked; the generator strips both
 * sets of attributes so the difference is expressed here once.
 */
.fitpro-app .fitpro-icon {
	display: block;
	fill: currentColor;
}

.fitpro-app .fitpro-icon--stroked {
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

/*
 * Scroll hints.
 *
 * Hidden with the `hidden` attribute when there is nothing that way, so the bar
 * looks like an ordinary tab bar whenever everything fits — which is most of the
 * time on a wide screen, and would otherwise be two permanently dead arrows.
 */
/*
 * The carets float **over** the ends of the rail rather than sitting beside it
 * in the flow.
 *
 * In the flow they changed the rail's width as they appeared, and that fed back:
 * navigating to the last tab scrolled it flush to the edge, which revealed the
 * right caret, which narrowed the rail by exactly the caret's width and pushed
 * the tab straight back out of view. Overlaying them means the rail's width
 * never changes, so `scrollIntoView` lands where it aimed and the overflow
 * measurement cannot chase its own tail.
 *
 * A tab partly under a caret is correct rather than a defect — a caret only
 * shows when there is more that way, and the gradient reads as "continues".
 */
.fitpro-app .fitpro-tabs__nudge {
	position: absolute;
	top: 0;
	bottom: env(safe-area-inset-bottom);
	z-index: 1;
	width: 44px;
	display: flex;
	align-items: center;
	border: 0;
	color: var(--fp-heading);
	cursor: pointer;
	padding: 0;
}

.fitpro-app .fitpro-tabs__nudge--left {
	left: 0;
	justify-content: flex-start;
	padding-left: 4px;
	background: linear-gradient(to right, var(--fp-card) 45%, transparent);
}

.fitpro-app .fitpro-tabs__nudge--right {
	right: 0;
	justify-content: flex-end;
	padding-right: 4px;
	background: linear-gradient(to left, var(--fp-card) 45%, transparent);
}

/*
 * An author `display` beats the user-agent's `[hidden] { display: none }`, so
 * the attribute alone left both carets on screen permanently — including a left
 * one pointing at nothing while the rail was already at its start.
 */
.fitpro-app .fitpro-tabs__nudge[hidden] {
	display: none;
}

.fitpro-app .fitpro-tabs__nudge svg {
	fill: currentColor;
}

.fitpro-app .fitpro-tabs__icon {
	font-size: 18px;
	line-height: 1;
}

/*
 * 980px matches one of Divi's own breakpoints. Aligning to them avoids a band
 * of widths where the app has switched layout but the surrounding Divi sections
 * have not, which reads as a rendering bug.
 */
@media (min-width: 980px) {
	/*
	 * The bar becomes a sidebar, so the wrapper takes the positioning it used to
	 * have as a bottom bar and the rail inside it simply stacks. Nothing scrolls
	 * horizontally at this width, so the scroll hints come out entirely rather
	 * than being hidden by the overflow measurement — there is no overflow to
	 * measure.
	 */
	/*
	 * The sidebar sticks directly under the top bar, which is itself stuck
	 * `--fp-chrome-top` down the viewport. Both terms are needed: with only
	 * `--fp-topbar-h` the rail rode 32px too high under an admin bar and, being
	 * the higher layer, scrolled straight over the header.
	 *
	 * `height` subtracts both for the same reason — otherwise the rail is
	 * taller than the space it has and its last tab is unreachable.
	 */
	.fitpro-app .fitpro-tabs-wrap {
		position: sticky;
		top: calc(var(--fp-topbar-h) + var(--fp-chrome-top));
		left: auto;
		right: auto;
		bottom: auto;
		width: 200px;
		border-top: 0;
		border-right: 1px solid var(--fp-line);
		height: calc(100dvh - var(--fp-topbar-h) - var(--fp-chrome-top));
		padding-bottom: 0;
	}

	.fitpro-app .fitpro-tabs {
		flex-direction: column;
		width: 100%;
		overflow-x: hidden;
		overflow-y: auto;
		padding: var(--fp-space-4) var(--fp-space-2);
	}

	.fitpro-app .fitpro-tabs__nudge {
		display: none;
	}

	.fitpro-app .fitpro-tabs__item:first-child,
	.fitpro-app .fitpro-tabs__item:last-child {
		/*
		 * Cross-axis now that the rail is a column: an auto margin here would
		 * override `align-items: stretch` and shrink each row to its own text.
		 */
		margin-inline: 0;
	}

	.fitpro-app .fitpro-tabs__item {
		flex: none;
		max-width: none;
		flex-direction: row;
		justify-content: flex-start;
		gap: var(--fp-space-3);
		min-height: var(--fp-tap-min);
		padding: 10px var(--fp-space-3);
		border-radius: var(--fp-radius);
		font-size: 12px;
		letter-spacing: 1.5px;
	}

	.fitpro-app .fitpro-tabs__item[aria-current="page"] {
		background: var(--fp-accent-soft);
	}

	/*
	 * In the sidebar a long label wraps rather than being cut off: "Client
	 * analytics" came out "CLIENT ANALYT…", and a translation is often longer.
	 * The phone's bottom bar keeps one line, where there is no room for two.
	 */
	.fitpro-app .fitpro-tabs__label {
		overflow: visible;
		white-space: normal;
		text-align: left;
		line-height: 1.3;
	}

	.fitpro-app .fitpro-body {
		display: flex;
		align-items: flex-start;
	}

	.fitpro-app .fitpro-main {
		padding: var(--fp-space-6);
		padding-bottom: var(--fp-space-8);
	}
}

/* ---------------------------------------------------------------------------
 * Hero — the radial orange→navy wash from the reference design
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-hero {
	position: relative;
	border-radius: var(--fp-radius-lg);
	padding: var(--fp-space-6) var(--fp-space-5);
	margin-bottom: var(--fp-space-5);
	color: var(--fp-inverse);
	background-image: radial-gradient(
		circle at 0% 0%,
		rgba(var(--fp-accent-rgb), 0.79) 0%,
		rgba(38, 45, 63, 0.96) 58%
	);
	overflow: hidden;
}

.fitpro-app .fitpro-hero h1,
.fitpro-app .fitpro-hero h2 {
	color: var(--fp-inverse);
}

.fitpro-app .fitpro-hero .fitpro-eyebrow {
	color: rgba(255, 255, 255, 0.85);
}

.fitpro-app .fitpro-hero p {
	color: rgba(255, 255, 255, 0.86);
	margin-bottom: 0;
}

/* ---------------------------------------------------------------------------
 * Grid, stats, states
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-grid {
	display: grid;
	gap: var(--fp-space-4);
	grid-template-columns: 1fr;
}

@media (min-width: 640px) {
	.fitpro-app .fitpro-grid--2,
	.fitpro-app .fitpro-grid--3,
	.fitpro-app .fitpro-grid--4 {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (min-width: 980px) {
	.fitpro-app .fitpro-grid--3 {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	.fitpro-app .fitpro-grid--4 {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}

.fitpro-app .fitpro-stat__value {
	font-family: var(--fp-font-heading);
	font-size: 34px;
	font-weight: 700;
	line-height: 1;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-stat__label {
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	color: var(--fp-muted);
	margin-top: var(--fp-space-2);
}

.fitpro-app .fitpro-empty {
	padding: var(--fp-space-7) var(--fp-space-4);
	text-align: center;
	color: var(--fp-muted);
	border: 1px dashed var(--fp-line-strong);
	border-radius: var(--fp-radius-lg);
}

.fitpro-app .fitpro-error {
	padding: var(--fp-space-4);
	border-left: 4px solid var(--fp-danger);
	background: var(--fp-danger-wash);
	color: var(--fp-danger-ink);
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-skeleton {
	background: linear-gradient(90deg, var(--fp-skeleton) 25%, var(--fp-skeleton-shine) 37%, var(--fp-skeleton) 63%);
	background-size: 400% 100%;
	animation: fitpro-shimmer 1.4s ease infinite;
	border-radius: var(--fp-radius);
}

@keyframes fitpro-shimmer {
	0% {
		background-position: 100% 50%;
	}

	100% {
		background-position: 0 50%;
	}
}

/* ---------------------------------------------------------------------------
 * Boot skeleton
 *
 * Rendered by the shortcode before React mounts. Without it there is a visible
 * blank page on a phone over mobile data, which reads as a broken site.
 * ------------------------------------------------------------------------ */

.fitpro-boot {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--fp-space-4);
	min-height: 60vh;
	color: var(--fp-muted);
	font-family: var(--fp-font-heading);
	font-size: 12px;
	letter-spacing: 4px;
	text-transform: uppercase;
}

.fitpro-boot__mark {
	width: 12px;
	height: 40px;
	background: var(--fp-accent);
	border-radius: 3px;
	animation: fitpro-pulse 1.1s ease-in-out infinite;
}

@keyframes fitpro-pulse {
	0%,
	100% {
		transform: scaleY(0.55);
		opacity: 0.55;
	}

	50% {
		transform: scaleY(1);
		opacity: 1;
	}
}

/* ---------------------------------------------------------------------------
 * Sign-in gate
 * ------------------------------------------------------------------------ */

.fitpro-gate {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: var(--fp-space-4);
	padding: var(--fp-space-8) var(--fp-space-4);
	text-align: center;
	font-family: var(--fp-font-body);
}

.fitpro-gate__title {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: clamp(20px, 4vw, 30px);
	letter-spacing: clamp(1.5px, 0.6vw, 5px);
	text-transform: uppercase;
	color: var(--fp-heading);
	margin: 0;
}

.fitpro-gate__alt {
	font-size: 13px;
	color: var(--fp-body);
	text-decoration: underline;
}

/* ---------------------------------------------------------------------------
 * Forms
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-field,
.fitpro-gate .fitpro-field {
	margin-bottom: var(--fp-space-4);
}

.fitpro-app .fitpro-field__label,
.fitpro-gate .fitpro-field__label,
/*
 * The points row's name carries an invisible one of these, so it lands on the
 * inputs' line rather than on their labels'. It is in this selector list, and
 * not a copy of the numbers below, because a copy is what drifts: change the
 * label's size or margin here and the spacer follows it exactly.
 */
.fitpro-app .fitpro-pointrow__name::before {
	display: block;
	margin-bottom: 6px;
	font-family: var(--fp-font-body);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1.6px;
	text-transform: uppercase;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-field__req {
	color: var(--fp-accent);
}

.fitpro-app .fitpro-field__help {
	margin: 0 0 8px;
	font-size: 13px;
	color: var(--fp-muted);
}

/*
 * A hint that is a refusal, not an explanation.
 *
 * The muted colour is right for "here is how this field works" and wrong for
 * "this will not work" -- reported against the periodization steer, which sat
 * in grey under the dropdown reading exactly like the help text above it and
 * was therefore read as neither.
 *
 * `--fp-danger` rather than the accent: the app uses the accent for emphasis
 * and this colour for a problem, and a coach should be able to tell the two
 * apart without reading. It carries weight as well as hue, because colour is
 * never the only signal here.
 */
.fitpro-app .fitpro-field__help--warn {
	color: var(--fp-danger);
	font-weight: 600;
}

/* The modifier, not the base class: a dozen screens use `fitpro-field__help`
   as a standalone explanatory paragraph above something, and those keep the
   bottom margin they were written against. Only the hint the field wrapper
   renders under its own control flips. */
.fitpro-app .fitpro-field__help--under,
.fitpro-gate .fitpro-field__help--under {
	margin: 6px 0 0;
}

.fitpro-app .fitpro-field__error {
	margin: 6px 0 0;
	font-size: 13px;
	font-weight: 700;
	color: var(--fp-danger);
}

.fitpro-app .fitpro-input,
.fitpro-gate .fitpro-input {
	display: block;
	width: 100%;
	min-height: var(--fp-tap-min);
	padding: 11px 14px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	font-family: var(--fp-font-body);

	/*
	 * 16px minimum. iOS Safari zooms the whole page when a focused input has a
	 * font size below 16px, and the layout never zooms back out.
	 */
	font-size: 16px;
	color: var(--fp-heading);
	appearance: none;
}

/*
 * The colour is restated here, and it is not redundant.
 *
 * Divi injects `input[type="text"]:focus, select:focus, textarea:focus
 * { color: #3e3e3e }`. That is one attribute, one pseudo-class and one element
 * — (0,2,1) — against the base rule's two classes, (0,2,0). The `b` column
 * ties and Divi wins on the element, so the moment a field takes focus its text
 * turns near-black on a near-black card and disappears while you type. Idle it
 * is fine, because Divi's *unfocused* rule is only (0,1,1) and loses.
 *
 * Adding `:focus` here makes this (0,3,0), which wins on the `b` column
 * outright. `TextField`, `TextareaField` and `SelectField` all carry
 * `fitpro-input`, so one rule covers the three controls Divi's selector names.
 *
 * Measured rather than reasoned about: with a real click and keystrokes the
 * computed colour was `rgb(62, 62, 62)` on a `rgb(27, 30, 38)` background.
 * A programmatic `.focus()` did not reproduce it, which is worth remembering
 * the next time something is "not reproducible".
 */
/*
 * A placeholder must not read as a value.
 *
 * Reported by a coach who added an exercise and took the tempo placeholder for
 * a default that was already set. It looks exactly like one: every literal
 * placeholder in this app was a bare value — "100", "2", "8", "3-1-1-0" —
 * rendered in the same face and nearly the same colour as typed text, in a
 * field whose whole purpose is to hold a number of that shape.
 *
 * Two changes together, because neither is enough alone. Each now reads
 * "e.g. 3-1-1-0", which says in words what it is; and the type is italic, which
 * is what makes an empty field look empty **at a glance** rather than only on
 * reading it.
 *
 * Muted rather than faint. A placeholder often carries the only example of the
 * format a field wants, so it has to stay legible — the fix for "it looks like
 * a value" is that it no longer looks like typed text, not that it is hard to
 * see.
 */
.fitpro-app .fitpro-input::placeholder,
.fitpro-gate .fitpro-input::placeholder {
	color: var(--fp-muted);
	font-style: italic;
	opacity: 1; /* Firefox dims placeholders by default; the colour above is the decision. */
}

.fitpro-app .fitpro-input:focus,
.fitpro-gate .fitpro-input:focus {
	border-color: var(--fp-accent);
	outline: none;
	box-shadow: 0 0 0 3px var(--fp-accent-soft);
	color: var(--fp-heading);
}

.fitpro-app .fitpro-input--error {
	border-color: var(--fp-danger);
}

.fitpro-app .fitpro-textarea {
	min-height: 96px;
	resize: vertical;
	line-height: 1.5;
}

.fitpro-app .fitpro-select,
.fitpro-gate .fitpro-select {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%23666' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: right 14px center;
	padding-right: 38px;
}

/*
 * A dropdown list paints its own surface, whatever its select is wearing.
 *
 * Reported from the program builder in dark mode: clicking a cell to choose a
 * workout or a rest day opened a list whose entries were invisible until the
 * keyboard or the pointer highlighted one.
 *
 * `.fitpro-pday__pick` is the only `<select>` in the product with
 * `background: transparent` — deliberately, because an empty cell in that grid
 * has to read as empty. Every other one carries `background: var(--fp-card)`,
 * and an `<option>` **inherits its background and colour from its select**, so
 * every other list in the app has been painting itself opaque by accident. The
 * program grid's options inherited the transparent, and Chromium composited
 * them onto its own popup surface with our dark-mode `--fp-body` on top.
 * Measured: `option` computed `background-color: rgba(0, 0, 0, 0)` with
 * `color: rgb(178, 184, 193)`.
 *
 * `color-scheme: dark` is in force on the element and does not rescue it. It
 * computes correctly all the way down to the option — verified — and an author
 * background plus `appearance: none` is enough for the browser to paint the
 * list from the author's colours rather than the platform's dark ones.
 *
 * So the option says what it is rather than inheriting it. Stated on every
 * option in the app rather than on that one select, because the invariant is
 * about dropdowns rather than about the program grid: a list drawn outside the
 * page cannot borrow a background from an ancestor, and the next transparent
 * select would repeat this in exactly the same silence.
 *
 * It is a no-op for the closed control. Chromium paints the collapsed value in
 * the **select's** own colour, so a rest day stays muted and italic and a
 * chosen workout stays bold — confirmed by screenshot either side of the rule
 * as well as by the computed values.
 *
 * It also settles something smaller: `.fitpro-pday--rest` colours its select
 * muted, which the options were inheriting too, so in the open list "Rest day"
 * rendered greyer than the rows around it and read as unavailable. Every row is
 * a choice, and they all look like one now.
 */
.fitpro-app option,
.fitpro-app optgroup,
.fitpro-gate option,
.fitpro-gate optgroup {
	background-color: var(--fp-card);
	color: var(--fp-heading);
}

.fitpro-app .fitpro-field-row,
.fitpro-gate .fitpro-field-row {
	display: grid;
	grid-template-columns: 1fr;
	gap: 0 var(--fp-space-3);
}

/*
 * Feet and inches, side by side at every width including 320px.
 *
 * `.fitpro-field-row` above stacks below 560px, which is right for two
 * unrelated fields and wrong here: two full-width boxes one above the other,
 * labelled Feet and Inches, read as two questions rather than one height. They
 * hold one or two digits, so 320px is not the constraint it looks like — each
 * box gets about 140px, well clear of the 44px tap floor on the steppers.
 *
 * A <fieldset> carries a default border, padding and min-inline-size from the
 * user agent; the last of those is the one that bites, because it refuses to
 * let the fieldset shrink below its content and pushes the page sideways on a
 * phone. Bug 52's family, from the one element that has its own rule for it.
 */
.fitpro-app .fitpro-heightfield,
.fitpro-gate .fitpro-heightfield {
	border: 0;
	margin: 0;
	padding: 0;
	min-inline-size: 0;
}

.fitpro-app .fitpro-heightfield__row,
.fitpro-gate .fitpro-heightfield__row {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 0 var(--fp-space-3);
}

@media (min-width: 560px) {
	.fitpro-app .fitpro-field-row,
	.fitpro-gate .fitpro-field-row {
		grid-template-columns: 1fr 1fr;
	}
}

/* Honeypot: removed from view without display:none, which some bots detect. */
.fitpro-hp {
	position: absolute;
	left: -9999px;
	width: 1px;
	height: 1px;
	overflow: hidden;
}

/* Chips (multi-select) */
.fitpro-app .fitpro-chips {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-chip {
	min-height: var(--fp-tap-min);
	padding: 8px 16px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius-pill);
	background: var(--fp-card);
	font-family: var(--fp-font-body);
	font-size: 13px;
	font-weight: 700;
	color: var(--fp-body);
	cursor: pointer;
}

.fitpro-app .fitpro-chip--on {
	background: var(--fp-accent-fill);
	border-color: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
}

/* The coach's copy of a picker is a record, not a control. */
.fitpro-app .fitpro-chip:disabled {
	cursor: default;
	opacity: 1;
}

/* ---------------------------------------------------------------------------
 * Body map
 *
 * A drawing you click, and every site as a toggle underneath it. The toggles
 * are not a fallback: an SVG polygon is not a control, so they are the only
 * half a keyboard or a screen reader can operate — and they are what makes the
 * picker complete when the map's chunk fails to load.
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-bodymap {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-4);
}

/*
 * The drawing is centred and capped, not stretched.
 *
 * The library sizes its SVG to the container, and a body scaled to the full
 * width of a 1440px card is a diagram nobody can click accurately — the regions
 * are small and close together at the shoulders and the forearms. 280px is
 * about life-size for a thumbnail and comfortably above the tap floor for the
 * smallest region on a phone.
 */
.fitpro-app .fitpro-bodymap__canvas {
	display: flex;
	justify-content: center;
	max-width: 280px;
	margin: 0 auto;
}

.fitpro-app .fitpro-bodymap__canvas svg {
	width: 100%;
	height: auto;
	cursor: pointer;
}

/* A fieldset again, for the same reason the height field is one: a group of
   toggles is one answer, and a <label> can only point at one control. */
.fitpro-app .fitpro-bodymap__list {
	border: 0;
	margin: 0;
	padding: 0;
	min-inline-size: 0;
}

.fitpro-app .fitpro-bodymap__summary {
	margin: 0;
	color: var(--fp-body);
	font-size: 13px;
	line-height: 1.6;
}

/* Scale */
.fitpro-app .fitpro-scale {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}

.fitpro-app .fitpro-scale__step {
	flex: 1 0 auto;
	min-width: 40px;
	min-height: var(--fp-tap-min);
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	font-family: var(--fp-font-heading);
	font-size: 15px;
	font-weight: 600;
	color: var(--fp-body);
	cursor: pointer;
}

.fitpro-app .fitpro-scale__step--on {
	background: var(--fp-accent-fill);
	border-color: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
}

.fitpro-app .fitpro-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: flex-end;
	gap: var(--fp-space-3);
	margin-top: var(--fp-space-5);
}

/* A row of controls that belongs to the field above it rather than to the form.
   `.fitpro-actions` right-aligns, which is where a form's Save goes and the
   wrong place for a picker sitting under its own label. */
.fitpro-app .fitpro-actions--start {
	justify-content: flex-start;
	margin-top: var(--fp-space-3);
}

.fitpro-app .fitpro-saved {
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-success);
}

.fitpro-app .fitpro-linkish,
.fitpro-gate .fitpro-linkish {
	border: 0;
	background: none;
	padding: 0;
	font-family: var(--fp-font-body);
	font-size: 14px;
	font-weight: 700;
	color: var(--fp-accent);
	text-decoration: underline;
	cursor: pointer;
}

.fitpro-app .fitpro-linkish[disabled] {
	opacity: 0.5;
	cursor: not-allowed;
}

/* ---------------------------------------------------------------------------
 * Auth screen
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-auth {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--fp-space-5);
	max-width: 1000px;
	margin: 0 auto;
	padding: var(--fp-space-5) var(--fp-space-4);
}

@media (min-width: 900px) {
	.fitpro-app .fitpro-auth {
		grid-template-columns: 1.1fr 0.9fr;
		align-items: start;
		padding: var(--fp-space-7) var(--fp-space-4);
	}
}

.fitpro-app .fitpro-auth__panel {
	background: var(--fp-card);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius-lg);
	box-shadow: var(--fp-shadow);
	padding: var(--fp-space-5);
}

.fitpro-app .fitpro-auth__lead {
	color: var(--fp-body);
	margin: var(--fp-space-3) 0 var(--fp-space-5);
}

.fitpro-app .fitpro-auth__alt {
	margin: var(--fp-space-4) 0 0;
	font-size: 14px;
	text-align: center;
}

.fitpro-app .fitpro-auth__aside {
	border-radius: var(--fp-radius-lg);
	padding: var(--fp-space-6) var(--fp-space-5);
	color: var(--fp-inverse);
	background-image: radial-gradient(
		circle at 0% 0%,
		rgba(var(--fp-accent-rgb), 0.79) 0%,
		rgba(38, 45, 63, 0.96) 58%
	);
}

.fitpro-app .fitpro-auth__aside h2 {
	color: var(--fp-inverse);
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-auth__aside p {
	color: rgba(255, 255, 255, 0.86);
}

/* ---------------------------------------------------------------------------
 * Wizard progress
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-steps {
	display: flex;
	gap: 6px;
	margin-bottom: var(--fp-space-2);
}

.fitpro-app .fitpro-steps__bar {
	flex: 1;
	height: 5px;
	border-radius: 3px;
	background: var(--fp-line);
	transition: background-color 0.2s ease;
}

.fitpro-app .fitpro-steps__bar--on {
	background: var(--fp-accent);
}

.fitpro-app .fitpro-steps__label {
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	color: var(--fp-muted);
	margin-bottom: var(--fp-space-4);
}

/* ---------------------------------------------------------------------------
 * Client list
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-page-head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-5);
}

.fitpro-app .fitpro-filters {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-4);
}

@media (min-width: 640px) {
	.fitpro-app .fitpro-filters {
		grid-template-columns: 2fr 1fr;
	}
}

.fitpro-app .fitpro-avatar {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 42px;
	height: 42px;
	border-radius: 50%;
	background: var(--fp-disc);
	color: var(--fp-disc-ink);
	font-family: var(--fp-font-heading);
	font-size: 14px;
	font-weight: 600;
	letter-spacing: 1px;
}

.fitpro-app .fitpro-avatar--sm {
	width: 28px;
	height: 28px;
	font-size: 11px;
	margin-right: var(--fp-space-2);
	vertical-align: middle;
}

/*
 * A stacked list of tappable rows. Four screens use it: clients, workouts,
 * content and conversations.
 *
 * It was called `.fitpro-client-cards`, which is how it broke. The Clients
 * screen swaps its cards for a table on a wide viewport, and the rule that does
 * the swapping was written against this generic class — so **workouts, content
 * and messages were all `display: none` above 900px**, with their rows present
 * in the DOM, correct, and invisible. Three blank screens on every desktop, no
 * error anywhere, and a coach with thirty workouts looking at an empty page.
 *
 * The name was the bug. A list named after the first screen that used it
 * invites a rule meant for that screen to be written against it, and nothing
 * about `.fitpro-client-cards { display: none }` looks wrong when you are
 * looking at the Clients screen. So the generic list has a generic name, and
 * the screen that gives way to a table opts in explicitly below.
 */
.fitpro-app .fitpro-cards {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-3);
}

.fitpro-app .fitpro-card-row {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	width: 100%;
	min-height: 68px;
	padding: var(--fp-space-3) var(--fp-space-4);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius-lg);
	background: var(--fp-card);
	box-shadow: var(--fp-shadow);
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-card-row__body {
	flex: 1;
	min-width: 0;
}

/*
 * Where somebody has got to, inside the greeting that names them.
 *
 * Right-aligned and beside their own name, because this half of the card is
 * about them rather than about what is due -- and because it is the way into
 * the badges, the points ledger and the leaderboard, which before this lived
 * behind an unlabelled card nobody had a reason to press.
 *
 * It carries no background of its own: a button inside the hero that painted
 * itself would read as a card inside a card.
 */
.fitpro-app .fitpro-levelrings {
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: var(--fp-space-2);
	margin-block-start: var(--fp-space-3);
	margin-inline-start: auto;
	text-align: end;
}

.fitpro-app .fitpro-levelrings--opens {
	inline-size: auto;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	cursor: pointer;
}

.fitpro-app .fitpro-levelrings__pair {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
}

/*
 * A disc, not a ring. A level is a fact rather than a position between two
 * things, and a ring filled to 100% would read as progress that has finished.
 * 84px matches the ring beside it, so the two read as a pair.
 */
.fitpro-app .fitpro-levelrings__disc {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	inline-size: 84px;
	block-size: 84px;
	padding: 4px;
	border-radius: 50%;
	background: var(--fp-disc);
	color: var(--fp-disc-ink);
	text-align: center;
}

.fitpro-app .fitpro-levelrings__level {
	font-family: var(--fp-font-heading);
	font-size: 26px;
	font-weight: 700;
	line-height: 1;
}

/*
 * The name is the long part -- "Hall of Fame" inside 84px -- so it wraps and
 * shrinks rather than overflowing the disc.
 */
.fitpro-app .fitpro-levelrings__name {
	margin-block-start: 2px;
	font-size: 9px;
	font-weight: 800;
	letter-spacing: 0.4px;
	line-height: 1.1;
	text-transform: uppercase;
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-levelrings__ring {
	display: block;
	flex: none;
}

.fitpro-app .fitpro-levelrings__points {
	display: block;
	font-family: var(--fp-font-heading);
	font-size: 22px;
	font-weight: 700;
	line-height: 1;
	/*
	 * Inherited, never a text token. The hero is a dark gradient in both
	 * themes, so `--fp-heading` is #333 on navy in light mode -- measured, and
	 * unreadable. Inheriting means this follows whatever the hero decides its
	 * own text should be.
	 */
	color: inherit;
}

.fitpro-app .fitpro-levelrings__unit {
	display: block;
	margin-block-start: 2px;
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	/* Quieter than the figure, without leaving the hero's own colour. */
	color: inherit;
	opacity: 0.72;
}

.fitpro-app .fitpro-levelrings__lines {
	display: flex;
	flex-direction: column;
	gap: 2px;
	font-size: 13px;
	color: inherit;
	opacity: 0.85;
}

.fitpro-app .fitpro-levelrings__next {
	font-weight: 700;
}

/*
 * The affordance the old card never had: this is a way through, and it has to
 * look like one before somebody will press it.
 */
/*
 * White rather than the accent, which is the one thing here that cannot be
 * checked in advance: the accent is the coach's own colour, the hero is built
 * from it, and a link tinted with it sits on a background made of it. On this
 * install both are blue and the label all but vanished.
 *
 * The underline is what makes it read as a way through once the colour no
 * longer does that job.
 */
.fitpro-app .fitpro-levelrings__go {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1.2px;
	text-transform: uppercase;
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
}

.fitpro-app .fitpro-levelrings--opens:hover .fitpro-levelrings__go,
.fitpro-app .fitpro-levelrings--opens:focus-visible .fitpro-levelrings__go {
	text-decoration-thickness: 2px;
}

/*
 * The ring's track is `--fp-line` inside `ProgressRing`, which is a card
 * border and is invisible on the hero. Overridden here rather than in the
 * component, because every other caller of it sits on a card where the token
 * is right.
 */
.fitpro-app .fitpro-levelrings__ring circle:first-of-type {
	stroke: rgba(255, 255, 255, 0.28);
}

/*
 * On a phone the greeting is already the full width, so the rings centre under
 * the text rather than hugging an edge the eye has to travel to.
 */
@media (max-width: 560px) {
	.fitpro-app .fitpro-levelrings {
		align-items: center;
		margin-inline-start: 0;
		text-align: center;
	}
}

/*
 * The badge grid.
 *
 * Locked badges are shown beside earned ones rather than hidden, so the grid
 * says what is next as well as what is done -- which is the difference between
 * a trophy cabinet and a reason to open the screen twice.
 */
.fitpro-app .fitpro-awards {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 200px), 1fr));
	gap: var(--fp-space-3);
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * A locked badge is quiet, not invisible.
 *
 * Dimmed rather than greyed to a flat colour, so its tier still reads: somebody
 * can see that the thing they have not earned yet is a gold one.
 */
.fitpro-app .fitpro-award {
	display: grid;
	grid-template-columns: 48px 1fr;
	grid-template-areas:
		'mark name'
		'.    what'
		'.    meta';
	gap: 2px var(--fp-space-2);
	padding: var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	opacity: 0.55;
}

.fitpro-app .fitpro-award.is-earned {
	opacity: 1;
	border-color: currentColor;
}

/*
 * A locked trophy is drained of its colour as well as dimmed.
 *
 * Opacity alone leaves a gold cup looking gold, so a grid of mostly-locked
 * badges reads as a grid of mostly-earned ones at a glance. Greyscale is the
 * second signal, and between them neither depends on telling bronze from gold.
 */
.fitpro-app .fitpro-award:not(.is-earned) .fitpro-award__mark {
	filter: grayscale(1);
}

/*
 * The tier's colour, and the only thing it is still visibly for.
 *
 * Every line of text in the tile sets its own colour, so this reaches the eye
 * through `.is-earned`'s `border-color: currentColor` -- the ring around a
 * badge somebody has won.
 *
 * Through the tokens rather than three more hexes: these are the same three
 * colours the tier label beside the name uses, and two hard-coded copies of one
 * idea is how they drift. It also gains a dark mode, which the literals never
 * had -- `#b8860b` is a light-theme gold ringing a dark card.
 */
.fitpro-app .fitpro-award--bronze { color: var(--fp-tier-bronze); }
.fitpro-app .fitpro-award--silver { color: var(--fp-tier-silver); }
.fitpro-app .fitpro-award--gold { color: var(--fp-tier-gold); }

.fitpro-app .fitpro-award__mark {
	grid-area: mark;
	inline-size: 48px;
	block-size: 48px;
	/*
	 * Drawn at 48 from a 192px file, so a 2x screen has resolution in hand and
	 * there is no second asset per tier to ship and keep in step.
	 */
	object-fit: contain;
}

.fitpro-app .fitpro-award__name {
	grid-area: name;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-award__what {
	grid-area: what;
	font-size: 13px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-award__meta {
	grid-area: meta;
	font-size: 12px;
	color: var(--fp-body);
}

/*
 * The leaderboard itself: one line per client, using the width it has.
 *
 * A ladder is read *down a column* -- every eye scanning it is comparing one
 * figure against the same figure on the row above -- so the name, the level, the
 * streaks, the badges and the score each get a column and stay in it. Stacked
 * into a left-hand strip, which is what the generic card row does, it left two
 * thirds of a 1440px screen empty and made every row three lines tall: nine
 * screens of scrolling for a roster of twelve.
 *
 * `minmax(0, …)` on the flexible columns rather than a bare fraction, because a
 * long client name would otherwise set the column's automatic minimum and push
 * the score off the end -- bug 59, in the one layout where the last column is
 * the one somebody came to read.
 */
.fitpro-app .fitpro-ladder {
	margin: 0;
	padding: 0;
	list-style: none;
}

.fitpro-app .fitpro-ladder__row {
	display: grid;
	grid-template-columns:
		auto
		minmax(0, 2fr)
		minmax(0, 2fr)
		minmax(0, 1.5fr)
		auto
		auto;
	/*
	 * Centred, because every cell on this row is now one line of text.
	 *
	 * It was `start` while the open badge list sat inside the badges cell and
	 * made it three hundred pixels tall. The list is its own grid row now, so
	 * there is no tall cell to align against and the line is a line again.
	 */
	align-items: center;
	gap: var(--fp-space-3);
	padding: var(--fp-space-2) var(--fp-space-2);
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-ladder__row:last-child {
	border-bottom: 0;
}

/*
 * One column axis for the whole ladder, not one per row.
 *
 * `display: grid` on the row makes every `<li>` its own grid, so the tracks are
 * computed from that row's own content: a row with five badge discs and a
 * four-digit score sizes its `auto` columns differently from a row with two
 * discs and a three-digit one, and the fractional columns take the difference.
 * Measured down five rows, the level began at 307, 307, 315, 353 and 347 --
 * which a reader sees not as five columns but as one ragged, centred-looking
 * smear, and reports as "why is this centred".
 *
 * That defeats the only thing a ladder is for, and the note above this block
 * has been claiming the opposite: the level and the streaks "each get a column
 * and stay in it" was true across a row and false down the page, which is the
 * direction anybody reads a ladder in.
 *
 * `subgrid` is precisely this arrangement -- the list owns the tracks, every
 * row adopts them, and the row stays a real element, so it keeps its border,
 * its padding and the tint on the reader's own row. Each row carries the same
 * padding, so the first and last tracks are adjusted by the same amount and the
 * axis still lines up.
 *
 * The row keeps its own `grid-template-columns` above as the fallback: without
 * subgrid this renders exactly what it rendered before, which is ragged rather
 * than broken. The panel's second row is unaffected -- only the columns are
 * inherited, never the rows.
 */
@supports (grid-template-columns: subgrid) {
	.fitpro-app .fitpro-ladder {
		display: grid;
		grid-template-columns:
			auto
			minmax(0, 2fr)
			minmax(0, 2fr)
			minmax(0, 1.5fr)
			auto
			auto;
		column-gap: var(--fp-space-3);
	}

	.fitpro-app .fitpro-ladder__row {
		grid-column: 1 / -1;
		grid-template-columns: subgrid;
	}
}

.fitpro-app .fitpro-ladder__name {
	font-weight: 700;
	color: var(--fp-heading);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.fitpro-app .fitpro-ladder__level,
.fitpro-app .fitpro-ladder__runs {
	font-size: 13px;
	color: var(--fp-body);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
 * The badges span is not a cell; its two children are.
 *
 * It holds the strip of discs and, when open, the panel listing every badge --
 * and inside one `auto` column the panel is what sizes that column. Opening it
 * widened the badges track from about 126px to 372px and took the difference
 * out of the name, the level and the streaks, so the row the panel belongs to
 * was garbled by reading it: "Dana Rodri..." beside "12 wks trai...".
 *
 * `display: contents` drops the span's box and promotes both children to grid
 * items of the row, so the strip sizes the column and the panel spans the whole
 * width on a row of its own -- which is what `__list`'s own `grid-column: 1/-1`
 * has claimed since it was written, and could not do from inside a cell. The
 * span carries no role, so nothing is lost from the accessibility tree.
 *
 * Its two children are still addressed through the span, never as `__row > `:
 * `display: contents` drops the box from the layout and changes nothing about
 * the DOM, so a child combinator on the row does not reach them. Written that
 * way the strip matched no rule at all, auto-placed into the first free cell
 * and took the rank's -- on a phone the rank and the discs landed in one box.
 *
 * Every cell is then placed by hand. Auto-placement would put the panel on row
 * two and the score on row three, because a full-width item leaves no room
 * beside it.
 */
.fitpro-app .fitpro-ladder__badges {
	display: contents;
}

.fitpro-app .fitpro-ladder__row > .fitpro-rank {
	grid-column: 1;
	grid-row: 1;
}

.fitpro-app .fitpro-ladder__name {
	grid-column: 2;
	grid-row: 1;
}

.fitpro-app .fitpro-ladder__level {
	grid-column: 3;
	grid-row: 1;
}

.fitpro-app .fitpro-ladder__runs {
	grid-column: 4;
	grid-row: 1;
}

.fitpro-app .fitpro-ladder__badges > .fitpro-bstack,
.fitpro-app .fitpro-ladder__badges > .fitpro-bstack__none {
	grid-column: 5;
	grid-row: 1;
	justify-self: end;
}

.fitpro-app .fitpro-ladder__row > .fitpro-points {
	grid-column: 6;
	grid-row: 1;
}

.fitpro-app .fitpro-ladder__badges > .fitpro-bstack__list {
	grid-row: 2;
}

/*
 * Below 760px the columns become rows.
 *
 * Six columns in 293px is 48px each, which is not a column, it is a stack of
 * ellipses. The name and the score keep the top line together -- they are the
 * two figures somebody is actually comparing -- and everything else falls
 * underneath.
 */
@media (max-width: 760px) {
	/*
	 * No shared axis on a phone: the row is three columns of its own with named
	 * areas, which a subgrid of six tracks cannot express. Declared after the
	 * `@supports` block so it wins on order at equal specificity.
	 */
	.fitpro-app .fitpro-ladder {
		display: block;
	}

	.fitpro-app .fitpro-ladder__row {
		grid-template-columns: auto minmax(0, 1fr) auto;
		/*
		 * The badge strip takes the whole width on a phone, rather than sitting
		 * indented under the name.
		 *
		 * It loses a little alignment and gains the room its open list needs:
		 * confined to the two right-hand columns the panel is about 250px, and
		 * every badge's name and criterion has to fit in it.
		 */
		grid-template-areas:
			'rank name points'
			'.    level level'
			'.    runs runs'
			'badges badges badges';
		gap: 2px var(--fp-space-2);
		padding-block: var(--fp-space-3);
	}

	.fitpro-app .fitpro-ladder__row > .fitpro-rank { grid-area: rank; }
	.fitpro-app .fitpro-ladder__name { grid-area: name; }
	.fitpro-app .fitpro-ladder__level { grid-area: level; }
	.fitpro-app .fitpro-ladder__runs { grid-area: runs; }
	/*
	 * The areas belong to the children now, since the span has no box. The
	 * panel takes a row of its own under the named ones, and has to give back
	 * the explicit `grid-row: 2` the desktop rule sets -- there are four named
	 * rows here, so row two is the level.
	 */
	.fitpro-app .fitpro-ladder__badges > .fitpro-bstack,
	.fitpro-app .fitpro-ladder__badges > .fitpro-bstack__none {
		grid-area: badges;
		justify-self: start;
	}

	.fitpro-app .fitpro-ladder__badges > .fitpro-bstack__list {
		grid-row: auto;
		grid-column: 1 / -1;
	}
	.fitpro-app .fitpro-ladder__row > .fitpro-points { grid-area: points; }

	/* An empty column is a gap on a phone; on a desktop it is alignment. */
	.fitpro-app .fitpro-ladder__runs:empty,
	.fitpro-app .fitpro-ladder__level:empty {
		display: none;
	}
}

/*
 * The badges on a row: overlapping circles, newest first.
 *
 * Overlapped rather than spaced, so five of them cost about the width of three
 * and the strip still reads as one thing. A button rather than a row of images,
 * because every one of them has a name and a criterion that a phone cannot
 * hover to discover.
 */
.fitpro-app .fitpro-bstack {
	display: inline-flex;
	align-items: center;
	padding: 0;
	border: 0;
	background: none;
	cursor: pointer;
}

/*
 * Solid tier colour with a white glyph, rather than a tint with coloured ink.
 *
 * A strip of five is meant to be read at a glance from across a row, and a
 * sixteen-percent wash of bronze on a dark card is a dark grey circle -- the
 * tier is the information and it has to survive being 26px wide. White ink on
 * all three clears 3:1 against its own disc, which is the bar for a graphical
 * object, and it is the same on every tier so the *shape* stays the thing being
 * read and the colour stays the thing being sensed.
 */
.fitpro-app .fitpro-bstack__badge {
	display: inline-flex;
	flex: none;
	align-items: center;
	justify-content: center;
	inline-size: 28px;
	block-size: 28px;
	border-radius: 50%;
	color: #ffffff;
	/* Against the card, so overlapping circles read as separate discs. */
	box-shadow: 0 0 0 2px var(--fp-card);
}

/*
 * Each sits on the one before it. The first keeps its whole width, so the strip
 * starts where the column starts rather than a few pixels inside it.
 */
.fitpro-app .fitpro-bstack__badge + .fitpro-bstack__badge {
	margin-inline-start: -9px;
}

.fitpro-app .fitpro-bstack .fitpro-icon,
.fitpro-app .fitpro-bstack__item .fitpro-icon {
	inline-size: 15px;
	block-size: 15px;
}

/*
 * Tier colours, as a wash behind the glyph rather than a flat fill.
 *
 * The glyph has to stay readable on it, and a solid bronze disc with a dark
 * icon on it is the contrast problem the macro icons already record. The ink is
 * the tier's own colour at full strength; the disc is the same colour at a
 * tenth.
 */
.fitpro-app .fitpro-bstack__badge--bronze { background: #a1642a; }
.fitpro-app .fitpro-bstack__badge--silver { background: #78828e; }
.fitpro-app .fitpro-bstack__badge--gold { background: #c8941a; }

.fitpro-app .fitpro-bstack__more {
	margin-inline-start: var(--fp-space-2);
	font-size: 12px;
	font-weight: 700;
	color: var(--fp-body);
}

.fitpro-app .fitpro-bstack__none {
	font-size: 12px;
	color: var(--fp-body);
}

/*
 * The full list, under the row rather than over it.
 *
 * A modal would owe the browser focus trapping and a scroll lock to be honest
 * about being one, and this is a list somebody glances at and closes.
 */
.fitpro-app .fitpro-bstack__list {
	grid-column: 1 / -1;
	margin-block-start: var(--fp-space-2);
	padding: var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
}

/*
 * The panel's heading, and no row to share.
 *
 * `__head` was a flex row holding this title beside a Close button, with the
 * button pinned against shrinking because uppercase letter-spaced text in a
 * squeezed flex line comes out one letter per row. The strip toggles itself
 * now, so there is no button, no row and nothing left to defend against -- and
 * a wrapper whose only remaining job is a bottom margin is a wrapper the title
 * can do without.
 */
.fitpro-app .fitpro-bstack__title {
	display: block;
	margin-block-end: var(--fp-space-2);
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-bstack__items {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
	gap: var(--fp-space-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

.fitpro-app .fitpro-bstack__item {
	display: flex;
	align-items: flex-start;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-bstack__text {
	display: flex;
	flex-direction: column;
	min-inline-size: 0;
}

.fitpro-app .fitpro-bstack__name {
	font-weight: 700;
	color: var(--fp-heading);
}

/*
 * The tier, as a word beside the name.
 *
 * A wash of the tier's own colour rather than solid: the text has to stay
 * readable on it, which is the call the macro icons already record, and a pill
 * at full strength beside a bold name reads as a button. It sits inside the
 * name element so it stays on that line, and gives back the weight and the
 * heading colour it would otherwise inherit -- it names the badge's worth, it
 * is not part of what the badge is called.
 *
 * `nowrap`, because a two-word tier broken across a line stops reading as a
 * label at all.
 */
.fitpro-app .fitpro-bstack__tier,
.fitpro-app .fitpro-award__tier {
	display: inline-block;
	padding: 1px 7px;
	border-radius: 100px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.3px;
	white-space: nowrap;
	vertical-align: 1px;
}

.fitpro-app .fitpro-bstack__tier {
	font-size: 10px;
}

/*
 * One rule per tier, shared by both screens.
 *
 * The ink is the tier's own colour and the pill is that colour at a tenth --
 * the recipe tags' arrangement, and the one this file already recorded for the
 * discs before they went solid: a full-strength fill behind small text is a
 * contrast problem, and measured, white on this gold is 2.7:1.
 *
 * Named from the tier rather than the hue, like `--fp-macro-fat`, so deciding
 * gold should be amber is one line rather than a hunt for every `#c8941a`.
 */
.fitpro-app .fitpro-bstack__tier--bronze,
.fitpro-app .fitpro-award__tier--bronze {
	color: var(--fp-tier-bronze);
	background: color-mix(in srgb, var(--fp-tier-bronze) 14%, transparent);
}

.fitpro-app .fitpro-bstack__tier--silver,
.fitpro-app .fitpro-award__tier--silver {
	color: var(--fp-tier-silver);
	background: color-mix(in srgb, var(--fp-tier-silver) 14%, transparent);
}

.fitpro-app .fitpro-bstack__tier--gold,
.fitpro-app .fitpro-award__tier--gold {
	color: var(--fp-tier-gold);
	background: color-mix(in srgb, var(--fp-tier-gold) 14%, transparent);
}

.fitpro-app .fitpro-bstack__what {
	font-size: 12px;
	color: var(--fp-body);
}

/*
 * The reader's own row, marked.
 *
 * A faint wash rather than the accent: the row is already named and ranked, and
 * a bright band would make somebody's own line the loudest thing on a list
 * whose subject is everybody else.
 */
.fitpro-app .fitpro-card-row.is-you,
.fitpro-app .fitpro-ladder__row.is-you {
	background: color-mix(in srgb, var(--fp-accent) 8%, transparent);
	border-radius: var(--fp-radius);
}

/*
 * ...and the two cells that centre themselves elsewhere follow the row here.
 */
.fitpro-app .fitpro-ladder__row > .fitpro-rank,
.fitpro-app .fitpro-ladder__row > .fitpro-points {
	align-self: start;
}

/*
 * The position on a leaderboard.
 *
 * Tabular figures and a fixed width, so a column of ranks lines up whether it
 * runs to one digit or three -- a ladder whose numbers wander left and right is
 * harder to read down than one that does not.
 */
.fitpro-app .fitpro-rank {
	flex: none;
	align-self: center;
	min-inline-size: 2.5ch;
	font-weight: 800;
	font-variant-numeric: tabular-nums;
	color: var(--fp-body);
	text-align: end;
}

/*
 * A ledger figure, right-aligned so a column of them lines up.
 *
 * The only negative here is a coach's own adjustment, which is why it is the
 * one that changes colour -- a number that went down is the one somebody will
 * want to ask about.
 */
.fitpro-app .fitpro-points {
	flex: none;
	align-self: center;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-points.is-down {
	color: var(--fp-danger);
}

/*
 * A receipt: one icon, one line, no date.
 *
 * Quieter than every other row in this list on purpose. Finishing a session
 * happens once and gets a picture; logging a meal happens four times a day, and
 * a list where those look the same is a list somebody stops reading.
 */
.fitpro-app .fitpro-receipt {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
	padding-block: var(--fp-space-2);
	color: var(--fp-body);
	font-size: 13px;
}

.fitpro-app .fitpro-receipt__icon {
	flex: none;
	inline-size: 16px;
	block-size: 16px;
	color: var(--fp-accent);
}

.fitpro-app .fitpro-receipt__line {
	min-inline-size: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
 * The picture on a celebration notification.
 *
 * Floated rather than made a flex child, because the row's body is a stack of
 * `display: block` spans and turning it into a flex row to seat one optional
 * image would change the layout of every other notification in the product.
 * A float takes the space it needs and lets the headline and the meta lines
 * wrap around it exactly as they already do.
 *
 * The source is 50px square and this draws it at 40, so it has a little
 * resolution in hand on a 2x screen rather than being stretched.
 */
.fitpro-app .fitpro-cheer {
	float: inline-start;
	width: 40px;
	height: 40px;
	margin-inline-end: var(--fp-space-3);
	margin-block-end: var(--fp-space-1);
}

.fitpro-app .fitpro-card-row__name {
	display: block;
	font-weight: 700;
	color: var(--fp-heading);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.fitpro-app .fitpro-card-row__meta {
	display: block;
	font-size: 13px;
	color: var(--fp-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.fitpro-app .fitpro-card-row__end {
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 4px;
	flex: none;
}

.fitpro-app .fitpro-pill {
	display: inline-block;
	padding: 3px 10px;
	border-radius: var(--fp-radius-pill);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	background: var(--fp-line);
	color: var(--fp-body);
}

.fitpro-app .fitpro-pill--active {
	background: color-mix(in srgb, var(--fp-success) 14%, transparent);
	color: var(--fp-success);
}

.fitpro-app .fitpro-pill--pending {
	background: color-mix(in srgb, var(--fp-warning) 16%, transparent);
	color: var(--fp-warning);
}

.fitpro-app .fitpro-pill--paused {
	background: color-mix(in srgb, var(--fp-body) 16%, transparent);
	color: var(--fp-navy);
}

.fitpro-app .fitpro-pill--archived {
	background: var(--fp-line);
	color: var(--fp-muted);
}

.fitpro-app .fitpro-flag {
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 0.6px;
	text-transform: uppercase;
	color: var(--fp-accent);
}

.fitpro-app .fitpro-count {
	margin-top: var(--fp-space-4);
	font-size: 12px;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

/*
 * Cards below 900px, table at and above it. A five-column table at 375px
 * either scrolls sideways or compresses past the point of usefulness, and this
 * screen gets used standing on a gym floor.
 *
 * The modifier is what makes this safe. Only a list that actually has a
 * `.fitpro-table-wrap` beside it may be hidden, because hiding one that does
 * not leaves the screen empty — which is exactly what happened for as long as
 * this rule named the generic class. `bin/check-classes.mjs` asserts the two
 * always ship together.
 */
.fitpro-app .fitpro-table-wrap {
	display: none;
}

@media (min-width: 900px) {
	.fitpro-app .fitpro-cards--table-above {
		display: none;
	}

	.fitpro-app .fitpro-table-wrap {
		display: block;
		overflow-x: auto;
		border: 1px solid var(--fp-line);
		border-radius: var(--fp-radius-lg);
		background: var(--fp-card);
	}
}

.fitpro-app .fitpro-table {
	width: 100%;
	border-collapse: collapse;
	font-size: 14px;
}

.fitpro-app .fitpro-table th {
	padding: 12px 16px;
	text-align: left;
	font-family: var(--fp-font-body);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1.2px;
	text-transform: uppercase;
	color: var(--fp-muted);
	border-bottom: 1px solid var(--fp-line);
	white-space: nowrap;
}

.fitpro-app .fitpro-table td {
	padding: 12px 16px;
	border-bottom: 1px solid var(--fp-line);
	color: var(--fp-body);
}

.fitpro-app .fitpro-table tbody tr {
	cursor: pointer;
}

.fitpro-app .fitpro-table tbody tr:hover {
	background: var(--fp-accent-soft);
}

.fitpro-app .fitpro-table tbody tr:last-child td {
	border-bottom: 0;
}

/* ---------------------------------------------------------------------------
 * Answer list
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-answers {
	margin: 0;
}

.fitpro-app .fitpro-answers__row {
	display: grid;
	grid-template-columns: 1fr;
	gap: 2px;
	padding: var(--fp-space-3) 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-answers__row:last-child {
	border-bottom: 0;
}

.fitpro-app .fitpro-answers dt {
	font-size: 12px;
	font-weight: 800;
	letter-spacing: 0.8px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-answers dd {
	margin: 0;
	color: var(--fp-heading);
	/*
	 * An email address has no break opportunity, so at 320px
	 * "jacques.pailliere@gmail.com" pushed the whole document 11px wide and
	 * every screen scrolled sideways. `anywhere` rather than `break-word`: the
	 * latter does not shrink the element's intrinsic min-content width, so a
	 * grid track sized from content stays too wide and the overflow survives.
	 */
	min-width: 0;
	overflow-wrap: anywhere;
}

@media (min-width: 640px) {
	.fitpro-app .fitpro-answers__row {
		grid-template-columns: minmax(180px, 1fr) 2fr;
		gap: var(--fp-space-4);
		align-items: baseline;
	}
}

/* ---------------------------------------------------------------------------
 * Provider settings
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-pill--prov-ok {
	background: color-mix(in srgb, var(--fp-success) 14%, transparent);
	color: var(--fp-success);
}

.fitpro-app .fitpro-pill--prov-failed {
	background: color-mix(in srgb, var(--fp-danger) 14%, transparent);
	color: var(--fp-danger);
}

.fitpro-app .fitpro-pill--prov-needs_reauth {
	background: color-mix(in srgb, var(--fp-warning) 18%, transparent);
	color: var(--fp-warning);
}

.fitpro-app .fitpro-pill--prov-unconfigured {
	background: var(--fp-line);
	color: var(--fp-muted);
}

.fitpro-app .fitpro-active-note {
	margin: 0 0 var(--fp-space-4);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-success);
}

.fitpro-app .fitpro-warn {
	padding: var(--fp-space-4);
	border-left: 4px solid var(--fp-warning);
	background: var(--fp-warning-wash);
	color: var(--fp-warning-ink);
	border-radius: var(--fp-radius);
	font-size: 14px;
}

.fitpro-app .fitpro-warn code {
	background: rgba(0, 0, 0, 0.06);
	padding: 1px 5px;
	border-radius: 3px;
	font-size: 12px;
}

.fitpro-app .fitpro-testresult--ok {
	padding: var(--fp-space-3) var(--fp-space-4);
	border-left: 4px solid var(--fp-success);
	background: var(--fp-success-wash);
	color: var(--fp-success-ink);
	border-radius: var(--fp-radius);
	font-size: 14px;
}

.fitpro-app .fitpro-usage {
	margin-top: var(--fp-space-4);
}

/*
 * Wraps, and the figure never breaks. On a Data sources card — a quarter of the
 * row — the label and the figure did not fit one line and interleaved into
 * "EXERCISES IN THE LAST 30 · 0 — DAYS · UNLIMITED". Wrapping puts the figure on
 * its own line as one unit instead.
 */
.fitpro-app .fitpro-usage__head {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: 2px var(--fp-space-3);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1.2px;
	text-transform: uppercase;
	color: var(--fp-muted);
	margin-bottom: 6px;
}

.fitpro-app .fitpro-usage__figure {
	white-space: nowrap;
}

.fitpro-app .fitpro-usage__track {
	height: 8px;
	border-radius: 4px;
	background: var(--fp-line);
	overflow: hidden;
}

.fitpro-app .fitpro-usage__fill {
	height: 100%;
	background: var(--fp-success);
	transition: width 0.3s ease;
}

.fitpro-app .fitpro-usage__fill--hot {
	background: var(--fp-danger);
}

.fitpro-app .fitpro-usage__plan {
	margin: 6px 0 0;
	font-size: 12px;
	color: var(--fp-muted);
}

/* ---------------------------------------------------------------------------
 * Exercise browser
 * ------------------------------------------------------------------------ */

@media (min-width: 900px) {
	.fitpro-app .fitpro-filters--wide {
		grid-template-columns: 2fr 1fr 1fr 1fr;
	}
}

.fitpro-app .fitpro-ex-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--fp-space-3);
}

@media (min-width: 700px) {
	.fitpro-app .fitpro-ex-grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

@media (min-width: 1100px) {
	.fitpro-app .fitpro-ex-grid {
		grid-template-columns: repeat(4, minmax(0, 1fr));
		gap: var(--fp-space-4);
	}
}

.fitpro-app .fitpro-ex-card {
	display: flex;
	flex-direction: column;
	gap: 4px;
	padding: 0 0 var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius-lg);
	background: var(--fp-card);
	box-shadow: var(--fp-shadow);
	overflow: hidden;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-ex-card:hover {
	border-color: var(--fp-accent);
}

.fitpro-app .fitpro-ex-card__thumb {
	display: block;
	width: 100%;
	aspect-ratio: 16 / 10;
	object-fit: cover;
	background: var(--fp-line);
}

.fitpro-app .fitpro-ex-card__thumb--none {
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--fp-muted);
	font-size: 28px;
}

.fitpro-app .fitpro-ex-card__title {
	padding: var(--fp-space-2) var(--fp-space-3) 0;
	font-weight: 700;
	font-size: 14px;
	color: var(--fp-heading);
	line-height: 1.3;
}

.fitpro-app .fitpro-ex-card__meta {
	padding: 0 var(--fp-space-3);
	font-size: 12px;
	color: var(--fp-muted);
	/*
	 * No `text-transform: capitalize`.
	 *
	 * It was doing the casing for a raw provider slug, and doing it wrongly:
	 * `full_body` came out as "Full_body" because the underscore is not a word
	 * break, and anything with a real space became Title Case, which is not the
	 * sentence case the rest of the app uses and mangles the words that stay
	 * lowercase in French, Spanish and Romanian. `humanise()` at the call site
	 * strips the underscores and capitalises the first letter only.
	 */
}

/*
 * The pager, rebuilt.
 *
 * Two pill buttons either side of a label was the geometry of a form's primary
 * action, under every list in the product, twice — reported as "too bulky" and
 * it was. Four carets in the accent, a page number that can be typed into, and
 * the totals.
 */
.fitpro-app .fitpro-pager {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-wrap: wrap;
	gap: 2px;
	margin-top: var(--fp-space-4);
	font-size: 13px;
	color: var(--fp-muted);
}

/*
 * 36px, not the 44px tap floor.
 *
 * That floor is for the in-workout logger — a phone held at arm's length,
 * mid-set, one-handed — and this file already records the same decision for the
 * row tools. Four 44px squares under a list crowd it, and the worst a mis-tap
 * does here is show the wrong page of a list that is still on screen.
 */
.fitpro-app .fitpro-pager__step {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	padding: 0;
	border: 0;
	border-radius: var(--fp-radius);
	background: transparent;
	color: var(--fp-accent);
	cursor: pointer;
	transition: background-color 0.15s ease, color 0.15s ease;
}

.fitpro-app .fitpro-pager__step:hover:not(:disabled) {
	background: var(--fp-accent-soft);
}

/*
 * Dimmed and inert at the ends, never hidden: a control that disappears moves
 * the three beside it, so reaching Next on the second-to-last page would put a
 * different button under the finger already there.
 */
.fitpro-app .fitpro-pager__step:disabled {
	color: var(--fp-line-strong);
	cursor: default;
}

.fitpro-app .fitpro-pager__jump {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	margin: 0 var(--fp-space-2);
	white-space: nowrap;
}

.fitpro-app .fitpro-pager__jump input {
	/*
	 * `size` on the element does the width, so the box grows with the number of
	 * pages a list actually has rather than being pinned here. Centred, because
	 * a right-aligned single digit sits oddly far from its label.
	 */
	min-height: 32px;
	padding: 4px 6px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-heading);
	font: inherit;
	font-variant-numeric: tabular-nums;
	text-align: center;
}

.fitpro-app .fitpro-pager__jump input:focus {
	border-color: var(--fp-accent);
	outline: none;
	box-shadow: 0 0 0 3px var(--fp-accent-soft);
}

.fitpro-app .fitpro-pager__total {
	margin-left: var(--fp-space-3);
	font-size: 12px;
	opacity: 0.75;
}

/*
 * The strip above a list that holds whatever governs it.
 *
 * A row so `.fitpro-pagesize`'s `margin-left: auto` has something to push
 * against, and empty of anything else today — a heading or a result count would
 * be the obvious next thing to put in it.
 */
.fitpro-app .fitpro-listhead {
	display: flex;
	align-items: center;
	justify-content: flex-end;
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-3);
}

/*
 * How many rows to show, above the list rather than under it.
 *
 * Right-aligned into the head of whatever it governs. `margin-left: auto` so it
 * sits at the end of a flex row containing a heading, without the caller having
 * to know it is there.
 */
.fitpro-app .fitpro-pagesize {
	display: inline-flex;
	align-items: center;
	gap: var(--fp-space-2);
	margin-left: auto;
	white-space: nowrap;
	font-size: 13px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-pagesize select {
	min-height: 32px;
	padding: 4px 8px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-heading);
	font: inherit;
	cursor: pointer;
}

/*
 * Three rows on a phone, decided rather than left to `flex-wrap`.
 *
 * Four carets, a label, a box and "of 5,229" come to about 318px inside a card
 * on a 375px screen, which is a handful of pixels too many — so the **last**
 * caret wrapped on its own beneath the rest, which reads as a broken control
 * rather than a full row. Letting a group of four split anywhere is the problem;
 * where it splits is not something to tune.
 *
 * So the carets keep one line to themselves, the page box takes the next and the
 * total the last. Nothing overflows and nothing is separated from what it
 * belongs to. Measured at 375px: `document.scrollWidth` 375 and no element past
 * the viewport edge.
 */
/*
 * On a phone the row is tightened until it fits, rather than stacked.
 *
 * Measured rather than guessed, which is the only reason this is one line: at
 * 375px the card gives the pager **293px** and it wanted 313 — four 36px
 * squares, 8px of gaps and a 161px page box. Twenty pixels, so twenty pixels
 * were found rather than a row broken.
 *
 * The buttons lose width and keep their height. 30 × 36 still clears the 24px
 * minimum in both directions with 7px of padding either side of a 16px glyph,
 * and the axis a thumb actually misses on a horizontal row is the vertical one.
 * The 44px floor this file calls non-negotiable is for the in-workout logger —
 * one-handed, mid-set, out of breath — and the worst a mis-tap does here is show
 * the wrong page of a list that is still on screen.
 *
 * After: 273 of 293 at 375px, and 273 of 278 at 360px, which is the narrowest
 * common phone it fits on. Below that it stacks, and the rule under this one
 * says how.
 */
@media (max-width: 560px) {
	.fitpro-app .fitpro-pager {
		gap: 0;
	}

	.fitpro-app .fitpro-pager__step {
		width: 30px;
	}

	.fitpro-app .fitpro-pager__jump {
		margin: 0 2px;
	}

	.fitpro-app .fitpro-pager__total {
		flex: 1 0 100%;
		margin-left: 0;
		text-align: center;
	}
}

/*
 * Narrower than any phone it fits on, so the carets take a row of their own.
 *
 * `order`, because the box sits **between** the carets in the markup — which is
 * where the page number belongs in every pager anybody has used. Giving it a
 * full-width row without reordering cut the four carets into two rows of two,
 * which is worse than the wrap it was fixing.
 *
 * Reading order and tab order part company here, deliberately: a keyboard user
 * still moves first → previous → page → next → last, which is the sequence that
 * makes sense whichever way the row is drawn.
 */
@media (max-width: 359px) {
	.fitpro-app .fitpro-pager__jump {
		order: 1;
		flex: 1 0 100%;
		justify-content: center;
		margin: var(--fp-space-2) 0 0;
	}

	.fitpro-app .fitpro-pager__total {
		order: 2;
	}
}

.fitpro-app .fitpro-stale {
	margin: var(--fp-space-3) 0;
	padding: var(--fp-space-3);
	border-left: 3px solid var(--fp-warning);
	background: var(--fp-warning-wash);
	color: var(--fp-warning-ink);
	font-size: 13px;
	border-radius: var(--fp-radius);
}

/* Detail sheet: bottom sheet on mobile, centred dialog on desktop. */
.fitpro-app .fitpro-sheet {
	position: fixed;
	inset: 0;
	z-index: 100;
	display: flex;
	align-items: flex-end;
	justify-content: center;
}

.fitpro-app .fitpro-sheet__backdrop {
	position: absolute;
	inset: 0;
	background: var(--fp-scrim);
}

.fitpro-app .fitpro-sheet__panel {
	position: relative;
	width: 100%;
	max-height: 88dvh;
	overflow-y: auto;
	padding: var(--fp-space-5);
	padding-bottom: calc(var(--fp-space-5) + env(safe-area-inset-bottom));
	background: var(--fp-card);
	border-radius: var(--fp-radius-lg) var(--fp-radius-lg) 0 0;

	/* Momentum scrolling inside the sheet on iOS. */
	-webkit-overflow-scrolling: touch;
}

@media (min-width: 700px) {
	.fitpro-app .fitpro-sheet {
		align-items: center;
	}

	.fitpro-app .fitpro-sheet__panel {
		max-width: 620px;
		max-height: 86dvh;
		border-radius: var(--fp-radius-lg);
	}
}

.fitpro-app .fitpro-sheet__close {
	position: absolute;
	top: 10px;
	right: 10px;
	width: var(--fp-tap-min);
	height: var(--fp-tap-min);
	border: 0;
	border-radius: 50%;
	background: var(--fp-surface);
	color: var(--fp-heading);
	font-size: 16px;
	cursor: pointer;
}

/*
 * The demonstration, whatever shape it was shot in.
 *
 * This was a fixed 16:9 box with `object-fit: cover`, which **crops** anything
 * that is not widescreen — and ymove shoots almost everything portrait. A
 * survey of sixteen of their videos found exactly one landscape. So the frame
 * was cutting the top and bottom off nearly every demonstration in the library:
 * on a squat, the half that matters.
 *
 * `contain` never crops. The wrapper carries the frame and the dark ground, so
 * a portrait clip letterboxes inside it the way it would in any video player
 * rather than being trimmed to fit a shape nobody chose.
 */
.fitpro-app .fitpro-ex__media {
	display: block;
	width: 100%;
	aspect-ratio: 16 / 9;
	object-fit: contain;
	border-radius: var(--fp-radius);
	background: var(--fp-navy);
}

/*
 * A YouTube embed, which is an iframe rather than a video element.
 *
 * `object-fit` means nothing to an iframe -- there is no replaced content to
 * fit -- so the 16:9 frame above is the whole of the sizing, and the border is
 * removed because a browser gives one by default.
 */
.fitpro-app .fitpro-ex__media--embed {
	border: 0;
	background: var(--fp-navy);
}

/*
 * A portrait clip gets a portrait frame.
 *
 * Letterboxing alone would be correct and wasteful: a 9:16 video inside a 16:9
 * box is two thirds black, and the demonstration ends up a thin strip down the
 * middle of the widest element on the screen. The provider reports orientation,
 * so the frame follows it — capped in height, or a phone-shaped video would
 * push the instructions below the fold on the one screen where somebody is
 * reading them before they attempt the movement.
 */
.fitpro-app .fitpro-ex__media--portrait {
	aspect-ratio: 3 / 4;
	max-height: 46vh;
	margin: 0 auto;
	width: auto;
	max-width: 100%;
}

.fitpro-app .fitpro-ex__media--square {
	aspect-ratio: 1 / 1;
	max-height: 46vh;
	margin: 0 auto;
	width: auto;
	max-width: 100%;
}

.fitpro-app .fitpro-ex__media--none {
	display: flex;
	align-items: center;
	justify-content: center;
	color: rgba(255, 255, 255, 0.5);
	font-size: 40px;
}

.fitpro-app .fitpro-ex__tags {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-steps-list {
	margin: 0 0 var(--fp-space-4);
	padding-left: 20px;
}

.fitpro-app .fitpro-steps-list li {
	margin-bottom: var(--fp-space-2);
	line-height: 1.55;
}

.fitpro-app .fitpro-cues {
	margin: 0 0 var(--fp-space-4);
	padding-left: 0;
	list-style: none;
}

.fitpro-app .fitpro-cues li {
	position: relative;
	padding-left: 22px;
	margin-bottom: var(--fp-space-2);
	line-height: 1.55;
}

.fitpro-app .fitpro-cues li::before {
	content: "✦";
	position: absolute;
	left: 0;
	color: var(--fp-accent);
}

.fitpro-app .fitpro-tag {
	padding: 3px 10px;
	border-radius: var(--fp-radius-pill);
	background: var(--fp-accent-soft);
	color: var(--fp-accent);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 0.6px;
	text-transform: uppercase;
}

/* ---------------------------------------------------------------------------
 * Workout builder
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-builder-row {
	margin-bottom: var(--fp-space-3);
	padding: var(--fp-space-4);
}

.fitpro-app .fitpro-builder-row__head {
	display: flex;
	/*
	 * Wraps, because nothing on this row except the title can shrink.
	 *
	 * The number, the superset badge and the four tool buttons are all
	 * `flex: none`, and together they are 324px — so on a 320px phone the row
	 * pushed the **whole document** sideways rather than the title merely
	 * getting narrow. Bug 52 again: a flex item that will not shrink below its
	 * content drags the page with it, and the element that looks wrong is the
	 * container that inherited the width rather than the one causing it.
	 */
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-builder-row__n {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 26px;
	height: 26px;
	border-radius: 50%;
	background: var(--fp-accent-soft);
	color: var(--fp-accent);
	font-family: var(--fp-font-heading);
	font-size: 12px;
	font-weight: 700;
}

.fitpro-app .fitpro-builder-row__title {
	/*
	 * Natural width with a floor, not `flex: 1`.
	 *
	 * Growing to fill the row pushed the muscle pills away from the name and up
	 * against the tools, which reads as two unrelated groups rather than as a
	 * name and what it trains. At natural width they sit right after it.
	 *
	 * The floor is what stops the title being the thing that gives way: it can
	 * shrink and ellipsise, but not past a point where an exercise name
	 * identifies nothing, and the pills wrap to the next line instead.
	 */
	flex: 0 1 auto;
	min-width: 12ch;
	font-weight: 700;
	color: var(--fp-heading);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/*
 * The muscle pills, between the exercise name and the tools.
 *
 * `flex: 0 1 auto` with a wrapping head, so on a wide row they sit beside the
 * name and on a narrow one they drop to their own line rather than crushing the
 * title — which is the thing that identifies the row and must not be the part
 * that gives way.
 */
.fitpro-app .fitpro-builder-row__muscles {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 4px;
	flex: 0 1 auto;
	min-width: 0;
}

/* A divider inside a card, between two things the card holds that are not the
   same kind of decision — the name half and the email half of Your details. A
   bare <hr> takes the browser's inset 3D border, which belongs to 1997. */
.fitpro-app .fitpro-rule {
	margin: var(--fp-space-5) 0 var(--fp-space-4);
	border: 0;
	border-top: 1px solid var(--fp-line);
}

/* --- row controls: reorder, remove ---------------------------------------
 *
 * The buttons at the right of an editable row, wherever one is. Declared once
 * because there were two copies of this and they had drifted — different
 * sizes, different arrow shapes, and only one of them had a hover state on the
 * button that destroys something.
 */

.fitpro-app .fitpro-rowtools {
	display: flex;
	gap: 4px;
}

.fitpro-app .fitpro-rowtools button {
	/*
	 * `inline-flex` with no padding, or the contents are not centred.
	 *
	 * A button defaults to `display: block` here and carries the user agent's
	 * own `padding: 1px 6px`, so an icon child starts 7px from the left edge and
	 * ends 19px from the right — measured, on a 44px square. Text glyphs got
	 * away with it because a centred line box hid the asymmetry; an SVG does
	 * not, and the ▶ sat visibly left of centre.
	 */
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	/*
	 * 36px rather than the 44px tap minimum: these sit inside a card the coach
	 * is already working in, on the desktop-first side of the app, and four
	 * 44px squares in a row crowd everything else off a narrow card. The 44px
	 * rule is for the in-workout logger, which is a phone held at arm's length
	 * mid-set.
	 */
	width: 36px;
	height: 36px;
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-body);
	font-size: 15px;
	cursor: pointer;
}

.fitpro-app .fitpro-rowtools button:disabled {
	opacity: 0.35;
	cursor: not-allowed;
}

.fitpro-app .fitpro-rowtools button.is-danger:hover:not(:disabled) {
	color: var(--fp-danger);
	border-color: var(--fp-danger);
}

.fitpro-app .fitpro-builder-row__tools {
	flex: none;
	/* Stays at the right of whichever line it lands on, wrapped or not. */
	margin-left: auto;
}

.fitpro-app .fitpro-builder-row__fields {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0 var(--fp-space-3);
	/*
	 * Each field sits at the top of its own cell rather than stretching to the
	 * tallest one in the row. Stretching is invisible while every field is the
	 * same height and, the moment one of them carries a hint the others do not,
	 * centres the short boxes against the tall one — which is the other half of
	 * the misalignment the hint's own position was causing.
	 */
	align-items: start;
}

@media (min-width: 700px) {
	.fitpro-app .fitpro-builder-row__fields {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}

/*
 * Half of a superset.
 *
 * A tinted left edge on the card rather than a border all the way round: the
 * pair are adjacent rows, and two full outlines butted together read as one box
 * with a line through it. An edge on each says "these two belong together"
 * without drawing anything between them.
 */
.fitpro-app .fitpro-builder-row.is-superset {
	border-left: 3px solid var(--fp-accent);
}

/*
 * The builder's actions: a column of centred rows.
 *
 * Its own block rather than `.fitpro-actions`, which right-aligns — right is
 * where a form's Save belongs, and these are not a form's Save. They are the
 * controls for the exercise above them, they read as a group, and a group of
 * three sitting hard against the right edge of a wide card reads as leftovers.
 *
 * The wrapper owns the vertical space so the rows inside can sit close together
 * while the block as a whole stays clear of the note field underneath — which
 * it was not: the buttons ended up almost touching the "Note for this exercise"
 * label, so the two looked related and are not.
 */
.fitpro-app .fitpro-builder-row__actions {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-3);
	margin: var(--fp-space-5) 0;
}

.fitpro-app .fitpro-builder-row__actionrow {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--fp-space-2);
}

/*
 * Three buttons do not fit a phone on one line, and wrapping them leaves one
 * stranded and centred under two. Each takes the full width instead — a row of
 * stubs reading "Add anta…" is worse than three legible lines.
 */
@media (max-width: 559px) {
	.fitpro-app .fitpro-builder-row__actionrow > * {
		flex: 1 1 100%;
	}
}

/*
 * Look at an exercise before adding it.
 *
 * Overlaid on the row rather than placed in the flow: the row itself is one big
 * button, and a sibling in the flow would either shrink the target that adds the
 * exercise or sit outside the row and stop reading as part of it.
 */
.fitpro-app .fitpro-picker li {
	position: relative;
}

/*
 * `button.` on purpose. `.fitpro-app .fitpro-picker button` styles the row —
 * full width, 56px tall, its own border — and an element selector outranks two
 * classes, so without the tag here the peek would silently inherit the row's
 * layout and the rule below would read perfectly while doing nothing. Bug 34's
 * shape, and the fourth time in this stylesheet.
 */
.fitpro-app .fitpro-picker button.fitpro-picker__peek {
	position: absolute;
	top: 50%;
	/*
	 * Clear of the row's own "+", not on top of it. The row reserves
	 * `--fp-tap-min` of right padding so the + ends before this starts —
	 * overlapping them made the primary action of the whole sheet, adding the
	 * exercise, land on the preview instead.
	 */
	right: 6px;
	transform: translateY(-50%);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	width: var(--fp-tap-min);
	height: var(--fp-tap-min);
	/* Both reset from the row rule above, which this shares an element with. */
	min-height: 0;
	padding: 0;
	border: 0;
	border-radius: var(--fp-radius);
	background: transparent;
	color: var(--fp-muted);
	cursor: pointer;
}

.fitpro-app .fitpro-picker button.fitpro-picker__peek:hover,
.fitpro-app .fitpro-picker button.fitpro-picker__peek:focus-visible {
	color: var(--fp-accent);
	background: transparent;
}

/* Exercise picker list */
.fitpro-app .fitpro-picker {
	list-style: none;
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-picker button {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	width: 100%;
	min-height: 56px;
	padding: var(--fp-space-3);
	/* Room for the overlaid peek control, or the meta text runs under it. */
	padding-right: calc(var(--fp-space-3) + var(--fp-tap-min));
	border: 0;
	border-bottom: 1px solid var(--fp-line);
	background: none;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-picker button:hover {
	background: var(--fp-accent-soft);
}

.fitpro-app .fitpro-picker__title {
	flex: 1;
	font-weight: 700;
	color: var(--fp-heading);
	font-size: 14px;
}

.fitpro-app .fitpro-picker__meta {
	font-size: 12px;
	color: var(--fp-muted);
	/*
	 * No `text-transform: capitalize`.
	 *
	 * It was doing the casing for a raw provider slug, and doing it wrongly:
	 * `full_body` came out as "Full_body" because the underscore is not a word
	 * break, and anything with a real space became Title Case, which is not the
	 * sentence case the rest of the app uses and mangles the words that stay
	 * lowercase in French, Spanish and Romanian. `humanise()` at the call site
	 * strips the underscores and capitalises the first letter only.
	 */
}

.fitpro-app .fitpro-picker__add {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 30px;
	height: 30px;
	flex: none;
	border-radius: 50%;
	background: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
	font-size: 18px;
	line-height: 1;
}

/* ---------------------------------------------------------------------------
 * Assignments
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-assignment {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-3);
	padding: var(--fp-space-3) 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-assignment:last-child {
	border-bottom: 0;
}

/*
 * The three macros the calculator arrives at.
 *
 * A row each rather than the recipe card's 2×2 grid, because this is the answer
 * to the question the screen was opened to ask and it gets the room: an icon, a
 * figure in grams, and the share of the day's calories it accounts for. The
 * shares are what tell a coach whether the split is the one they meant, so they
 * are right-aligned into a column somebody can read down.
 *
 * `tabular-nums` on the share: three percentages that do not line up read as
 * three unrelated numbers, and the whole point is that they add to a hundred.
 */
.fitpro-app .fitpro-macroplan {
	list-style: none;
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-macroplan li {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-3);
	padding: 10px 0;
	border-bottom: 1px solid var(--fp-line);
	font-size: 15px;
}

.fitpro-app .fitpro-macroplan li:last-child {
	border-bottom: 0;
}

.fitpro-app .fitpro-macroplan__grams {
	/* Takes the slack, so the name sits left and the share sits right whatever
	   the words are in this language. */
	flex: 1;
	text-align: right;
	font-weight: 700;
	color: var(--fp-heading);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-macroplan__share {
	flex: none;
	min-width: 4ch;
	text-align: right;
	color: var(--fp-muted);
	font-variant-numeric: tabular-nums;
}

/*
 * The Assignments section on a client's profile.
 *
 * The rows reuse `.fitpro-assignment` — five lists of the same shape read as
 * one section, and five slightly different shapes read as five features that
 * happen to be next to each other.
 *
 * The end of the row holds a state badge and a control, which is two things and
 * therefore needs a box of its own: without one they are two flex children of
 * the row and `justify-content: space-between` pushes them apart, so the badge
 * drifts into the middle and stops reading as belonging to the button.
 *
 * It wraps at the row level rather than shrinking. Bug 52's family again: a
 * title that will not go below its content plus a badge and a link is more than
 * a 320px card has, and the failure without a wrap is the page sliding sideways
 * rather than this row growing a line.
 */
.fitpro-app .fitpro-assignment {
	flex-wrap: wrap;
}

.fitpro-app .fitpro-assign-row__end {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	margin-left: auto;
}

/*
 * What a row opens: the whole width, under both columns.
 *
 * `flex-basis: 100%` rather than a third flex child, because the row wraps —
 * a detail sized by its content would sit beside the title on a wide screen and
 * below it on a narrow one, which reads as two different layouts.
 */
.fitpro-app .fitpro-assignment__detail {
	flex-basis: 100%;
	/*
	 * A flex item will not shrink below its content (bug 52), and a program's
	 * calendar is a 560px table in its own scroller: without this the scroller
	 * never scrolls and the phone's page is 600px wide instead.
	 */
	min-width: 0;
	margin-top: var(--fp-space-3);
}

/*
 * A refusal, beside the thing refused.
 *
 * "That session has already been done" is about *this* row, and a message at
 * the top of the card would leave the reader working out which of eight rows it
 * meant. Danger rather than muted: the coach pressed something and it did not
 * happen.
 */
.fitpro-app .fitpro-assign-row__error {
	color: var(--fp-danger);
	font-size: 12px;
}

/*
 * The "give them one more" control under each list.
 *
 * Ruled off, because it is a different act from the list above it — reading
 * what somebody has, then adding to it — and without a line the first field
 * reads as another row.
 *
 * Fields side by side above 560px and stacked below, the rule
 * `.fitpro-field-row` already follows, and the button after them on its own
 * line at every width: it is the commit, and a commit that shares a line with
 * the field it commits gets pressed early.
 */
.fitpro-app .fitpro-assign-add {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-2);
	margin-top: var(--fp-space-4);
	padding-top: var(--fp-space-4);
	border-top: 1px solid var(--fp-line);
	align-items: stretch;
}

.fitpro-app .fitpro-assign-add .fitpro-btn {
	align-self: flex-start;
}

@media (min-width: 560px) {
	.fitpro-app .fitpro-assign-add {
		display: grid;
		/*
		 * `minmax(0, …)`, never a bare `fr`. A `1fr` track is
		 * `minmax(auto, 1fr)` and that automatic minimum will not shrink below
		 * the column's content — and the content here is a `<select>` holding a
		 * workout title, which is exactly the trap bug 59 recorded in the
		 * logger. The failure is the card pushing the page sideways rather than
		 * the name ellipsising.
		 */
		grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
		align-items: end;
		gap: var(--fp-space-3);
	}

	.fitpro-app .fitpro-assign-add > * {
		min-width: 0;
	}

	.fitpro-app .fitpro-assign-add .fitpro-btn,
	.fitpro-app .fitpro-assign-add .fitpro-segmented-wrap {
		grid-column: 1 / -1;
	}

	/*
	 * `justify-self`, because a grid item stretches by default and the commit
	 * came out as a full-width bar across the card — which reads as the card's
	 * primary action rather than as the end of one small form inside it. The
	 * flex branch above says the same thing with `align-self`, on the axis that
	 * is the inline one there.
	 */
	.fitpro-app .fitpro-assign-add .fitpro-btn {
		justify-self: start;
	}
}

.fitpro-app .fitpro-assignment__meta {
	display: block;
	font-size: 12px;
	color: var(--fp-muted);
	text-transform: uppercase;
	letter-spacing: 0.8px;
}

/* ---------------------------------------------------------------------------
 * Logger
 *
 * Sized for one-handed use, mid-set, with sweaty hands. Every interactive
 * element here is at or above the 44px tap minimum, and the primary action is
 * always full-width so it can be hit without looking.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-logger {
	padding-bottom: 96px;
}

.fitpro-app .fitpro-logger__bar {
	position: sticky;
	top: var(--fp-topbar-h);
	z-index: 15;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-2);
	flex-wrap: wrap;
	padding: var(--fp-space-2) 0;
	margin-bottom: var(--fp-space-3);
	background: var(--fp-surface);
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-logger__totals {
	font-family: var(--fp-font-heading);
	font-size: 13px;
	font-weight: 600;
	letter-spacing: 1px;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-netstate {
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-success);
}

.fitpro-app .fitpro-netstate--off {
	color: var(--fp-warning);
}

.fitpro-app .fitpro-exblock--done {
	border-color: var(--fp-success);
}

.fitpro-app .fitpro-exblock__target,
.fitpro-app .fitpro-exblock__previous {
	margin: 0 0 var(--fp-space-2);
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-exblock__previous {
	color: var(--fp-accent);
	font-weight: 700;
}

.fitpro-app .fitpro-setlist {
	list-style: none;
	margin: 0 0 var(--fp-space-4);
	padding: 0;
}

.fitpro-app .fitpro-setlist li {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	padding: 8px 0;
	border-bottom: 1px solid var(--fp-line);
	font-size: 14px;
}

/*
 * The effort rating beside a logged set.
 *
 * Quieter than the weight and reps deliberately: those are what the set *was*,
 * this is how it felt, and a reader scanning back through five sets is looking
 * for the numbers first. `nowrap` because "RIR 2" splitting across a line break
 * on a 320px phone reads as two separate values.
 */
/*
 * The expanded detail on an effort-variance notification.
 *
 * Spans the whole row — the card row is a flex line and this has to break out
 * of it, or the answer list is squeezed into whatever the title left over.
 */
/*
 * A notification row may carry an expanded detail, so it has to be able to
 * wrap — and `.fitpro-card-row` is `nowrap`.
 *
 * Scoped to `.fitpro-note` rather than fixed on the shared row class, which a
 * dozen screens use and none of the others need to wrap. Without it the
 * detail's `flex-basis: 100%` simply consumed the line and squeezed the row's
 * body — `flex: 1 1 0%` with `min-width: 0` — to **zero width**: the title was
 * still in the DOM, rendered at 0px, so the row read as having lost its own
 * heading. Measured, because at a glance it looks like the title was never
 * output.
 */
.fitpro-app .fitpro-note {
	flex-wrap: wrap;
}

.fitpro-app .fitpro-variance {
	flex-basis: 100%;
	margin-top: var(--fp-space-3);
	padding-top: var(--fp-space-3);
	border-top: 1px solid var(--fp-line);
}

/*
 * The prescription row: what to do on the left, how to move it on the right.
 *
 * Wraps rather than shrinking, because on a phone the target is what somebody
 * works from and the tempo is a qualifier — pushed onto its own line it is
 * still perfectly readable, where squeezed beside the target it would take the
 * numbers with it.
 */
.fitpro-app .fitpro-exblock__prescription {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: 4px var(--fp-space-3);
}

/*
 * The tempo itself.
 *
 * Small and quiet — it is a qualifier, not a quantity — with the icons in the
 * accent so the four phases read as one marked group rather than as more
 * numbers. `nowrap` inside a phase so a duration never separates from the arrow
 * that says which phase it belongs to, which would silently reassign every
 * value in the row.
 */
.fitpro-app .fitpro-tempo {
	display: inline-flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 2px 8px;
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-tempo__label {
	font-weight: 700;
}

.fitpro-app .fitpro-tempo__phase {
	display: inline-flex;
	align-items: center;
	gap: 3px;
	white-space: nowrap;
}

.fitpro-app .fitpro-tempo__phase .fitpro-icon {
	width: 14px;
	height: 14px;
	color: var(--fp-accent);
}

/*
 * The three answers in the effort prompt.
 *
 * Stacked full width at every size rather than in a row: they are sentences
 * rather than words, and this is read one-handed by somebody who has just
 * finished a hard set. A row of three would truncate all three on a phone and
 * make the difference between them a guess.
 */
.fitpro-app .fitpro-reasons {
	display: grid;
	gap: var(--fp-space-3);
	margin-top: var(--fp-space-4);
}

.fitpro-app .fitpro-setlist__effort {
	margin-left: 8px;
	color: var(--fp-muted);
	font-size: 12px;
	white-space: nowrap;
}

.fitpro-app .fitpro-setlist__n {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	border-radius: 50%;
	background: var(--fp-success);
	color: var(--fp-inverse);
	font-size: 11px;
	font-weight: 700;
	flex: none;
}

.fitpro-app .fitpro-setlist__undo {
	margin-left: auto;
	min-height: 32px;
	padding: 0 12px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius-pill);
	background: none;
	color: var(--fp-muted);
	font-size: 11px;
	font-weight: 700;
	text-transform: uppercase;
	cursor: pointer;
}

/*
 * Weight and reps, side by side — but only where two of them fit.
 *
 * **`1fr` is `minmax(auto, 1fr)`, and that `auto` will not shrink below its
 * content.** Bug 52's lesson in grid rather than flex: the column's min-content
 * is the two 52px buttons plus the `<input>`'s own intrinsic width, which is
 * about twenty characters. Measured on a 375px phone, each column resolved to
 * **338px inside a 293px container** and the document came out 729px wide — the
 * whole logger, and everything else on the page, pushed half a screen sideways.
 *
 * `min-width: 0` on the input is not enough and was already there: it lets the
 * *flex item* shrink, and says nothing about the grid column's automatic
 * minimum. `minmax(0, 1fr)` is what says that.
 *
 * **And fixing the overflow is only half of it.** With two columns on a 375px
 * screen the number field came out 25px wide — no overflow, and no way to read
 * "245" either. The buttons cannot give the space back: 44px is the tap floor
 * everywhere in this app and this is the one screen where that is documented as
 * non-negotiable, because it is operated one-handed, mid-set, by somebody out
 * of breath.
 *
 * So below 560px there is one stepper per row. Measured after: 122px of number
 * at 320px, 177px at 375px, and two columns with 214px each at 753px.
 */
.fitpro-app .fitpro-stepper-row {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-4);
}

/*
 * Immediately after the rule it narrows, never in a media block further down —
 * bugs 34 and 41, both of which were a rule that read correctly where it was
 * written and lost to a later one of equal weight.
 */
@media (max-width: 560px) {
	.fitpro-app .fitpro-stepper-row {
		grid-template-columns: minmax(0, 1fr);
	}
}

/*
 * Effort ratings stay two-up, where weight and reps cannot.
 *
 * RIR and RPE hold one or two characters; weight and reps hold three or four
 * and are what somebody reads at a glance mid-set. Stacking all four put the
 * **Log set button below the fold on a 375px phone** — measured — which on this
 * screen is not a cosmetic complaint: it is a person out of breath scrolling to
 * find the only control that matters.
 *
 * So these get the 44px floor rather than the 52px the main pair use, and a
 * smaller figure. 44 is the documented minimum and this is the screen that
 * minimum was written for, so it does not go below it — which is exactly why
 * they stack after all below 360px, where two 44px pairs genuinely will not fit.
 *
 * **After the 560px block, not inside it.** Media queries add no specificity,
 * so this and the rule above are both two classes and source order is the whole
 * of it — bugs 34 and 41, twice already.
 */
.fitpro-app .fitpro-stepper-row--effort {
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.fitpro-app .fitpro-stepper-row--effort .fitpro-stepper__btn {
	width: var(--fp-tap-min);
	min-height: var(--fp-tap-min);
	font-size: 20px;
}

/*
 * Spans both columns of the effort row, so the sentence reads across rather
 * than being cut in half by the grid it sits in.
 */
.fitpro-app .fitpro-stepper-row__help {
	grid-column: 1 / -1;
	margin: 0;
	font-size: 11px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-stepper-row--effort .fitpro-stepper__value {
	min-height: var(--fp-tap-min);
	font-size: 18px;
}

@media (max-width: 359px) {
	.fitpro-app .fitpro-stepper-row--effort {
		grid-template-columns: minmax(0, 1fr);
	}
}

.fitpro-app .fitpro-stepper__label {
	display: block;
	margin-bottom: 6px;
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1.4px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-stepper__row {
	display: flex;
	align-items: stretch;
	gap: 6px;
}

.fitpro-app .fitpro-stepper__btn {
	width: 52px;
	min-height: 52px;
	flex: none;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-heading);
	font-size: 24px;
	line-height: 1;
	cursor: pointer;

	/* Stops iOS selecting the glyph when tapped repeatedly and quickly. */
	user-select: none;
	-webkit-user-select: none;
	touch-action: manipulation;
}

.fitpro-app .fitpro-stepper__btn:active {
	background: var(--fp-accent-soft);
	border-color: var(--fp-accent);
}

.fitpro-app .fitpro-stepper__value {
	flex: 1;
	min-width: 0;
	min-height: 52px;
	padding: 0 4px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	font-family: var(--fp-font-heading);
	font-size: 22px;
	font-weight: 600;
	text-align: center;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-stepper__suffix {
	display: block;
	margin-top: 4px;
	font-size: 11px;
	color: var(--fp-muted);
	text-align: center;
}

/*
 * The same stepper in a dense list.
 *
 * Written immediately after the rules it narrows, never in a block further
 * down -- bugs 34 and 41, both of which were a rule that read correctly where
 * it was written and lost to a later one of equal weight.
 *
 * 40px rather than 52. The documented 44px floor is for the **in-workout
 * logger** -- one-handed, mid-set, out of breath -- and this is a review card
 * somebody is reading before pressing Save, where the worst a mis-tap does is
 * change a number they are looking straight at and can press back.
 */
.fitpro-app .fitpro-stepper--compact .fitpro-stepper__btn {
	width: 40px;
	min-height: 40px;
	font-size: 20px;
}

.fitpro-app .fitpro-stepper--compact .fitpro-stepper__value {
	min-height: 40px;
	font-size: 17px;
}

/* Rest timer: pinned above the tab bar, never covering the log button. */
/*
 * The countdown for a timed set.
 *
 * In the flow of the card rather than fixed to the bottom like the rest timer,
 * and that is the whole difference between them: a rest happens between
 * exercises and has to stay reachable while the client scrolls, where a hold
 * belongs to the set they are looking at and following them around the screen
 * would be one more thing over the Log button.
 */
.fitpro-app .fitpro-hold {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-3);
	padding: var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
}

/* Running, it is the thing on the card worth looking at. */
.fitpro-app .fitpro-hold.is-running {
	border-color: var(--fp-accent);
	background: color-mix(in srgb, var(--fp-accent) 8%, var(--fp-card));
}

.fitpro-app .fitpro-hold__label {
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--fp-body);
}

/* The rest timer's own pill, on a light surface instead of the navy bar. */
.fitpro-app .fitpro-hold__go {
	min-height: var(--fp-tap-min);
	padding: 0 20px;
	border: 1px solid var(--fp-accent);
	border-radius: var(--fp-radius-pill);
	background: none;
	color: var(--fp-accent);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	white-space: nowrap;
	cursor: pointer;
}

.fitpro-app .fitpro-hold__go:hover {
	background: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
}

.fitpro-app .fitpro-hold__clock {
	font-family: var(--fp-font-heading);
	font-size: 28px;
	font-weight: 700;
	line-height: 1;
	color: var(--fp-heading);

	/* Tabular figures stop the count jittering as digits change width. */
	font-variant-numeric: tabular-nums;
}

@media (max-width: 380px) {
	.fitpro-app .fitpro-hold {
		gap: var(--fp-space-2);
	}

	.fitpro-app .fitpro-hold__label {
		display: none;
	}
}

.fitpro-app .fitpro-rest {
	position: fixed;
	left: 0;
	right: 0;
	bottom: calc(var(--fp-tabbar-h) + env(safe-area-inset-bottom));
	z-index: 40;
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--fp-space-4);
	padding: var(--fp-space-3);
	background: var(--fp-navy);
	color: var(--fp-inverse);
}

.fitpro-app .fitpro-rest__label {
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 2px;
	text-transform: uppercase;
	opacity: 0.8;
}

.fitpro-app .fitpro-rest__clock {
	font-family: var(--fp-font-heading);
	font-size: 28px;
	font-weight: 700;
	line-height: 1;

	/* Tabular figures stop the timer jittering as digits change width. */
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-rest__skip {
	min-height: 36px;
	padding: 0 16px;
	border: 1px solid rgba(255, 255, 255, 0.4);
	border-radius: var(--fp-radius-pill);
	background: none;
	color: var(--fp-inverse);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	cursor: pointer;
}

.fitpro-app .fitpro-rest__actions {
	display: inline-flex;
	gap: var(--fp-space-2);
}

/*
 * A 320px phone has room for the clock and two buttons only if the label and
 * the tracking give way; the clock is what somebody glances at.
 */
@media (max-width: 400px) {
	.fitpro-app .fitpro-rest {
		gap: var(--fp-space-2);
	}

	.fitpro-app .fitpro-rest__label {
		display: none;
	}

	.fitpro-app .fitpro-rest__skip {
		padding: 0 12px;
		letter-spacing: 1px;
	}
}

@media (min-width: 980px) {
	.fitpro-app .fitpro-rest {
		bottom: 0;
		left: 200px;
	}
}

/*
 * Adjust timer: three rests as circles, then which rests the change covers.
 *
 * Circles at 76px, above the 44px floor the logger keeps, because this is
 * chosen mid-rest by somebody who has just put a bar down.
 */
.fitpro-app .fitpro-restpresets {
	display: flex;
	justify-content: center;
	gap: var(--fp-space-4);
	margin: var(--fp-space-4) 0;
}

.fitpro-app .fitpro-restpreset {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 76px;
	height: 76px;
	border: 2px solid var(--fp-accent);
	border-radius: 50%;
	background: color-mix(in srgb, var(--fp-accent) 8%, transparent);
	font-family: var(--fp-font-heading);
	font-size: 22px;
	font-weight: 700;
	color: var(--fp-heading);
	cursor: pointer;
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-restpreset:hover,
.fitpro-app .fitpro-restpreset:focus-visible {
	background: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
	outline: none;
}

.fitpro-app .fitpro-restscopes {
	display: grid;
	gap: var(--fp-space-2);
	margin: var(--fp-space-3) 0;
}

.fitpro-app .fitpro-restscope {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 2px;
	min-height: var(--fp-tap-min);
	padding: 12px var(--fp-space-4);
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-restscope:hover,
.fitpro-app .fitpro-restscope:focus-visible {
	border-color: var(--fp-accent);
	outline: none;
}

.fitpro-app .fitpro-restscope__label {
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-restscope__help {
	font-size: 13px;
	color: var(--fp-body);
}

/* ---------------------------------------------------------------------------
 * Nutrition
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-rings {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	justify-content: center;
	gap: var(--fp-space-4);
}

/*
 * The ring is now just the circle and what sits inside it.
 *
 * The label used to live in here too, which is why the numbers were positioned
 * against a box that included it -- `bottom: 18px`, reserving the label's line.
 * With the label moved out to the wrapper the inner simply fills the svg, and
 * "dead centre" needs no allowance for anything.
 */
.fitpro-app .fitpro-ring {
	position: relative;
	display: flex;
	line-height: 0;
}

.fitpro-app .fitpro-ring__inner {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 2px;
	line-height: 1;
	pointer-events: none;
}

/* The ring with its caption underneath. */
.fitpro-app .fitpro-ring-wrap {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 4px;
}

.fitpro-app .fitpro-ring__value {
	font-family: var(--fp-font-heading);
	font-size: 20px;
	font-weight: 700;
	line-height: 1;
	color: var(--fp-heading);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-ring__target {
	font-size: 11px;
	color: var(--fp-muted);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-ring__label {
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1.2px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-remaining {
	margin: var(--fp-space-4) 0 0;
	text-align: center;
	font-family: var(--fp-font-heading);
	font-size: 14px;
	font-weight: 600;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	color: var(--fp-accent);
}

.fitpro-app .fitpro-items {
	list-style: none;
	margin: var(--fp-space-3) 0;
	padding: 0;
}

.fitpro-app .fitpro-items li {
	padding: var(--fp-space-3) 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-items__head {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
	margin-bottom: var(--fp-space-2);
}

.fitpro-app .fitpro-items__name {
	flex: 1;
	min-width: 0;
	font-weight: 700;
	color: var(--fp-heading);
	font-size: 14px;
}

.fitpro-app .fitpro-items__flag {
	padding: 2px 8px;
	border-radius: var(--fp-radius-pill);
	background: color-mix(in srgb, var(--fp-warning) 18%, transparent);
	color: var(--fp-warning);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
}

.fitpro-app .fitpro-items__head button {
	width: 32px;
	height: 32px;
	flex: none;
	border: 0;
	border-radius: 50%;
	background: var(--fp-surface);
	color: var(--fp-muted);
	cursor: pointer;
}

/*
 * Which day the diary is showing.
 *
 * Above every card rather than inside one, because it governs all of them --
 * the rings, the log form and the list are each about the selected day, and a
 * control living inside one of them would read as belonging to that one.
 *
 * `minmax(0, ...)` nowhere here because it is flex, but the date input still
 * needs `min-width: 0`: a native date control reports a wide intrinsic width
 * (it is reserving room for a locale's longest format plus a calendar button)
 * and without it the row pushed the page sideways at 320px. Bug 52's family.
 */
.fitpro-app .fitpro-daybar {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2);
	margin-bottom: var(--fp-space-4);
}

.fitpro-app .fitpro-daybar__step {
	width: var(--fp-tap-min);
	min-height: var(--fp-tap-min);
	flex: none;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-heading);
	font-size: 22px;
	line-height: 1;
	cursor: pointer;
	user-select: none;
	-webkit-user-select: none;
}

.fitpro-app .fitpro-daybar__step:disabled {
	opacity: 0.4;
	cursor: default;
}

/*
 * A basis rather than a bare `flex: 1`.
 *
 * A native date control draws its own text and calendar button and reports no
 * overflow when it runs out of room -- `scrollWidth` equals `clientWidth`
 * whatever happens -- so it simply renders "2026-0" and nothing in the DOM
 * says so. Measured at 320px it came out 86px wide and clipped mid-year. The
 * basis is what reserves a readable width, and the wrap on the row above is
 * what lets the button beside it drop to its own line instead of taking it.
 */
.fitpro-app .fitpro-daybar__date {
	flex: 1 1 130px;
	min-width: 0;
	min-height: var(--fp-tap-min);
	padding: 0 var(--fp-space-3);
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-heading);
	font-family: var(--fp-font-body);
	font-size: 14px;
	text-align: center;
}

.fitpro-app .fitpro-daybar__today {
	flex: none;
	min-height: var(--fp-tap-min);
	padding: 0 var(--fp-space-3);
	border: 1px solid var(--fp-accent);
	border-radius: var(--fp-radius-pill);
	background: none;
	color: var(--fp-accent);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	cursor: pointer;
}

/* The maker, under the food rather than welded onto the end of its name. */
.fitpro-app .fitpro-items__brand {
	margin: -2px 0 0;
	font-size: 12px;
	font-weight: 400;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-items__serving {
	margin: var(--fp-space-1) 0 0;
	font-size: 12px;
	color: var(--fp-muted);
	font-variant-numeric: tabular-nums;
}

/*
 * Servings beside grams.
 *
 * `minmax(0, 1fr)`, never a bare `1fr` -- bug 59, and this column holds a
 * stepper, which is exactly the content whose automatic minimum (two buttons
 * plus an input's intrinsic width) pushed the whole document sideways last
 * time.
 */
.fitpro-app .fitpro-items__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
	gap: var(--fp-space-3);
	align-items: end;
	margin-top: var(--fp-space-2);
}

/* Nothing to count, so the grams box keeps its own width instead of half the row. */
.fitpro-app .fitpro-items__grid--one {
	grid-template-columns: minmax(0, 140px);
}

.fitpro-app .fitpro-items__grid .fitpro-field {
	margin-bottom: 0;
}

.fitpro-app .fitpro-items__macros {
	margin-top: var(--fp-space-2);
}

.fitpro-app .fitpro-pending-total {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2) var(--fp-space-3);
	margin: var(--fp-space-3) 0 0;
	padding-top: var(--fp-space-3);
	border-top: 2px solid var(--fp-line);
	font-family: var(--fp-font-heading);
	font-size: 14px;
	font-weight: 600;
	letter-spacing: 1px;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-pending-total__label {
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1.4px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

/*
 * The viewfinder.
 *
 * Taller than it was: 4:3 on a phone held upright is a letterbox, and a
 * barcode fills a wide box only when it is held close enough that the lens
 * cannot focus on it. 3:4 gives somebody room to stand back, which is the
 * single biggest thing that makes one of these work.
 */
.fitpro-app .fitpro-scanner__frame {
	position: relative;
	margin-bottom: var(--fp-space-3);

	/*
	 * Clips the dimming the aim box casts. That shadow is deliberately larger
	 * than the viewport so it covers the whole picture whatever shape the
	 * camera hands back; without this it covers the sheet as well.
	 */
	overflow: hidden;
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-scanner {
	display: block;
	width: 100%;
	aspect-ratio: 3 / 4;
	max-height: 52vh;
	object-fit: cover;
	border-radius: var(--fp-radius);
	background: var(--fp-navy);
}

@media (min-width: 620px) {
	/* A laptop webcam is landscape and there is no holding it up to a tin. */
	.fitpro-app .fitpro-scanner {
		aspect-ratio: 4 / 3;
	}
}

/*
 * Somewhere to aim, and nothing more: the detector reads the whole frame, so
 * this is a hint rather than a crop. Without one people hold the packet right
 * against the lens, which is the one distance a phone camera cannot focus at.
 */
.fitpro-app .fitpro-scanner__aim {
	position: absolute;
	top: 50%;
	left: 8%;
	right: 8%;
	height: 28%;
	transform: translateY(-50%);
	border: 2px solid var(--fp-accent);
	border-radius: var(--fp-radius);
	box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.25);
	pointer-events: none;
}

/* ---------------------------------------------------------------------------
 * Plans
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-plan {
	display: flex;
	flex-direction: column;
	gap: 4px;
}

.fitpro-app .fitpro-plan--current {
	border-color: var(--fp-accent);
	box-shadow: 0 0 0 1px var(--fp-accent) inset, var(--fp-shadow);
}

.fitpro-app .fitpro-plan__name {
	font-family: var(--fp-font-heading);
	font-size: 20px;
	font-weight: 700;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--fp-heading);
	margin: 0;
}

.fitpro-app .fitpro-plan__price {
	font-family: var(--fp-font-heading);
	font-size: 26px;
	font-weight: 700;
	color: var(--fp-accent);
	margin: 0 0 var(--fp-space-3);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-plan .fitpro-btn {
	margin-top: auto;
}

/* ---------------------------------------------------------------------------
 * Segmented control
 * ------------------------------------------------------------------------ */

/*
 * Three boxes, and each one earns its place.
 *
 *   .fitpro-segmented-wrap   full width, centres what is inside it
 *   .fitpro-segmented-rail   shrink-to-fit, the positioning context for carets
 *   .fitpro-segmented        the track itself, and the thing that scrolls
 *
 * The control used to be a single full-width box, so four short words sat
 * bunched against the left edge of a 1440px screen with a metre of empty track
 * to their right — which reads as a layout that failed rather than as a
 * control. It is now sized by its segments and centred, and grows the same
 * carets the tab bar uses once they stop fitting.
 *
 * Centring belongs on the wrapper, not on the scroller. `justify-content:
 * center` on an overflowing scroll container pushes its content past *both*
 * edges and the left part becomes unreachable, because `scrollLeft` cannot go
 * negative — the trap already recorded on the tab rail, which solves it with
 * auto margins because it has no wrapper to centre it. Here there is one, so
 * the rail simply stops growing at 100% and the wrapper stops centring it.
 */
.fitpro-app .fitpro-segmented-wrap {
	display: flex;
	justify-content: center;
	margin-bottom: var(--fp-space-4);
}

.fitpro-app .fitpro-segmented-rail {
	position: relative;
	display: flex;
	/* Shrink-to-fit up to the width available, never past it. */
	min-width: 0;
	max-width: 100%;
	/* Positioning context for the overlaid carets. */
	isolation: isolate;
	border-radius: var(--fp-radius-pill);
}

.fitpro-app .fitpro-segmented {
	display: flex;
	gap: 4px;
	padding: 4px;
	margin-bottom: var(--fp-space-4);
	border-radius: var(--fp-radius-pill);
	background: var(--fp-line);
	/* Same reasoning as the tab rail: four segments do not fit 375px, and the
	   control must scroll rather than widen the page. */
	overflow-x: auto;
	overflow-y: hidden;
	scrollbar-width: none;
	-webkit-overflow-scrolling: touch;
	overscroll-behavior-x: contain;
}

/* Inside a wrapper the spacing belongs to the wrapper, so the two cannot add
   up to forty pixels of gap that nobody asked for. */
.fitpro-app .fitpro-segmented-wrap .fitpro-segmented {
	margin-bottom: 0;
	min-width: 0;
}

.fitpro-app .fitpro-segmented::-webkit-scrollbar {
	display: none;
}

/*
 * Scroll hints, overlaid on the ends of the track.
 *
 * Overlaid rather than placed beside it, for the reason the tab bar records: in
 * the flow they change the rail's width as they appear, which moves the very
 * element `scrollIntoView` was aiming at and lets the measurement chase its own
 * tail. The gradient is the track colour, not the card colour — these sit on
 * the pill, not on the page.
 */
.fitpro-app .fitpro-segmented__nudge {
	position: absolute;
	top: 0;
	bottom: 0;
	z-index: 1;
	width: 34px;
	display: flex;
	align-items: center;
	padding: 0;
	border: 0;
	color: var(--fp-heading);
	cursor: pointer;
}

.fitpro-app .fitpro-segmented__nudge--left {
	left: 0;
	justify-content: flex-start;
	padding-left: 6px;
	border-radius: var(--fp-radius-pill) 0 0 var(--fp-radius-pill);
	background: linear-gradient(to right, var(--fp-line) 55%, transparent);
}

.fitpro-app .fitpro-segmented__nudge--right {
	right: 0;
	justify-content: flex-end;
	padding-right: 6px;
	border-radius: 0 var(--fp-radius-pill) var(--fp-radius-pill) 0;
	background: linear-gradient(to left, var(--fp-line) 55%, transparent);
}

/* An author `display` beats the user-agent's `[hidden] { display: none }`, so
   without this both carets would sit there permanently — including a left one
   pointing at nothing. Bug 23's neighbour, in a second place. */
.fitpro-app .fitpro-segmented__nudge[hidden] {
	display: none;
}

.fitpro-app .fitpro-segmented__nudge svg {
	fill: currentColor;
}

/*
 * Never shrink, on the child rather than on `.fitpro-segmented__item`.
 *
 * The item class lost to `.fitpro-segmented button` — one class versus two
 * classes and an element — so the "must not shrink" guarantee was not in force
 * wherever it was written, and four labels squeezed into unreadable stubs
 * instead of scrolling. Bug 34, in a second place: a rule that reads correctly
 * on its own but is outranked somewhere else in the file.
 */
.fitpro-app .fitpro-segmented > * {
	flex: 0 0 auto;
	white-space: nowrap;
}

.fitpro-app .fitpro-segmented button {
	min-height: var(--fp-tap-min);
	padding: 0 18px;
	border: 0;
	border-radius: var(--fp-radius-pill);
	background: none;
	color: var(--fp-body);
	font-family: var(--fp-font-body);
	font-size: 12px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	cursor: pointer;
	transition: background 0.12s ease, color 0.12s ease;
}

/*
 * Three ways of saying "this one is selected", all of them styled.
 *
 * The screens disagreed: Workouts sets `.is-active`, while Schedule, both
 * Nutrition screens and Booking set `aria-pressed`. Only the first was ever
 * styled, so on four of the five screens **no segment ever looked selected** —
 * the control rendered as a row of identical grey words and the reader had to
 * infer the current view from the content below it.
 *
 * Fixed here rather than by editing five call sites, because the next screen
 * will pick whichever convention its author remembers. `aria-selected` is
 * included for the same reason: it is what `role="tab"` actually wants, and a
 * future tablist should not be a sixth thing to fix.
 */
.fitpro-app .fitpro-segmented button:hover {
	color: var(--fp-heading);
	background: rgba(255, 255, 255, 0.55);
}

.fitpro-app .fitpro-segmented button.is-active,
.fitpro-app .fitpro-segmented button[aria-pressed="true"],
.fitpro-app .fitpro-segmented button[aria-selected="true"] {
	background: var(--fp-card);
	color: var(--fp-accent);
	box-shadow: var(--fp-shadow);
}

/* A segment drawn as an icon: square-ish, the glyph centred. */
.fitpro-app .fitpro-segmented button.fitpro-segmented__icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0 14px;
}

/* The selected segment is already where you are; hover must not imply
   otherwise by lightening it back towards the unselected colour. */
.fitpro-app .fitpro-segmented button.is-active:hover,
.fitpro-app .fitpro-segmented button[aria-pressed="true"]:hover,
.fitpro-app .fitpro-segmented button[aria-selected="true"]:hover {
	background: var(--fp-card);
	color: var(--fp-accent);
}

/* ---------------------------------------------------------------------------
 * Pro mode / Client mode
 *
 * Deliberately *after* the segmented control, not up with the top bar where it
 * used to live. It extends `.fitpro-segmented`, and both blocks are two classes
 * deep, so the one further down the file wins — which meant the segmented
 * control's own `margin-bottom: 20px` beat the switch's `margin-bottom: 0`. In
 * a row aligned on `center` a bottom margin is centred along with the box, so
 * the control rendered ten pixels high in the header and read as top-aligned.
 * Nothing errors; it just sits wrong. Bug 34's family: a rule that is correct
 * where it is written and outranked somewhere else in the file.
 * ------------------------------------------------------------------------ */

/*
 * The mode switch, on the navy bar.
 *
 * It reuses `.fitpro-segmented` so "which mode" reads exactly like "which tab"
 * everywhere else, and only the colours are restated — the track has to sit on
 * navy rather than on the page background, and the pale grey it normally uses
 * would disappear against it.
 */
.fitpro-app .fitpro-modeswitch {
	margin-bottom: 0;
	padding: 3px;
	background: rgba(255, 255, 255, 0.12);
	/* It sits in a 56px bar beside a brand and a sign-out link; it must give way
	   rather than push them off the end. */
	max-width: 100%;
}

.fitpro-app .fitpro-modeswitch a,
.fitpro-app .fitpro-modeswitch button {
	display: inline-flex;
	align-items: center;
	/* Shorter than the 44px floor on purpose: this is a bar 56px tall, and the
	   control is two words a coach uses occasionally, not a primary action. */
	min-height: 30px;
	padding: 0 12px;
	border-radius: var(--fp-radius-pill);
	color: rgba(255, 255, 255, 0.75);
	font-family: var(--fp-font-body);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1.2px;
	text-transform: uppercase;
	text-decoration: none;
	white-space: nowrap;
	transition: background 0.12s ease, color 0.12s ease;
}

.fitpro-app .fitpro-modeswitch a:hover,
.fitpro-app .fitpro-modeswitch button:hover:not(:disabled) {
	color: var(--fp-inverse);
	background: rgba(255, 255, 255, 0.14);
}

.fitpro-app .fitpro-modeswitch a.is-active,
.fitpro-app .fitpro-modeswitch button.is-active {
	background: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
	box-shadow: none;
}

/*
 * Half of this control switches accounts rather than following a link, so that
 * half is a real <button> — and every rule above was written against `a` alone,
 * including the one inside the 560px media query. Same shape as bug 34: rules
 * that read perfectly and simply never reach the element that arrived later.
 */
.fitpro-app .fitpro-modeswitch button {
	border: 0;
	background: none;
	cursor: pointer;
	/*
	 * No `font: inherit`.
	 *
	 * It was here to stop a <button> using the user agent's own font — and it
	 * reset the family, size, weight and letter-spacing the shared rule above
	 * had just set, so "Client mode" rendered in a visibly different face from
	 * "Pro mode" beside it. The shared rule already states all four; the button
	 * needs nothing but its chrome removed.
	 */
}

.fitpro-app .fitpro-modeswitch button:disabled {
	opacity: 0.6;
	cursor: wait;
}

.fitpro-app .fitpro-modeswitch__short {
	display: none;
}

/*
 * Below 560px the bar has to hold a brand, two modes and a sign-out link. The
 * word "mode" goes first — it says nothing the lit pill does not — and the
 * brand's text follows, leaving its orange mark as the anchor. Measured: at
 * 320px the long labels clipped "Client mode" mid-word and squeezed the brand
 * to "JP…", which is worse than showing neither.
 *
 * The long label stays in the accessible name; only the visual text is
 * swapped, so a screen reader still hears "Client mode".
 */
@media (max-width: 560px) {
	.fitpro-app .fitpro-modeswitch a,
	.fitpro-app .fitpro-modeswitch button {
		padding: 0 10px;
		letter-spacing: 0.6px;
	}

	.fitpro-app .fitpro-modeswitch__long {
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	.fitpro-app .fitpro-modeswitch__short {
		display: inline;
	}

	/*
	 * The name fits after all, at type sized for the space it has.
	 *
	 * It used to be visually hidden here, which was measured against the wrong
	 * constraint: at 15px with 3px of tracking the whole string is 158px and
	 * there are 155px between the mark and the mode switch on a 375px screen.
	 * Three pixels, and the answer was to remove the name of the business.
	 *
	 * 12px at 1px of tracking is about 105px, which leaves room at 375 and
	 * ellipsises at 320 — and "JPJR FITNESS…" says whose product this is where
	 * an empty bar does not.
	 */
	.fitpro-app .fitpro-topbar__brand {
		font-size: 12px;
		/*
		 * Half a pixel rather than one. At 1px this string is 104px against the
		 * 100px a 320px screen leaves, so it ellipsised on the narrowest phone
		 * for the sake of four pixels — the same four-pixel reasoning that had
		 * hidden it altogether. 0.5 measures 96px and nothing is cut.
		 */
		letter-spacing: 0.5px;
		/* Takes the squeeze before the actions do; the mode switch and the way
		   out are controls and must not shrink. */
		min-width: 0;
	}

	/*
	 * The mode switch sits in the middle of the bar, not beside the way out.
	 *
	 * With `space-between` it rode wherever the brand and the sign-out left it —
	 * a few pixels right of centre on a 375px screen, which reads as a layout
	 * that did not quite work rather than as a decision. On a desktop the bar is
	 * wide enough that off-centre is simply "on the right", which is fine.
	 *
	 * Three columns, and `minmax(0, 1fr)` either side rather than `1fr`. A bare
	 * `1fr` is `minmax(auto, 1fr)`, so the side holding the name would grow to
	 * the name's width and push the switch off centre by half the difference —
	 * bug 59's lesson, which is about exactly this automatic minimum. Equal
	 * zero-floored columns keep the middle one centred whatever is either side,
	 * and the name's own ellipsis handles it if it ever runs out of room.
	 *
	 * `display: contents` on the actions wrapper lets the switch and the sign-out
	 * become columns of the bar directly. It is a plain `div` with no role, so
	 * nothing is lost from the accessibility tree by dropping its box.
	 */
	.fitpro-app .fitpro-topbar {
		display: grid;
		grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
	}

	.fitpro-app .fitpro-topbar__actions {
		display: contents;
	}

	.fitpro-app .fitpro-topbar__brand {
		grid-column: 1;
		justify-self: start;
	}

	.fitpro-app .fitpro-topbar .fitpro-modeswitch {
		grid-column: 2;
		justify-self: center;
	}

	.fitpro-app .fitpro-topbar .fitpro-signout {
		grid-column: 3;
		justify-self: end;
	}

	/*
	 * With a logo it takes the place of both the words and the accent bar. The
	 * bar is the stand-in for a mark on an install that has none, and two marks
	 * in a 375px bar is one too many.
	 */
	.fitpro-app .fitpro-topbar__brand--logo .fitpro-topbar__logo {
		display: block;
	}

	.fitpro-app .fitpro-topbar__brand--logo .fitpro-topbar__mark,
	.fitpro-app .fitpro-topbar__brand--logo .fitpro-topbar__title {
		display: none;
	}
}

/* ---------------------------------------------------------------------------
 * The set plan, in the client's logger
 *
 * Shown only when the sets actually differ from each other. Three sets of ten
 * says everything in one line; a ramp does not, and until now a coach had to
 * write it in the notes and hope it was read.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-setplan {
	display: flex;
	flex-direction: column;
	gap: 4px;
	margin: 0 0 var(--fp-space-3);
	padding: 0;
	list-style: none;
}

.fitpro-app .fitpro-setplan li {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	padding: 7px var(--fp-space-3);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
	font-size: 13px;
	color: var(--fp-body);
}

/*
 * A finished set is struck through rather than removed.
 *
 * Removing it would renumber everything below and make the list jump under a
 * thumb mid-session; keeping it is also how somebody checks what they just did
 * without leaving the screen.
 */
.fitpro-app .fitpro-setplan li.is-done {
	color: var(--fp-muted);
	text-decoration: line-through;
	text-decoration-color: var(--fp-line);
}

.fitpro-app .fitpro-setplan__n {
	display: flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 24px;
	height: 24px;
	border-radius: 50%;
	background: var(--fp-line);
	color: var(--fp-heading);
	font-size: 11px;
	font-weight: 800;
}

.fitpro-app .fitpro-setplan li.is-done .fitpro-setplan__n {
	background: var(--fp-accent-soft);
	color: var(--fp-accent);
}

.fitpro-app .fitpro-setplan__target {
	font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
 * Set-by-set prescription, in the builder
 *
 * A CSS grid rather than a table, for the reason the attention scorecard
 * records: the column count has to be a media-query decision, and a `<table>`
 * cannot change how many columns it has.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-setgrid {
	margin-top: var(--fp-space-3);
}

.fitpro-app .fitpro-setgrid__head,
.fitpro-app .fitpro-setgrid__row {
	display: grid;
	/* Set number, reps, weight, RIR, RPE, remove. Weight gets the widest
	   column because it is the one that carries decimals. */
	grid-template-columns: 44px 1fr 1.3fr 1fr 1fr 36px;
	gap: var(--fp-space-2);
	align-items: center;
}

.fitpro-app .fitpro-setgrid__head {
	margin-bottom: 6px;
	padding: 0 2px;
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-setgrid__row {
	margin-bottom: 6px;
}

.fitpro-app .fitpro-setgrid__row .fitpro-input {
	/* The rows are dense on purpose — five of them is a normal exercise and the
	   coach is comparing down a column, not reading across. */
	min-height: 38px;
	padding: 6px 8px;
	font-size: 13px;
	text-align: center;
}

/*
 * The set number is the warm-up toggle.
 *
 * A checkbox in a sixth column would cost more width than the whole rest of the
 * row can spare on a laptop, and a warm-up set is exactly the thing a coach
 * marks by pointing at the number. It says which it is rather than relying on
 * the tint alone, because a tint is not a label.
 */
.fitpro-app .fitpro-setgrid__n {
	display: flex;
	align-items: center;
	justify-content: center;
	height: 38px;
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
	color: var(--fp-muted);
	font-family: var(--fp-font-body);
	font-size: 12px;
	font-weight: 800;
	cursor: pointer;
}

.fitpro-app .fitpro-setgrid__row.is-warmup .fitpro-setgrid__n {
	border-color: var(--fp-accent);
	background: var(--fp-accent-soft);
	color: var(--fp-accent);
}

/* A warm-up is greyed rather than hidden: it is part of the prescription, and
   it is excluded from volume, so it should read as different. */
.fitpro-app .fitpro-setgrid__row.is-warmup .fitpro-input {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-setgrid__remove {
	/* Centred the same way the row controls are, and for the same reason: an SVG
	   child in a default `display: block` button inherits the user agent's own
	   asymmetric padding and sits left of centre. */
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	height: 38px;
	border: 0;
	border-radius: var(--fp-radius);
	background: none;
	color: var(--fp-muted);
	cursor: pointer;
}

.fitpro-app .fitpro-setgrid__remove:hover:not([disabled]) {
	background: var(--fp-danger-soft);
	color: var(--fp-danger);
}

.fitpro-app .fitpro-setgrid__remove[disabled] {
	opacity: 0.35;
	cursor: default;
}

/*
 * Below 560px the two effort columns go.
 *
 * Six columns in 288px is 48px each, which is narrower than the numbers in
 * them. RIR and RPE are the two a coach is least likely to be editing on a
 * phone — building a session is desk work — so they are the ones that give way,
 * and the header row loses them in step.
 */
@media (max-width: 560px) {
	.fitpro-app .fitpro-setgrid__head,
	.fitpro-app .fitpro-setgrid__row {
		grid-template-columns: 40px 1fr 1.3fr 32px;
	}

	.fitpro-app .fitpro-setgrid__head span:nth-child(4),
	.fitpro-app .fitpro-setgrid__head span:nth-child(5),
	.fitpro-app .fitpro-setgrid__row .fitpro-input:nth-of-type(3),
	.fitpro-app .fitpro-setgrid__row .fitpro-input:nth-of-type(4) {
		display: none;
	}
}

/* ---------------------------------------------------------------------------
 * The exercise block in the client's logger
 *
 * This is read one-handed, mid-set, by somebody who is out of breath. Every
 * decision here follows from that: the tap target is the whole head rather than
 * a small icon, the thumbnail is big enough to recognise a movement from across
 * a rack, and the coach's note is styled to be noticed rather than to be tidy.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-exblock__head {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	width: 100%;
	/* Pulled out to the card's own padding so the row reads as the card's
	   header rather than as a control sitting inside it. */
	margin: calc(var(--fp-space-4) * -1) calc(var(--fp-space-4) * -1) var(--fp-space-3);
	padding: var(--fp-space-3) var(--fp-space-4);
	width: calc(100% + var(--fp-space-4) * 2);
	border: 0;
	border-bottom: 1px solid var(--fp-line);
	border-radius: var(--fp-radius-lg) var(--fp-radius-lg) 0 0;
	background: none;
	text-align: left;
	cursor: pointer;
	transition: background 0.12s ease;
}

/*
 * Swapping an exercise (`SwapExercise.jsx`): an icon with a small label under
 * it, at the top right of the exercise, quiet next to logging the set. The
 * card is its positioning context and the head leaves it room.
 */
.fitpro-app .fitpro-exblock {
	position: relative;
}

.fitpro-app .fitpro-exblock--swappable .fitpro-exblock__head {
	padding-right: 92px;
}

.fitpro-app .fitpro-swapbtn {
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 2px;
	min-width: 44px;
	min-height: 44px;
	padding: 4px 6px;
	border: 0;
	border-radius: var(--fp-radius);
	background: none;
	color: var(--fp-muted);
	cursor: pointer;
	font: inherit;
}

.fitpro-app .fitpro-exblock > .fitpro-swapbtn {
	position: absolute;
	top: var(--fp-space-2);
	right: var(--fp-space-2);
	z-index: 1;
}

.fitpro-app .fitpro-swapbtn:hover,
.fitpro-app .fitpro-swapbtn:focus-visible {
	color: var(--fp-accent);
	background: var(--fp-surface);
}

.fitpro-app .fitpro-swapbtn__label {
	font-size: 11px;
	line-height: 1.2;
	white-space: nowrap;
}

/* Beside the icon, where under it does not fit: a short row in the preview list. */
.fitpro-app .fitpro-preview__row--swappable {
	display: flex;
	align-items: flex-start;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-preview__row--swappable .fitpro-preview__open {
	flex: 1 1 auto;
	min-width: 0;
}

.fitpro-app .fitpro-preview__row--swappable .fitpro-swapbtn {
	flex: none;
}

.fitpro-app .fitpro-swapped {
	display: block;
	font-size: 12px;
	font-weight: 400;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-standing-swaps {
	margin-bottom: var(--fp-space-5);
	padding-bottom: var(--fp-space-4);
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-swapwhy {
	display: grid;
	gap: var(--fp-space-3);
	margin: var(--fp-space-3) 0;
}

.fitpro-app .fitpro-exblock__head:hover,
.fitpro-app .fitpro-exblock__head:focus-visible {
	background: var(--fp-surface);
}

.fitpro-app .fitpro-exblock__thumb {
	width: 56px;
	height: 56px;
	flex: none;
	border-radius: var(--fp-radius);
	object-fit: cover;
	background: var(--fp-line);
}

.fitpro-app .fitpro-exblock__thumb--none {
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--fp-muted);
	font-size: 20px;
}

.fitpro-app .fitpro-exblock__heading {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}

.fitpro-app .fitpro-exblock__name {
	font-family: var(--fp-font-heading);
	font-size: 15px;
	font-weight: 700;
	letter-spacing: 0.5px;
	color: var(--fp-heading);
	line-height: 1.3;
}

/* "How to do it" is the affordance. Stated in words rather than as an icon,
   because this is the first screen a new client meets and a glyph is a guess. */
.fitpro-app .fitpro-exblock__how {
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-accent);
}

.fitpro-app .fitpro-exblock__superset {
	display: inline-block;
	margin-right: var(--fp-space-2);
	padding: 2px 8px;
	border-radius: var(--fp-radius-pill);
	background: var(--fp-navy);
	color: var(--fp-inverse);
	font-family: var(--fp-font-body);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	vertical-align: middle;
}

/*
 * The coach's note. Given a left rule and a warm ground because it is the one
 * line on this screen written by a person to this person, and it competes with
 * numbers that are visually louder than it.
 */
.fitpro-app .fitpro-exblock__note {
	margin: 0 0 var(--fp-space-3);
	padding: var(--fp-space-3);
	border-left: 3px solid var(--fp-accent);
	border-radius: 0 var(--fp-radius) var(--fp-radius) 0;
	background: var(--fp-accent-soft);
	color: var(--fp-body);
	font-size: 13px;
	line-height: 1.55;
}

.fitpro-app .fitpro-exblock__note strong {
	color: var(--fp-heading);
}

/* ---------------------------------------------------------------------------
 * Editable title, and the confirmation under it
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-titlebar {
	position: relative;
	align-items: flex-start;
	gap: var(--fp-space-3);
	margin-top: var(--fp-space-3);
}

.fitpro-app .fitpro-titlebar__main {
	min-width: 0;
	flex: 1 1 auto;
}

/*
 * The heading is the control. A button rather than a heading with a click
 * handler, so it is keyboard reachable and announced as something you can
 * operate — and it keeps the `<h2>` inside it, so the document outline is
 * unchanged and a screen reader still finds the page's heading where it
 * expects to.
 */
.fitpro-app .fitpro-titlebar__title {
	display: block;
	/*
	 * Shrink to the words, not to the column.
	 *
	 * `width: 100%` made the heading fill the row, which pushed Assign / Save /
	 * Delete onto a line of their own on a 1400px screen — a page head that
	 * wraps for no reason reads as a layout that ran out of room. The input
	 * below still takes the full width, because a field you are typing into
	 * should not resize itself as you type.
	 */
	width: auto;
	max-width: 100%;
	padding: 2px 8px;
	margin: 0 0 0 -8px;
	border: 1px solid transparent;
	border-radius: var(--fp-radius);
	background: none;
	text-align: left;
	cursor: text;
	transition: border-color 0.12s ease, background 0.12s ease;
}

.fitpro-app .fitpro-titlebar__title h2 {
	margin: 0;
}

/* The affordance appears on hover and on focus. Never only on hover: that is
   invisible on a phone, where the tap works exactly the same. */
.fitpro-app .fitpro-titlebar__title:hover,
.fitpro-app .fitpro-titlebar__title:focus-visible {
	border-color: var(--fp-line);
	background: var(--fp-card);
}

.fitpro-app .fitpro-titlebar__title[disabled] {
	cursor: default;
	border-color: transparent;
	background: none;
}

/*
 * The input inherits the heading's type so the text does not jump when editing
 * starts. A field that resizes its own content on click reads as a glitch.
 */
.fitpro-app .fitpro-titlebar__input {
	width: 100%;
	padding: 2px 8px;
	margin: 0 0 0 -8px;
	border: 1px solid var(--fp-accent);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-heading);
	font-family: var(--fp-font-heading);
	font-size: clamp(22px, 4vw, 30px);
	font-weight: 700;
	letter-spacing: 2px;
	text-transform: uppercase;
}

.fitpro-app .fitpro-titlebar__tools {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
	flex: none;
	flex-wrap: wrap;
}

/*
 * Destructive actions are red, and only these. `--fp-orange` is the product's
 * primary colour and sits on the button a coach presses most, so it cannot also
 * mean "this destroys something".
 */
.fitpro-app .fitpro-btn--danger {
	color: var(--fp-danger);
}

.fitpro-app .fitpro-btn--danger:hover {
	background: var(--fp-danger-soft);
	color: var(--fp-danger);
}

.fitpro-app .fitpro-btn.fitpro-btn--primary.fitpro-btn--danger {
	background: var(--fp-danger);
	color: var(--fp-inverse);
}

.fitpro-app .fitpro-btn.fitpro-btn--primary.fitpro-btn--danger:hover {
	background: var(--fp-danger);
	filter: brightness(0.92);
}

/*
 * The confirmation sits under the title rather than over the page.
 *
 * A modal would have to trap focus, restore it, and handle Escape — three
 * things that are easy to get subtly wrong and that a native `confirm()` gets
 * right while being unstyleable and unable to explain itself. This explains
 * itself, which matters here because deleting a workout somebody has trained
 * archives it instead, and the coach should read that before pressing rather
 * than discover it after.
 */
.fitpro-app .fitpro-confirm {
	flex-basis: 100%;
	margin-top: var(--fp-space-3);
	padding: var(--fp-space-4);
	border: 1px solid var(--fp-danger);
	border-radius: var(--fp-radius-lg);
	background: var(--fp-danger-soft);
}

.fitpro-app .fitpro-confirm p {
	margin: 0 0 var(--fp-space-3);
	color: var(--fp-heading);
	font-size: 13px;
	line-height: 1.6;
}

.fitpro-app .fitpro-confirm .fitpro-actions {
	margin: 0;
}

@media (max-width: 560px) {
	/* The tools drop under the title rather than squeezing it: at 320px a
	   heading sharing a row with three buttons has about 90px left. */
	.fitpro-app .fitpro-titlebar {
		flex-direction: column;
		align-items: stretch;
	}

	.fitpro-app .fitpro-titlebar__tools {
		justify-content: flex-start;
	}
}

/* ---------------------------------------------------------------------------
 * Food search results
 *
 * Each row is one whole button, not a row with a button in it. This is operated
 * one-handed in a kitchen or a shop, and a 44px target that covers the entire
 * row is the difference between logging a meal and giving up on it.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-foodlist {
	display: flex;
	flex-direction: column;
	gap: 6px;
	margin: var(--fp-space-3) 0 0;
	padding: 0;
	list-style: none;
}

.fitpro-app .fitpro-foodlist button:not(.fitpro-btn) {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 3px;
	width: 100%;
	min-height: var(--fp-tap-min);
	padding: 10px var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: inherit;
	font-family: var(--fp-font-body);
	text-align: left;
	cursor: pointer;
	transition: border-color 0.12s ease, background 0.12s ease;
}

.fitpro-app .fitpro-foodlist button:not(.fitpro-btn):hover {
	border-color: var(--fp-accent);
	background: var(--fp-accent-soft);
}

.fitpro-app .fitpro-foodlist__name {
	font-size: 13px;
	font-weight: 700;
	color: var(--fp-heading);
	line-height: 1.35;
}

.fitpro-app .fitpro-foodlist__name em {
	font-style: normal;
	font-weight: 400;
	color: var(--fp-muted);
}

/* The macros and, crucially, what they are per. The serving basis varies by
   source and by food, and reading it as 100 g when it is 15 g overstates the
   entry sevenfold. */
.fitpro-app .fitpro-foodlist__macros {
	font-size: 11.5px;
	color: var(--fp-muted);
	font-variant-numeric: tabular-nums;
	line-height: 1.4;
}

.fitpro-app .fitpro-foodlist__macros em {
	font-style: normal;
	opacity: 0.8;
}

/* ---------------------------------------------------------------------------
 * Data sources — the four-way comparison in Settings
 *
 * A grid rather than a stack, because the whole point of this screen is
 * comparing options side by side. Stacked, the reader has to hold three feature
 * lists in their head to compare the fourth against them, which is exactly the
 * failure the attention scorecard already recorded from the other direction.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-sources {
	display: grid;
	/* auto-fit, not a fixed count: four cards on a wide screen, two on a
	   tablet, one on a phone, with no breakpoint list to keep in sync. 260px is
	   the width at which the longest feature label stops wrapping. */
	grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
	gap: var(--fp-space-4);
}

.fitpro-app .fitpro-source {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-3);
	padding: var(--fp-space-4);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius-lg);
	background: var(--fp-card);
}

/* The one in use is stated in colour as well as in words. Which data source is
   answering is the single most consequential thing on this screen. */
.fitpro-app .fitpro-source.is-active {
	border-color: var(--fp-accent);
	box-shadow: 0 0 0 1px var(--fp-accent);
}

/*
 * Not built yet: legible, obviously inert, and never mistaken for a broken
 * card. Opacity alone would fail that last test — a faded card reads as
 * disabled-because-something-went-wrong — so the ground changes too.
 */
.fitpro-app .fitpro-source.is-unavailable {
	background: var(--fp-surface);
	border-style: dashed;
}

.fitpro-app .fitpro-source__head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-source__head h4 {
	margin: 0;
	font-family: var(--fp-font-heading);
	font-size: 15px;
	font-weight: 700;
	letter-spacing: 1.2px;
	text-transform: uppercase;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-source__desc {
	margin: 0;
	color: var(--fp-muted);
	font-size: 13px;
	line-height: 1.6;
}

.fitpro-app .fitpro-source__foot {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2);
	/* Pushed to the bottom so the buttons line up across cards of different
	   description lengths — a row of buttons at four different heights reads as
	   four unrelated panels. */
	margin-top: auto;
	padding-top: var(--fp-space-2);
}

.fitpro-app .fitpro-source__note {
	margin: 0;
	color: var(--fp-muted);
	font-size: 12px;
	line-height: 1.5;
}

/* ---------------------------------------------------------------------------
 * Feature ticks
 *
 * The crosses carry more information than the ticks: everything here does
 * exercises, and what separates the options is what they cannot do. So an
 * unavailable feature is greyed but never hidden — a shorter list would read as
 * a smaller card rather than as a missing capability.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-feat {
	display: flex;
	flex-direction: column;
	gap: 6px;
	margin: 0;
	padding: 0;
	list-style: none;
}

.fitpro-app .fitpro-feat li {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
	font-size: 12.5px;
	line-height: 1.4;
	color: var(--fp-body);
}

.fitpro-app .fitpro-feat li.is-off {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-feat__mark {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: none;
	width: 18px;
	height: 18px;
	border-radius: 50%;
	background: var(--fp-line);
	color: var(--fp-muted);
}

.fitpro-app .fitpro-feat__mark.is-on {
	background: color-mix(in srgb, var(--fp-success) 14%, transparent);
	color: var(--fp-success);
}

.fitpro-app .fitpro-feat__mark svg {
	fill: currentColor;
}

/* ---------------------------------------------------------------------------
 * Consents — "Your privacy"
 *
 * This had no stylesheet at all. Every part of a consent row is a <span>, which
 * is inline, so the label, the "needed to use the app" note, the sentence
 * explaining what the data is for and the date it was agreed all ran together
 * into one unbroken line of text per row. It was legible in the sense that the
 * words were present, and unreadable in every sense that matters — and it is
 * the one screen in the product where the reader has to actually understand
 * what they are agreeing to before they agree to it.
 *
 * So the structure is stated here rather than left to the browser: each row is
 * a two-column grid, and the parts of a row stack.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-consents {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-3);
	margin: var(--fp-space-4) 0 0;
	padding: 0;
	list-style: none;
}

.fitpro-app .fitpro-consents li {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--fp-space-4);
	padding: var(--fp-space-4) 0 0;
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-consents li:first-child {
	padding-top: 0;
	border-top: 0;
}

/* The parts of one consent, stacked. The `<span>`s are what shipped and they
   are the right element — a label is not a paragraph — so the block is asserted
   here rather than by rewriting the markup. */
.fitpro-app .fitpro-consents__body {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-width: 0;
}

.fitpro-app .fitpro-consents__label {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2);
	font-weight: 700;
	color: var(--fp-heading);
}

/* "Needed to use the app" is a status, not part of the name. Reading as a pill
   is what keeps it from being mistaken for one. */
.fitpro-app .fitpro-consents__req {
	display: inline-block;
	padding: 2px 9px;
	border-radius: var(--fp-radius-pill);
	background: var(--fp-line);
	color: var(--fp-muted);
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	white-space: nowrap;
}

/*
 * The sentence saying what the information is for.
 *
 * PIPEDA scales the consent it expects to how sensitive the data is, and a
 * consent nobody can read is not informed consent. So this gets real line
 * height and a measure that stops at about 70 characters rather than running
 * the full width of a desktop card.
 */
.fitpro-app .fitpro-consents__desc {
	display: block;
	max-width: 62ch;
	color: var(--fp-body);
	font-size: 13px;
	line-height: 1.6;
}

.fitpro-app .fitpro-consents__when {
	display: block;
	color: var(--fp-muted);
	font-size: 12px;
}

/* Inside the stacked body it is a line of text under the description, so it
   takes the description's measure rather than the narrow column it needs when
   it is standing in for a button. */
.fitpro-app .fitpro-consents__body > .fitpro-consents__blocked {
	display: block;
	max-width: 62ch;
}

/* A required consent shows its state and offers no switch, so the state has to
   read as a status rather than as a disabled button somebody might keep
   pressing. */
.fitpro-app .fitpro-consents__state {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	padding: 6px 14px;
	border-radius: var(--fp-radius-pill);
	background: var(--fp-line);
	color: var(--fp-muted);
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	white-space: nowrap;
}

.fitpro-app .fitpro-consents__state.is-on {
	background: var(--fp-accent-soft);
	color: var(--fp-accent);
}

.fitpro-app .fitpro-consents li > .fitpro-btn,
.fitpro-app .fitpro-consents li > .fitpro-consents__blocked,
.fitpro-app .fitpro-consents__state {
	flex: none;
}

.fitpro-app .fitpro-consents__foot {
	margin: var(--fp-space-4) 0 0;
	padding-top: var(--fp-space-4);
	border-top: 1px solid var(--fp-line);
	color: var(--fp-muted);
	font-size: 12px;
	line-height: 1.6;
}

/*
 * Below 640px the switch drops under the text rather than fighting it for
 * width. At 320px a side-by-side row leaves the description about 150px, which
 * breaks "progress photos" across three lines and puts the button somewhere it
 * cannot be pressed with a thumb.
 */
@media (max-width: 640px) {
	.fitpro-app .fitpro-consents li {
		flex-direction: column;
		align-items: stretch;
		gap: var(--fp-space-3);
	}

	.fitpro-app .fitpro-consents__state {
		align-self: flex-start;
	}
}

/* ---------------------------------------------------------------------------
 * Uploader
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-uploader__progress {
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.fitpro-app .fitpro-uploader__meta {
	display: flex;
	justify-content: space-between;
	gap: var(--fp-space-3);
	font-size: 12px;
	color: var(--fp-muted);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-uploader__meta span:first-child {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.fitpro-app .fitpro-uploader__track {
	height: 8px;
	border-radius: 4px;
	background: var(--fp-line);
	overflow: hidden;
}

.fitpro-app .fitpro-uploader__fill {
	height: 100%;
	background: var(--fp-accent);
	transition: width 0.2s ease;
}

/* ---------------------------------------------------------------------------
 * Messaging
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-thread {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-3);
	max-height: 60dvh;
	overflow-y: auto;
	padding: var(--fp-space-3) 0;
	margin-bottom: var(--fp-space-4);
	-webkit-overflow-scrolling: touch;
}

.fitpro-app .fitpro-msg {
	align-self: flex-start;
	max-width: 82%;
	padding: var(--fp-space-3) var(--fp-space-4);
	border-radius: var(--fp-radius-lg);
	background: var(--fp-card);
	border: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-msg--mine {
	align-self: flex-end;
	background: var(--fp-accent-soft);
	border-color: transparent;
}

.fitpro-app .fitpro-msg__body {
	font-size: 15px;
	line-height: 1.5;
	color: var(--fp-heading);
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-msg__body p:last-child {
	margin-bottom: 0;
}

.fitpro-app .fitpro-msg__time {
	display: block;
	margin-top: 6px;
	font-size: 10px;
	letter-spacing: 0.5px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-msg__media {
	display: block;
	width: 100%;
	max-width: 320px;
	margin-top: var(--fp-space-2);
	border-radius: var(--fp-radius);
	overflow: hidden;
}

.fitpro-app .fitpro-msg__media img,
.fitpro-app video.fitpro-msg__media {
	display: block;
	width: 100%;
	border-radius: var(--fp-radius);
}

/*
 * A prerequisite that has to be done at the other end — today only the CORS
 * policy an S3 bucket needs before a browser may upload to it.
 *
 * Folded shut. It is thirty lines of JSON that matter once, on the day the
 * bucket is set up, and a coach returning to change a key should not have to
 * scroll past it every time.
 */
.fitpro-app .fitpro-setupnote {
	margin-bottom: var(--fp-space-4);
	padding: 12px 14px;
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
}

.fitpro-app .fitpro-setupnote > summary {
	cursor: pointer;
	font-weight: 700;
	font-size: 14px;
}

.fitpro-app .fitpro-setupnote p {
	margin: 10px 0;
	color: var(--fp-muted);
	font-size: 13px;
	line-height: 1.6;
}

/*
 * `pre` scrolls rather than wraps: JSON that has been reflowed is no longer
 * valid to paste, which is the only thing this block is for.
 */
.fitpro-app .fitpro-setupnote__code {
	margin: 0 0 10px;
	padding: 10px 12px;
	border-radius: var(--fp-radius);
	background: var(--fp-navy);
	color: #e6e9ee;
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	font-size: 12px;
	line-height: 1.5;
	overflow-x: auto;
	white-space: pre;
}

/*
 * An attached file that is not a picture or a clip — today that means a PDF,
 * which is the only non-playable type the allowlist admits.
 *
 * A card rather than a bare link, because the reader has to be told three
 * things before they decide to tap: what it is called, what kind of file it is
 * and how big it is. A phone on mobile data is the case that matters, and
 * "Open" on an unlabelled link there is a guess.
 *
 * Wraps rather than scrolls: a real filename is long, and the actions must stay
 * reachable at 320px.
 */
.fitpro-app .fitpro-attachment {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-2);
	padding: 12px 14px;
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
}

.fitpro-app .fitpro-attachment__text {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
	flex: 1 1 160px;
}

/*
 * `overflow-wrap` rather than an ellipsis: a filename is how somebody knows
 * which document this is, and truncating "september-meal-plan-v2.pdf" to
 * "september-meal…" removes the half that distinguishes it from the next one.
 */
.fitpro-app .fitpro-attachment__name {
	font-weight: 700;
	font-size: 14px;
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-attachment__meta {
	color: var(--fp-muted);
	font-size: 12px;
}

.fitpro-app .fitpro-attachment__actions {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
}

.fitpro-app .fitpro-msg__file {
	display: inline-block;
	margin-top: var(--fp-space-2);
	padding: 8px 14px;
	border-radius: var(--fp-radius-pill);
	background: var(--fp-surface);
	font-size: 13px;
	font-weight: 700;
}

/* ---------------------------------------------------------------------------
   Recipes and meal plans
   --------------------------------------------------------------------------- */

/*
 * The recipe grid.
 *
 * A card holds a title, two lines of description, tags, four circles and a
 * row of facts, which does not fit the 150px tile the grid was sized for when
 * a card was a picture and a title. 260px is the narrowest that holds four
 * 56px circles side by side; below that the grid is one column, which on a
 * phone is what it would be anyway.
 */
.fitpro-app .fitpro-recipe-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
	gap: var(--fp-space-4);
	margin-bottom: var(--fp-space-4);
}

/*
 * The card is an article with one button stretched over it (see
 * `RecipeCard`). `position: relative` is the box the button's `::after`
 * fills; the focus ring is drawn on the card, because the button it belongs
 * to is a line of text at the bottom and a ring round that would not say the
 * whole card is what opens.
 */
.fitpro-app .fitpro-recipe-card {
	position: relative;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	background: var(--fp-card);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius-lg);
	box-shadow: var(--fp-shadow);
	transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.fitpro-app .fitpro-recipe-card:hover {
	border-color: var(--fp-line-strong);
	box-shadow: var(--fp-shadow-lg);
	transform: translateY(-2px);
}

.fitpro-app .fitpro-recipe-card:focus-within {
	outline: 2px solid var(--fp-accent);
	outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
	.fitpro-app .fitpro-recipe-card,
	.fitpro-app .fitpro-recipe-card:hover {
		transform: none;
		transition: none;
	}
}

.fitpro-app .fitpro-recipe-card__img {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 2 / 1;
	object-fit: cover;
	background: var(--fp-surface);
}

.fitpro-app .fitpro-recipe-card__body {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--fp-space-3);
	padding: var(--fp-space-4) var(--fp-space-5) var(--fp-space-4);
}

/*
 * The provider's own title, in the body face: recipe names are long and are
 * somebody else's words, and the heading face's capitals and tracking would
 * make "Quick Protein Plate: Turkey, Roasted Vegetables & Grains" a block of
 * shouting three lines deep.
 */
.fitpro-app .fitpro-recipe-card__title,
.fitpro-app .fitpro-recipe__title {
	margin: 0;
	font-family: var(--fp-font-body);
	font-weight: 700;
	letter-spacing: 0;
	text-transform: none;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-recipe-card__title {
	font-size: 17px;
	line-height: 1.3;
}

/*
 * Two lines, then an ellipsis: the whole description is one tap away.
 *
 * `padding: 0` is load-bearing. The theme gives every `<p>` a bottom padding,
 * and a clamp clips the content box only -- so the third line showed through
 * the padding, cut in half, under the ellipsis that said it had stopped.
 */
.fitpro-app .fitpro-recipe-card__blurb {
	display: -webkit-box;
	margin: -4px 0 0;
	padding: 0;
	overflow: hidden;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	font-size: 13px;
	line-height: 1.5;
	color: var(--fp-body);
}

/*
 * The circles, the facts and the link sit at the foot of the card, so a row of
 * cards with different titles, blurbs and numbers of tags still lines up the
 * figures a reader compares across it.
 */
.fitpro-app .fitpro-recipe-card__body > .fitpro-perserving {
	margin-top: auto;
}

/*
 * The title is the button, and the whole card behind it. The `::after` covers
 * the card; the bookmark and the action underneath (`__action`) are lifted
 * above it. It looks like the heading it sits in.
 */
.fitpro-app .fitpro-recipe-card__open {
	display: inline;
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	font: inherit;
	letter-spacing: inherit;
	text-align: left;
	text-transform: none;
	color: inherit;
	cursor: pointer;
}

.fitpro-app .fitpro-recipe-card__open::after {
	content: '';
	position: absolute;
	inset: 0;
}

.fitpro-app .fitpro-recipe-card__open:focus-visible {
	outline: none;
}

.fitpro-app .fitpro-recipe-card__action {
	position: relative;
	z-index: 1;
	padding: 0 var(--fp-space-5) var(--fp-space-4);
}

/* ---------------------------------------------------------------------------
 * Tags, facts and the per-serving circles: the card's and the sheet's.
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-recipe-tags {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
	margin: 0;
}

.fitpro-app .fitpro-recipe-tag {
	padding: 3px 10px;
	border-radius: var(--fp-radius-pill);
	font-size: 12px;
	font-weight: 600;
	line-height: 1.4;
	white-space: nowrap;
}

.fitpro-app .fitpro-recipe-tag--meal {
	color: var(--fp-tag-meal);
	background: color-mix(in srgb, var(--fp-tag-meal) 12%, transparent);
}

.fitpro-app .fitpro-recipe-tag--cuisine {
	color: var(--fp-tag-cuisine);
	background: color-mix(in srgb, var(--fp-tag-cuisine) 12%, transparent);
}

.fitpro-app .fitpro-recipe-tag--diet {
	color: var(--fp-tag-diet);
	background: color-mix(in srgb, var(--fp-tag-diet) 12%, transparent);
}

.fitpro-app .fitpro-perserving {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-perserving__label {
	margin: 0;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	text-align: center;
	color: var(--fp-muted);
}

/*
 * Four circles, one per macro.
 *
 * A grid of four equal columns with each circle capped and centred, so they
 * stay round -- `aspect-ratio` on the item, never a fixed height -- and stay
 * spaced evenly whether the card is 260px or 400px. The ring is the macro's
 * colour at a low mix and the glyph carries the full colour, the rule the macro
 * icons already follow: the number is data, and data is body text.
 */
.fitpro-app .fitpro-perserving__circles {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--fp-space-2);
	margin: 0;
}

.fitpro-app .fitpro-perserving__circle {
	--fp-circle-ink: var(--fp-macro-calories);
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 2px;
	width: 100%;
	max-width: 64px;
	aspect-ratio: 1;
	justify-self: center;
	border: 2px solid color-mix(in srgb, var(--fp-circle-ink) 35%, transparent);
	border-radius: 50%;
	background: color-mix(in srgb, var(--fp-circle-ink) 8%, transparent);
}

.fitpro-app .fitpro-perserving__circle--protein {
	--fp-circle-ink: var(--fp-macro-protein);
}

.fitpro-app .fitpro-perserving__circle--carbs {
	--fp-circle-ink: var(--fp-macro-carbs);
}

.fitpro-app .fitpro-perserving__circle--fat {
	--fp-circle-ink: var(--fp-macro-fat);
}

.fitpro-app .fitpro-perserving__value {
	font-size: 13px;
	font-weight: 700;
	line-height: 1.1;
	white-space: nowrap;
	color: var(--fp-heading);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-perserving--large .fitpro-perserving__circle {
	max-width: 84px;
}

.fitpro-app .fitpro-perserving--large .fitpro-perserving__value {
	font-size: 16px;
}

/*
 * Time and yield on the left, difficulty pushed to the right. Icons are
 * decorative -- the words beside them say it -- and inherit the muted ink.
 */
.fitpro-app .fitpro-recipe-facts {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-1) var(--fp-space-4);
	margin: 0;
	font-size: 13px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-recipe-facts__item {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	white-space: nowrap;
}

.fitpro-app .fitpro-recipe-facts__item .fitpro-icon {
	flex: none;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-recipe-facts__level {
	margin-left: auto;
	color: var(--fp-muted);
}

/*
 * Save recipe.
 *
 * On a card, a round button over the top right of the picture, above the
 * stretched "Read recipe" layer (`z-index: 2`) so a tap on it saves rather
 * than opens. The card surface behind it, not a translucent one, so it reads
 * on a pale picture and a dark one alike. 40px, on a card somebody browses
 * rather than a logger operated mid-set.
 *
 * Saved is the same drawing filled in the accent: one shape for both states,
 * and `aria-pressed` saying which.
 */
.fitpro-app .fitpro-savebtn {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	min-height: 36px;
	padding: 0 16px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius-pill);
	background: var(--fp-card);
	font: inherit;
	font-size: 13px;
	font-weight: 600;
	color: var(--fp-heading);
	cursor: pointer;
	transition: border-color 0.12s ease, color 0.12s ease;
}

.fitpro-app .fitpro-savebtn:hover {
	border-color: var(--fp-accent);
	color: var(--fp-accent);
}

.fitpro-app .fitpro-savebtn:focus-visible {
	outline: 2px solid var(--fp-accent);
	outline-offset: 2px;
}

.fitpro-app .fitpro-savebtn.is-saved {
	border-color: var(--fp-accent);
	color: var(--fp-accent);
}

.fitpro-app .fitpro-savebtn.is-saved .fitpro-icon--stroked {
	fill: currentColor;
}

.fitpro-app .fitpro-savebtn--icon {
	position: absolute;
	top: 10px;
	right: 10px;
	z-index: 2;
	justify-content: center;
	width: 40px;
	min-height: 40px;
	height: 40px;
	padding: 0;
	border-radius: 50%;
	box-shadow: var(--fp-shadow);
}

.fitpro-app .fitpro-recipe__save {
	display: flex;
}

/* "I ate this" on a saved recipe: a button, then the day, meal and servings in place. */
.fitpro-app .fitpro-recipe__eat {
	margin-top: var(--fp-space-3);
}

.fitpro-app .fitpro-ate {
	display: grid;
	gap: var(--fp-space-3);
	justify-items: start;
}

.fitpro-app .fitpro-ate--open {
	justify-items: stretch;
	padding: var(--fp-space-3);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
}

.fitpro-app .fitpro-ate__title {
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-ate__done {
	display: grid;
	gap: 2px;
	margin: 0;
	padding: 0;
	color: var(--fp-body);
}

.fitpro-app .fitpro-ate__done strong {
	color: var(--fp-success);
}

.fitpro-app .fitpro-ate__macros {
	font-size: 14px;
}

/* A count of servings is a few characters; a box the width of the sheet reads as a text field. */
.fitpro-app .fitpro-ate .fitpro-stepper {
	justify-self: start;
	margin: 0;
}

.fitpro-app .fitpro-ate .fitpro-stepper__value {
	width: 72px;
}

/* ---------------------------------------------------------------------------
 * The recipe sheet.
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-recipe {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-3);
}

/*
 * A banner, not the whole photograph: at the sheet's width a 16:10 picture is
 * 470px tall and pushes the ingredients below the fold before anybody has read
 * the title. The close button sits on it, on its own surface circle.
 */
.fitpro-app .fitpro-recipe__hero {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 21 / 9;
	object-fit: cover;
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-recipe__title {
	font-size: 22px;
	line-height: 1.25;
	padding-right: calc(var(--fp-tap-min) + var(--fp-space-2));
}

/* With a picture the close button is over the picture, not the title. */
.fitpro-app .fitpro-recipe__hero ~ .fitpro-recipe__title {
	padding-right: 0;
}

/*
 * The description carries its attribution as a link whose target is a long,
 * unbroken address; `overflow-wrap` keeps a bare one from pushing the sheet
 * sideways on a phone.
 */
.fitpro-app .fitpro-recipe__blurb {
	margin: 0;
	font-size: 14px;
	line-height: 1.6;
	color: var(--fp-body);
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-recipe .fitpro-perserving {
	margin-top: var(--fp-space-2);
}

/*
 * The servings stepper and the sentence saying what it changes, side by side
 * where there is room and stacked where there is not.
 */
.fitpro-app .fitpro-recipe__portion {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2) var(--fp-space-4);
	padding: var(--fp-space-3) var(--fp-space-4);
	background: var(--fp-surface);
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-recipe__portion .fitpro-stepper {
	flex: 0 0 auto;
	margin: 0;
}

.fitpro-app .fitpro-recipe__portion .fitpro-stepper__value {
	width: 56px;
}

.fitpro-app .fitpro-recipe__portion .fitpro-field__help {
	flex: 1 1 14rem;
	margin: 0;
}

/*
 * A section owns the space above it and its head the space below, so neither
 * depends on the other bringing a margin.
 */
.fitpro-app .fitpro-recipe__part {
	margin-top: var(--fp-space-3);
}

.fitpro-app .fitpro-recipe__parthead {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-2);
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-recipe__parthead h4 {
	margin: 0;
	font-size: 14px;
	letter-spacing: 1.5px;
}

/*
 * Cups or grams: two halves of one pill, the chosen half filled. Its own small
 * control rather than `Segmented`, which is a centred rail sized for switching
 * screens and would sit under the heading instead of beside it.
 */
.fitpro-app .fitpro-unittoggle {
	display: inline-flex;
	padding: 2px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius-pill);
}

.fitpro-app .fitpro-unittoggle button {
	min-height: 30px;
	padding: 0 14px;
	border: 0;
	border-radius: var(--fp-radius-pill);
	background: none;
	font: inherit;
	font-size: 13px;
	font-weight: 600;
	color: var(--fp-body);
	cursor: pointer;
}

.fitpro-app .fitpro-unittoggle button[aria-pressed="true"] {
	background: var(--fp-heading);
	color: var(--fp-card);
}

.fitpro-app .fitpro-unittoggle button:focus-visible {
	outline: 2px solid var(--fp-accent);
	outline-offset: 1px;
}

/*
 * Amount and name on the left, reading as one phrase -- "3 tbsp paprika" --
 * and the line's calories on the right, muted, lined up down the list.
 */
.fitpro-app .fitpro-recipe__ingredients {
	display: grid;
	margin: 0;
}

.fitpro-app .fitpro-recipe__ingredients li {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--fp-space-3);
	padding: 7px 0;
	border-bottom: 1px solid var(--fp-line);
	font-size: 14px;
}

.fitpro-app .fitpro-recipe__ingredients li:last-child {
	border-bottom: 0;
}

.fitpro-app .fitpro-recipe__line {
	min-width: 0;
	color: var(--fp-body);
}

.fitpro-app .fitpro-recipe__amount {
	font-weight: 700;
	white-space: nowrap;
	color: var(--fp-heading);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-recipe__kcal {
	flex: none;
	font-size: 12px;
	white-space: nowrap;
	color: var(--fp-muted);
	font-variant-numeric: tabular-nums;
}

/*
 * Numbered. The app-wide list reset zeroes the padding every `<ol>` relies on
 * (bug 38's `!important`), so the indent is restated with the same weight.
 */
.fitpro-app ol.fitpro-recipe__method {
	margin: 0;
	padding-left: 22px !important;
	list-style: decimal;
}

.fitpro-app ol.fitpro-recipe__method li {
	margin-bottom: var(--fp-space-2);
	padding-left: 4px;
	font-size: 14px;
	line-height: 1.6;
	color: var(--fp-body);
}

.fitpro-app ol.fitpro-recipe__method li::marker {
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app p.fitpro-recipe__method {
	margin: 0;
	font-size: 14px;
	line-height: 1.7;
	white-space: pre-line;
	color: var(--fp-body);
}

/*
 * One release in the changelog.
 *
 * A rule above each, rather than a card each: eighteen cards is a stack of
 * boxes to scroll past, and the thing being separated is a heading and a short
 * list. The first has no rule, so the panel does not open with a line under
 * the sentence that introduces it.
 */
.fitpro-app .fitpro-release {
	padding-top: var(--fp-space-4);
	margin-top: var(--fp-space-4);
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-release:first-of-type {
	border-top: 0;
}

.fitpro-app .fitpro-release__head {
	display: flex;
	align-items: baseline;
	gap: var(--fp-space-3);
	flex-wrap: wrap;
	margin: 0 0 var(--fp-space-2);
}

/* Which version is running, marked rather than left to be counted. */
.fitpro-app .fitpro-release__now {
	font-family: var(--fp-font-body);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	padding: 2px 8px;
	border-radius: 100px;
	color: var(--fp-accent-contrast);
	background: var(--fp-accent-fill);
}

/*
 * Real bullets, against the app-wide reset.
 *
 * `.fitpro-app ul` strips markers with `!important` to defeat Divi's
 * `#left-area ul` (bug 38), and these genuinely are a list of separate items
 * rather than interface furniture -- the same exception the agreement body
 * already takes, and it needs the same `!important` to win.
 */
.fitpro-app .fitpro-release__items {
	list-style: disc !important;
	padding-left: 20px !important;
	margin: 0;
	display: grid;
	gap: var(--fp-space-2);
	color: var(--fp-body);
}

/*
 * Upload a video, or paste a link.
 *
 * Two ways in, side by side with the word "or" between them, because they are
 * alternatives rather than two fields to fill in. It stacks below 620px: two
 * drop zones on a phone are two unreadable boxes.
 */
.fitpro-app .fitpro-videopick {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
	align-items: center;
	gap: var(--fp-space-4);
	margin-bottom: var(--fp-space-5);
}

.fitpro-app .fitpro-videopick__drop {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 4px;
	padding: var(--fp-space-5) var(--fp-space-4);
	border: 1px dashed var(--fp-line-strong);
	border-radius: var(--fp-radius);
	text-align: center;
	cursor: pointer;
}

.fitpro-app .fitpro-videopick__drop:hover {
	border-color: var(--fp-accent);
}

/*
 * Visually hidden rather than `display: none`, so the file input keeps its
 * place in the tab order and its focus ring -- the rule the switch already
 * follows. The label is the whole drop zone, so clicking anywhere in it opens
 * the picker.
 */
.fitpro-app .fitpro-videopick__drop input {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
}

.fitpro-app .fitpro-videopick__title {
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-videopick__hint {
	font-size: 12px;
	color: var(--fp-muted);
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-videopick__or {
	font-size: 12px;
	text-transform: uppercase;
	letter-spacing: 1px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-videopick__link .fitpro-field {
	margin-bottom: 0;
}

@media (max-width: 620px) {
	.fitpro-app .fitpro-videopick {
		grid-template-columns: minmax(0, 1fr);
		justify-items: center;
	}

	.fitpro-app .fitpro-videopick__drop,
	.fitpro-app .fitpro-videopick__link {
		width: 100%;
	}
}

/*
 * A meal swap, opened from the coach's notification.
 *
 * The two meals sit side by side wherever there is room and stack when there
 * is not: a before and an after read as a comparison across, and as a list
 * down, and both are better than two columns of 90px.
 */
.fitpro-app .fitpro-swapsides {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
	gap: var(--fp-space-3);
}

.fitpro-app .fitpro-swapside {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-swapside__label {
	font-size: 11px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-swapside__title {
	font-weight: 700;
	color: var(--fp-heading);
}

/*
 * The difference, which is the row a coach actually acts on -- so it is not
 * boxed like the two above it, and sits under them rather than beside.
 */
.fitpro-app .fitpro-swapdelta {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	flex-wrap: wrap;
	margin-top: var(--fp-space-3);
	font-size: 16px;
	font-weight: 700;
}

/*
 * An assignment row's title, which opens the session.
 *
 * A `<button>` is `display: block` and brings the user agent's own padding, so
 * wrapping two lines of text in one needs both undone -- the lesson the
 * icon-only tool buttons already record. Transparent and borderless, because
 * it has to look exactly like the text it replaced.
 */
.fitpro-app .fitpro-assignment__open {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 2px;
	flex: 1 1 auto;
	min-width: 0;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-assignment__open:hover strong {
	color: var(--fp-accent);
}

/* What is in a session, read before doing it. */
.fitpro-app .fitpro-preview {
	list-style: none !important;
	padding: 0 !important;
	margin: var(--fp-space-4) 0 0;
	display: grid;
	gap: var(--fp-space-3);
}

.fitpro-app .fitpro-preview li + li {
	border-top: 1px solid var(--fp-line);
	padding-top: var(--fp-space-3);
}

.fitpro-app .fitpro-preview__open {
	display: flex;
	align-items: flex-start;
	gap: var(--fp-space-3);
	width: 100%;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-preview__open img {
	flex: 0 0 auto;
	border-radius: var(--fp-radius);
	object-fit: cover;
}

/*
 * `min-width: 0` so a long exercise name wraps in its own column rather than
 * growing the row past the sheet -- bug 52, on the element holding free text.
 */
.fitpro-app .fitpro-preview__body {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.fitpro-app .fitpro-preview__title {
	display: flex;
	align-items: baseline;
	gap: var(--fp-space-2);
	flex-wrap: wrap;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-preview__superset {
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	padding: 1px 6px;
	border-radius: 100px;
	color: var(--fp-accent-contrast);
	background: var(--fp-accent-fill);
}

/*
 * A column of cards that spaces itself.
 *
 * `.fitpro-card` has no bottom margin on purpose -- it has to be droppable into
 * anything -- so every screen stacking them has done it with hand-placed 16px
 * spacers, which works exactly as long as somebody remembers. On the client's
 * home screen two of the six gaps were there and four were not.
 *
 * A `gap` cannot be forgotten per child, and it skips the ones that render
 * nothing: a card returning `null` produces no DOM node, so there is no empty
 * row and no doubled space where a card decided it had nothing to say.
 */
.fitpro-app .fitpro-stack {
	display: grid;
	/*
	 * `minmax(0, 1fr)`, not the implicit `auto` column, whose minimum is its
	 * widest child's content (bug 59): the activity feed's filter rail is eight
	 * buttons that will not wrap, and it pushed the coach's client page 95px
	 * wider than the window instead of scrolling inside its own rail.
	 */
	grid-template-columns: minmax(0, 1fr);
	gap: var(--fp-space-5);
	align-content: start;
}

/*
 * A stack can be hidden (the client page's two groups), and `display: grid`
 * above would otherwise beat the browser's own `[hidden] { display: none }`.
 */
.fitpro-app .fitpro-stack[hidden] {
	display: none;
}

/*
 * A section a route can land on (`shared/anchor.js`). The wrapper has no box,
 * so a card that renders nothing leaves no gap in the stack and the card itself
 * is what gets scrolled to.
 */
.fitpro-app [data-anchor] {
	display: contents;
}

/* The card a notification or a feed row just brought the coach to, for a moment. */
.fitpro-app .fitpro-anchor-target {
	outline: 2px solid var(--fp-accent);
	outline-offset: 4px;
	transition: outline-color 0.6s ease;
}

@media (prefers-reduced-motion: reduce) {
	.fitpro-app .fitpro-anchor-target {
		transition: none;
	}
}

/* The client page's groups follow the switch above them, one gap below it. */
.fitpro-app .fitpro-client-group {
	margin-top: var(--fp-space-5);
}

/*
 * The consistency card: a ring, three tallies, and two readings under it.
 */
.fitpro-app .fitpro-consistency {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--fp-space-5);
	flex-wrap: wrap;
}

.fitpro-app .fitpro-consistency__pct {
	font-family: var(--fp-font-heading);
	font-size: 30px;
	font-weight: 700;
	line-height: 1;
	color: var(--fp-heading);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-consistency__mood {
	font-size: 12px;
	color: var(--fp-muted);
}

/*
 * The three figures, side by side at any width.
 *
 * `minmax(0, 1fr)` rather than `1fr`: a bare `1fr` is `minmax(auto, 1fr)` and
 * will not shrink below its content, which on a 320px phone pushes the whole
 * card sideways once a label is as long as "Missed days" in French (bug 59).
 */
.fitpro-app .fitpro-tallies {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--fp-space-2);
	flex: 1 1 16rem;
}

.fitpro-app .fitpro-tally {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 2px;
	padding: var(--fp-space-3) var(--fp-space-2);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	text-align: center;
}

.fitpro-app .fitpro-tally__value {
	font-family: var(--fp-font-heading);
	font-size: 22px;
	font-weight: 700;
	line-height: 1;
	color: var(--fp-heading);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-tally__label {
	font-size: 11px;
	color: var(--fp-muted);
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-consistency__line {
	margin: var(--fp-space-4) 0 0;
	text-align: center;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-consistency__vs {
	margin: 2px 0 0;
	text-align: center;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-consistency__pick {
	max-width: 10rem;
}

/* Weight and body fat, as two tiles rather than a third card. */
.fitpro-app .fitpro-readings {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
	gap: var(--fp-space-3);
}

.fitpro-app .fitpro-reading {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: var(--fp-space-4);
	background: var(--fp-card);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
}

/*
 * A tile on Today that opens where its figure is kept: the workouts, the diary.
 * A button, drawn exactly like the tile it replaces, with a hover to say so.
 */
.fitpro-app .fitpro-opens,
.fitpro-app .fitpro-stat--open {
	width: 100%;
	margin: 0;
	font: inherit;
	color: inherit;
	text-transform: none;
	letter-spacing: normal;
	cursor: pointer;
	transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.fitpro-app .fitpro-streak.fitpro-opens,
.fitpro-app .fitpro-stat--open {
	text-align: left;
}

/* A tally has no fill of its own; a button would take the theme's grey. */
.fitpro-app .fitpro-tally.fitpro-opens,
.fitpro-app .fitpro-tally.fitpro-opens:hover {
	background: transparent;
}

.fitpro-app .fitpro-stat--open .fitpro-stat__value,
.fitpro-app .fitpro-stat--open .fitpro-stat__label {
	display: block;
}

.fitpro-app .fitpro-opens:hover,
.fitpro-app .fitpro-stat--open:hover {
	border-color: var(--fp-line-strong);
	box-shadow: var(--fp-shadow);
}

.fitpro-app .fitpro-opens:focus-visible,
.fitpro-app .fitpro-stat--open:focus-visible {
	outline: 2px solid var(--fp-accent);
	outline-offset: 2px;
}

/* A reading that opens its chart on the profile. A button, so it keeps the tile's look. */
.fitpro-app .fitpro-reading--open {
	width: 100%;
	margin: 0;
	font: inherit;
	text-align: left;
	text-transform: none;
	letter-spacing: normal;
	cursor: pointer;
	transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.fitpro-app .fitpro-reading--open:hover {
	border-color: var(--fp-line-strong);
	box-shadow: var(--fp-shadow);
}

.fitpro-app .fitpro-reading--open:focus-visible {
	outline: 2px solid var(--fp-accent);
	outline-offset: 2px;
}

.fitpro-app .fitpro-reading__label {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-reading__value {
	font-family: var(--fp-font-heading);
	font-size: 22px;
	font-weight: 700;
	line-height: 1.1;
	color: var(--fp-heading);
}

/*
 * Which way it moved, and deliberately **not** coloured good or bad.
 *
 * A client building muscle wants the weight line going up and one cutting
 * wants it down; green-for-down would congratulate half a roster for the wrong
 * thing. The same call the measurement chart already makes.
 */
.fitpro-app .fitpro-reading__move {
	font-size: 12px;
	color: var(--fp-muted);
	font-variant-numeric: tabular-nums;
}

/*
 * The day strip on a client's meal plan.
 *
 * A rail, like the tab bar and the segmented control: sized by its contents
 * and scrolled sideways once a 28-day plan stops fitting, rather than squeezed
 * into stubs. `flex: 0 0 auto` on the children is what makes that work, and is
 * the guarantee bug 34 quietly removed from the tabs once before.
 */
.fitpro-app .fitpro-daystrip {
	display: flex;
	gap: var(--fp-space-2);
	overflow-x: auto;
	padding-bottom: var(--fp-space-2);
	margin-bottom: var(--fp-space-4);
	scrollbar-width: thin;
}

.fitpro-app .fitpro-daystrip__day {
	flex: 0 0 auto;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 2px;
	min-width: 84px;
	padding: var(--fp-space-2) var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-body);
	cursor: pointer;
	text-align: left;
}

.fitpro-app .fitpro-daystrip__day.is-active {
	border-color: var(--fp-accent);
	background: color-mix(in srgb, var(--fp-accent) 12%, transparent);
	color: var(--fp-heading);
}

.fitpro-app .fitpro-daystrip__n {
	font-family: var(--fp-font-heading);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
}

.fitpro-app .fitpro-daystrip__date {
	font-size: 12px;
	color: var(--fp-muted);
}

/*
 * The meal itself is the control that opens the recipe.
 *
 * A `<button>` is `display: block` and carries the user agent's own padding,
 * so wrapping a picture and two lines of text in one needs both undone -- the
 * lesson the icon-only tool buttons already record. It is transparent and
 * borderless because it has to look exactly like the row it replaced.
 */
.fitpro-app .fitpro-slot__open {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	flex: 1 1 auto;
	min-width: 0;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-slot__open:hover .fitpro-slot__title {
	color: var(--fp-accent);
}

/*
 * Two answers to one meal, so they wrap together rather than one of them
 * being pushed off the end on a phone.
 */
.fitpro-app .fitpro-slot__actions {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
	flex-wrap: wrap;
	flex: 0 0 auto;
}

/* What else could go in this slot. */
.fitpro-app .fitpro-swaps {
	display: grid;
	gap: var(--fp-space-3);
	margin: var(--fp-space-4) 0 0;
}

.fitpro-app .fitpro-swaps li {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	flex-wrap: wrap;
}

.fitpro-app .fitpro-swaps img {
	border-radius: var(--fp-radius);
	object-fit: cover;
	flex: 0 0 auto;
}

/*
 * `min-width: 0` so a long recipe title wraps inside its own column instead of
 * growing the row past the panel -- bug 52, on the one element here that holds
 * arbitrary text.
 */
.fitpro-app .fitpro-swaps__body {
	flex: 1 1 12rem;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.fitpro-app .fitpro-swaps__title {
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-swaps__macros {
	font-size: 13px;
}

/* The day and slot a candidate comes from, above its name. */
.fitpro-app .fitpro-swaps__when {
	font-size: 12px;
	color: var(--fp-muted);
}

/* A meal with no recipe behind it is the same row, and nothing to press. */
.fitpro-app span.fitpro-slot__open {
	cursor: default;
}

.fitpro-app span.fitpro-slot__open:hover .fitpro-slot__title {
	color: inherit;
}

/* --- "Not for me": where the replacement comes from --------------------- */

.fitpro-app .fitpro-swapfrom__ask {
	margin: var(--fp-space-4) 0 var(--fp-space-2);
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-swapfrom {
	display: grid;
	gap: var(--fp-space-3);
	margin: 0;
	padding: 0;
}

/*
 * A whole card is the button, because it is one choice with a sentence under
 * it -- a row of three small buttons would leave the sentence explaining
 * nothing anybody presses.
 */
.fitpro-app .fitpro-swapfrom__option {
	display: flex;
	flex-direction: column;
	gap: 4px;
	width: 100%;
	min-height: var(--fp-tap-min);
	padding: var(--fp-space-3) var(--fp-space-4);
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-body);
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-swapfrom__option strong {
	color: var(--fp-heading);
}

.fitpro-app .fitpro-swapfrom__option:hover,
.fitpro-app .fitpro-swapfrom__option:focus-visible {
	border-color: var(--fp-accent);
}

.fitpro-app .fitpro-swapfrom__back {
	margin-top: var(--fp-space-3);
	padding: 0;
	border: 0;
	background: none;
	color: var(--fp-accent);
	font: inherit;
	font-weight: 600;
	cursor: pointer;
	min-height: var(--fp-tap-min);
}

/* --- Shopping list ------------------------------------------------------ */

/* The cart in the plan's header: an icon button with a full-size touch target. */
.fitpro-app .fitpro-grocery__open {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--fp-tap-min);
	height: var(--fp-tap-min);
	margin-left: auto;
	padding: 0;
	border: 1px solid var(--fp-line-strong);
	border-radius: 50%;
	background: none;
	color: var(--fp-accent);
	cursor: pointer;
}

.fitpro-app .fitpro-grocery__open:hover,
.fitpro-app .fitpro-grocery__open:focus-visible {
	border-color: var(--fp-accent);
}

.fitpro-app .fitpro-grocery__weeks {
	margin: var(--fp-space-3) 0;
	padding: 0;
	border: 0;
	min-inline-size: 0;
}

.fitpro-app .fitpro-grocery__over {
	font-style: normal;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-grocery__heading {
	margin: var(--fp-space-5) 0 var(--fp-space-2);
	font-size: 14px;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-grocery__recipes,
.fitpro-app .fitpro-grocery__list,
.fitpro-app .fitpro-grocery__plain {
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-grocery__recipes li {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-2) var(--fp-space-3);
	padding: var(--fp-space-2) 0;
	border-bottom: 1px solid var(--fp-line);
}

/* The name takes the width and wraps, so the spinner stays on the row. */
.fitpro-app .fitpro-grocery__recipe {
	display: flex;
	flex: 1 1 10rem;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-grocery__recipe em {
	font-style: normal;
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-grocery__list li {
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-grocery__item {
	align-items: flex-start;
	margin: 0;
	padding: var(--fp-space-2) 0;
}

.fitpro-app .fitpro-grocery__item input {
	flex: none;
	margin-top: 2px;
}

.fitpro-app .fitpro-grocery__item > span {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-grocery__amount {
	font-size: 13px;
	color: var(--fp-muted);
}

/* Found it: struck through and dimmed, and left where it was so the list does not jump in the aisle. */
.fitpro-app .fitpro-grocery__list li.is-done strong {
	text-decoration: line-through;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-grocery__plain li {
	padding: 4px 0;
	font-size: 13px;
}

.fitpro-app .fitpro-grocery__link {
	padding: 0;
	border: 0;
	background: none;
	color: var(--fp-accent);
	font: inherit;
	font-weight: 700;
	text-decoration: underline;
	cursor: pointer;
}

/* --- Moving and reusing a logged meal ------------------------------------ */

.fitpro-app .fitpro-meal__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-2);
	margin: 2px 0 8px;
}

/*
 * Small, on one line. At a form's full pill size the two stacked into two rows
 * between a meal's name and its totals and read as the meal's main content --
 * they are tools on a record, not the thing being read. 36px still clears the
 * touch floor this file uses outside the in-workout logger.
 */
.fitpro-app .fitpro-meal__actions .fitpro-btn {
	min-height: 36px;
	padding: 6px 14px;
	font-size: 11px;
	letter-spacing: 2px;
}

.fitpro-app .fitpro-moves__entries {
	margin: var(--fp-space-3) 0;
	padding: 0;
	border: 0;
	min-inline-size: 0;
}

.fitpro-app .fitpro-moves__meal {
	display: flex;
	flex-direction: column;
	gap: 4px;
	margin-bottom: var(--fp-space-3);
}

/* The sheet is wider for a recipe: an ingredients list and a method read badly
   in a 620px column that was sized for a form. */
@media (min-width: 700px) {
	.fitpro-app .fitpro-sheet__panel--wide {
		max-width: 760px;
	}
}

.fitpro-app .fitpro-day-totals {
	/*
	 * Flex with a gap, because the macros are now inline-flex boxes rather than
	 * text separated by a middot. Left as plain text flow they butt straight up
	 * against each other -- the calorie delta ran into the protein glyph with no
	 * space at all, which reads as one number rather than two.
	 */
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 2px var(--fp-space-4);
	margin-top: 0;
	padding-bottom: var(--fp-space-3);
	border-bottom: 1px solid var(--fp-line);
	color: var(--fp-muted);
	font-size: 13px;
}

.fitpro-app .fitpro-day-totals strong {
	color: var(--fp-heading);
	font-size: 18px;
}

.fitpro-app .fitpro-delta {
	margin-left: 6px;
	font-size: 12px;
	font-weight: 700;
}

.fitpro-app .fitpro-delta--ok {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-delta--warn {
	color: var(--fp-warning);
}

.fitpro-app .fitpro-slot {
	padding: var(--fp-space-3) 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-slot:last-child {
	border-bottom: 0;
	padding-bottom: 0;
}

.fitpro-app .fitpro-slot__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-slot__name {
	font-family: var(--fp-font-heading);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-slot__empty {
	margin: 6px 0 0;
	color: var(--fp-muted);
	font-size: 13px;
}

.fitpro-app .fitpro-slot__items {
	list-style: none;
	margin: var(--fp-space-2) 0 0;
	padding: 0;
}

.fitpro-app .fitpro-slot__items li {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
	padding: 6px 0;
}

.fitpro-app .fitpro-slot__items img {
	flex: 0 0 auto;
	border-radius: var(--fp-radius);
	object-fit: cover;
}

.fitpro-app .fitpro-slot__title {
	flex: 1 1 auto;
	display: flex;
	flex-direction: column;
	min-width: 0;
	font-size: 14px;
}

.fitpro-app .fitpro-slot__macros {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 2px var(--fp-space-3);
	color: var(--fp-muted);
	font-size: 12px;
}

.fitpro-app .fitpro-slot__servings {
	flex: 0 0 auto;
	width: 68px;
	min-height: var(--fp-tap-min);
	padding: 6px 8px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	font: inherit;
	text-align: center;
}

.fitpro-app .fitpro-slot__remove {
	flex: 0 0 auto;
	width: var(--fp-tap-min);
	height: var(--fp-tap-min);
	border: 0;
	border-radius: 50%;
	background: none;
	color: var(--fp-muted);
	font-size: 16px;
	cursor: pointer;
}

.fitpro-app .fitpro-slot__remove:hover {
	background: var(--fp-surface);
	color: var(--fp-danger);
}

.fitpro-app .fitpro-check {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
	min-height: var(--fp-tap-min);
	margin-bottom: var(--fp-space-3);
	cursor: pointer;
}

.fitpro-app .fitpro-check input {
	width: 20px;
	height: 20px;
}

.fitpro-app .fitpro-notice {
	padding: var(--fp-space-3);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
	color: var(--fp-body);
	font-size: 14px;
}

/* The client's plan puts a real action on every row, so the row wraps rather
   than squeezing a 44px button against a thumbnail on a 320px screen. */
@media (max-width: 420px) {
	.fitpro-app .fitpro-slot__items li {
		flex-wrap: wrap;
	}

	.fitpro-app .fitpro-slot__title {
		flex: 1 1 60%;
	}

	/*
	 * The two answers take their own line, where their own `flex-wrap` can do
	 * something. At `flex: 0 0 auto` the pair was as wide as both buttons side
	 * by side -- about 320px of tracked uppercase -- and a row that cannot
	 * shrink pushed the whole page 26px sideways at 375px. Measured, bug 52's
	 * shape again.
	 */
	.fitpro-app .fitpro-slot__actions {
		flex: 1 1 100%;
		min-width: 0;
	}
}

/* ---------------------------------------------------------------------------
 * Needs your attention — the client scorecard
 * ------------------------------------------------------------------------ */

/*
 * Grid, not `<table>`, and the reason is the phone.
 *
 * Six columns inside a card on a 320px screen leaves 29px each: the headings
 * shred into vertical stacks two characters wide, and every tap target is a
 * third of the 44px floor. A table can only be six columns. A grid is six
 * columns where there is room and a stacked list where there is not, from the
 * same markup — and once it stacks there is space to print each explanation
 * outright instead of hiding it behind a tooltip nobody on a touchscreen can
 * open.
 */
.fitpro-app .fitpro-score-wrap {
	position: relative;
}

.fitpro-app .fitpro-score + .fitpro-score {
	margin-top: var(--fp-space-4);
}

/*
 * The name band. Severity tints it rather than marking it with a rule: a 3px
 * border on a rounded two-line header reads as a stray dash, not as a status —
 * and this is the only place severity is expressed, now that the cells
 * themselves are strictly pass or fail.
 */
.fitpro-app .fitpro-score__name {
	margin: 0;
	padding: var(--fp-space-2) var(--fp-space-3);
	border-radius: var(--fp-radius) var(--fp-radius) 0 0;
	background: var(--fp-surface);
	font-family: var(--fp-font-heading);
	font-size: 14px;
	text-align: center;
}

.fitpro-app .fitpro-score__name--high {
	background: var(--fp-danger-wash);
}

.fitpro-app .fitpro-score__name--medium {
	background: var(--fp-warning-wash);
}

.fitpro-app .fitpro-score__name--low {
	background: var(--fp-surface);
}

.fitpro-app .fitpro-score__name button {
	border: 0;
	background: none;
	padding: 0;
	font: inherit;
	font-weight: 700;
	letter-spacing: 1.5px;
	text-transform: uppercase;
	color: var(--fp-heading);
	cursor: pointer;
}

.fitpro-app .fitpro-score__name button:hover {
	color: var(--fp-accent);
}

/*
 * Whole-client facts — quiet for a fortnight, access about to lapse — read as
 * context under the name rather than as two more columns nobody can score.
 */
.fitpro-app .fitpro-score__notes {
	display: block;
	margin-top: 2px;
	font-family: var(--fp-font-body);
	font-size: 12px;
	font-weight: 400;
	letter-spacing: 0;
	text-transform: none;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-score__grid {
	display: grid;
	/* Count supplied inline by the component, so a seventh check needs no CSS. */
	grid-template-columns: repeat(var(--fp-score-cols, 6), minmax(0, 1fr));
	list-style: none;
	margin: 0;
	padding: 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-score__item {
	position: relative;
	min-width: 0;
}

.fitpro-app .fitpro-score__cell {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 4px;
	width: 100%;
	/* 44px, non-negotiable: this is the primary control on a phone. */
	min-height: var(--fp-tap-min);
	padding: var(--fp-space-2) 2px;
	border: 0;
	background: none;
	font: inherit;
	cursor: pointer;
}

.fitpro-app .fitpro-score__label {
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.4px;
	line-height: 1.2;
	text-transform: uppercase;
	color: var(--fp-muted);
	text-align: center;
	/* Breaks rather than widening its column; the tooltip carries the full word
	   anyway, and below the breakpoint there is a whole row to spell it out. */
	overflow-wrap: anywhere;
	hyphens: auto;
}

/* The word is the heading at full width; the glyph takes over when it will not
   fit. Exactly one of the pair is ever displayed. */
.fitpro-app .fitpro-score__glyph {
	display: none;
	align-items: center;
	justify-content: center;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-score__mark {
	display: inline-flex;
	align-items: center;
	justify-content: center;
}

/*
 * Two states, not four. Shading the crosses by severity looked more informative
 * and read worse: an amber cross is a third thing to decode, and it steals the
 * meaning of red, which should say "problem" everywhere in the grid without
 * qualification.
 */
.fitpro-app .fitpro-score__cell--ok .fitpro-score__mark {
	color: var(--fp-success);
}

.fitpro-app .fitpro-score__cell--fail .fitpro-score__mark {
	color: var(--fp-danger);
}

/*
 * The touch equivalent of the tooltip: a tapped cell opens this line under the
 * grid, and the line is itself the way through to fixing the thing. It names the
 * column in words, which is what makes an icon-only header row safe — nothing is
 * ever left to be guessed from a glyph alone.
 */
.fitpro-app .fitpro-score__said {
	display: block;
	width: 100%;
	margin-top: var(--fp-space-2);
	padding: var(--fp-space-2) var(--fp-space-3);
	border: 0;
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-score__said strong {
	display: block;
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.6px;
	text-transform: uppercase;
	color: var(--fp-accent);
}

.fitpro-app .fitpro-score__said span {
	font-size: 13px;
	color: var(--fp-body);
}

/*
 * The tip. Hidden with visibility rather than display so it can transition, and
 * so its layout is resolved before it is ever shown — a tip that reflows as it
 * appears reads as a glitch.
 *
 * Note there is no `overflow` anywhere above it. Setting either axis to
 * something other than `visible` makes the other compute to `auto` as well, so a
 * horizontal scroll guard on the wrapper would clip this vertically and every
 * explanation would silently vanish.
 */
.fitpro-app .fitpro-score__tip {
	position: absolute;
	left: 50%;
	bottom: calc(100% + 6px);
	z-index: 5;
	display: flex;
	flex-direction: column;
	gap: 2px;
	width: max-content;
	max-width: min(220px, 74vw);
	transform: translateX(-50%);
	padding: 8px 10px;
	border-radius: var(--fp-radius);
	background: var(--fp-navy);
	color: #fff;
	font-size: 12px;
	line-height: 1.4;
	text-align: left;
	visibility: hidden;
	opacity: 0;
	transition: opacity 0.12s ease;
	pointer-events: none;
}

.fitpro-app .fitpro-score__tip strong {
	font-size: 10px;
	letter-spacing: 0.6px;
	text-transform: uppercase;
	color: var(--fp-accent);
}

.fitpro-app .fitpro-score__item:hover .fitpro-score__tip,
.fitpro-app .fitpro-score__item:focus-within .fitpro-score__tip {
	visibility: visible;
	opacity: 1;
}

/*
 * A centred tip on an outer column would hang off the side of the card. The two
 * columns at each end anchor theirs to the inside edge instead — two, not one,
 * because a 220px tip centred on a 50px column reaches well past its neighbour.
 */
.fitpro-app .fitpro-score__item:nth-child(-n + 2) .fitpro-score__tip {
	left: 0;
	transform: none;
}

.fitpro-app .fitpro-score__item:nth-child(n + 5) .fitpro-score__tip {
	left: auto;
	right: 0;
	transform: none;
}

.fitpro-app .fitpro-score__rest {
	margin: var(--fp-space-3) 0 0;
	padding-top: var(--fp-space-3);
	border-top: 1px solid var(--fp-line);
	color: var(--fp-muted);
	font-size: 13px;
}

/*
 * Below the width where six words fit, the headings become icons and the grid
 * stays a grid.
 *
 * The alternative — stacking into six full-width rows — was built first and
 * thrown away: it is perfectly readable and it destroys the only thing the
 * scorecard is for, which is seeing a whole client in one glance. Six rows per
 * client is three screens of scrolling for a roster of three.
 */
@media (max-width: 560px) {
	/*
	 * Full-bleed to the card's edges. A 320px phone leaves 178px inside the
	 * card's padding, which is 29px a column — small enough that a mis-tap is
	 * likely. Reclaiming the padding gets each cell to 38px, and with a 57px row
	 * height that is a target worth aiming at. It is still under the 44px floor,
	 * which is why nothing here is destructive: the worst a mis-tap does is
	 * explain the wrong column, and the line it opens says which one it is.
	 */
	.fitpro-app .fitpro-score__grid,
	.fitpro-app .fitpro-score__name {
		margin-left: calc(var(--fp-space-5) * -1);
		margin-right: calc(var(--fp-space-5) * -1);
		border-radius: 0;
	}

	.fitpro-app .fitpro-score__label {
		/* Not `display: none` — it is still the button's visible text for a
		   screen reader that ignores aria-label, and the accessible name is
		   built from it either way. */
		position: absolute;
		width: 1px;
		height: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	.fitpro-app .fitpro-score__glyph {
		display: inline-flex;
	}

	.fitpro-app .fitpro-score__cell {
		padding: var(--fp-space-2) 0;
		gap: 6px;
	}
}

/*
 * No hover, at any width: the tooltip cannot be opened, so it is not offered.
 * The tapped-cell line does that job instead. This is a separate condition from
 * the width above on purpose — a tablet in landscape has room for the words and
 * still no way to hover them.
 */
@media (hover: none) {
	.fitpro-app .fitpro-score__tip {
		display: none;
	}
}

/*
 * The tapped cell keeps a visible marker while its line is open, so it is
 * obvious which of six icons the sentence underneath belongs to. A background
 * tint alone was invisible against the card — it needs the accent.
 */
.fitpro-app .fitpro-score__item.is-selected .fitpro-score__cell {
	background: var(--fp-surface);
	box-shadow: inset 0 -2px 0 var(--fp-accent);
}

.fitpro-app .fitpro-score__item.is-selected .fitpro-score__glyph {
	color: var(--fp-accent);
}

/* ---------------------------------------------------------------------------
 * Forms, tasks and the builder
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-muted {
	color: var(--fp-muted);
	font-size: 13px;
}

/*
 * An advisory the reader needs *before* they act, not an error. Amber rather
 * than red: nothing has gone wrong, and colouring it like a failure would make
 * a coach hesitate over a normal edit.
 */
.fitpro-app .fitpro-note {
	margin: 0 0 var(--fp-space-4);
	padding: var(--fp-space-3);
	border-left: 3px solid var(--fp-warning);
	border-radius: 0 var(--fp-radius) var(--fp-radius) 0;
	background: var(--fp-warning-wash);
	font-size: 13px;
	color: var(--fp-body);
}

/* --- the client's outstanding forms ------------------------------------- */

.fitpro-app .fitpro-tasks {
	list-style: none;
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-tasks li + li {
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-tasks__row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-3);
	width: 100%;
	min-height: var(--fp-tap-min);
	padding: var(--fp-space-3) 0;
	border: 0;
	background: none;
	font: inherit;
	color: inherit;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-tasks__body {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}

.fitpro-app .fitpro-tasks__title {
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-tasks__due {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-tasks__due.is-late {
	color: var(--fp-danger);
	font-weight: 700;
}

.fitpro-app .fitpro-tasks__go {
	flex: 0 0 auto;
	padding: 6px 14px;
	border-radius: var(--fp-radius-pill);
	background: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
	font-size: 11px;
	font-weight: 900;
	letter-spacing: 1px;
	text-transform: uppercase;
	white-space: nowrap;
}

/* --- a form being filled in ---------------------------------------------- */

.fitpro-app .fitpro-formsection + .fitpro-formsection {
	margin-top: var(--fp-space-5);
	padding-top: var(--fp-space-4);
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-formsection__title {
	margin: 0 0 var(--fp-space-3);
	font-family: var(--fp-font-heading);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

/* --- the builder --------------------------------------------------------- */

.fitpro-app .fitpro-qedit {
	margin-bottom: var(--fp-space-4);
	padding: var(--fp-space-4);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
}

.fitpro-app .fitpro-qedit__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	margin-bottom: var(--fp-space-2);
}

.fitpro-app .fitpro-qedit__num {
	font-family: var(--fp-font-heading);
	font-size: 13px;
	font-weight: 700;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-qedit__grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--fp-space-3);
}

@media (min-width: 640px) {
	.fitpro-app .fitpro-qedit__grid {
		grid-template-columns: 1fr 1fr;
	}
}

/* --- the form and assignment lists --------------------------------------- */

.fitpro-app .fitpro-formlist {
	list-style: none;
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-formlist li {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-3);
	padding: var(--fp-space-3) 0;
}

.fitpro-app .fitpro-formlist li + li {
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-formlist__body {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
	/* Grows and shrinks; the buttons wrap under it on a phone rather than
	   squeezing the title into an ellipsis. */
	flex: 1 1 200px;
}

.fitpro-app .fitpro-formlist__title {
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-formlist__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-2);
}

/*
 * The row's buttons are secondary and sit three-across. The global pill padding
 * (20px 50px 20px 30px) is built for a page's primary action and would put one
 * button per line here.
 */
.fitpro-app .fitpro-formlist__actions .fitpro-btn {
	padding: 10px 18px;
	font-size: 11px;
	letter-spacing: 1.5px;
}

.fitpro-app .fitpro-answerset {
	padding: var(--fp-space-3) 0;
}

.fitpro-app .fitpro-answerset + .fitpro-answerset {
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-answerset h4 {
	margin: 0 0 var(--fp-space-2);
	font-size: 14px;
}

/* --- per-client dashboard checks ----------------------------------------- */

.fitpro-app .fitpro-mutes {
	margin: var(--fp-space-4) 0 0;
	padding: var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-mutes legend {
	padding: 0 6px;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-mutes__help {
	margin: 0 0 var(--fp-space-3);
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-mutes__row {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
	/* The whole row is the hit area, not the 13px checkbox. */
	min-height: var(--fp-tap-min);
	cursor: pointer;
}

.fitpro-app .fitpro-mutes__row input {
	width: 18px;
	height: 18px;
	accent-color: var(--fp-accent);
}

/* ---------------------------------------------------------------------------
 * Progress photos
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-photos {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-2);
	align-items: center;
}

.fitpro-app .fitpro-photos__item {
	position: relative;
	display: block;
	/* Square, so three across still fits a 320px phone with the gaps. */
	width: 88px;
	height: 88px;
	border-radius: var(--fp-radius);
	overflow: hidden;
	background: var(--fp-surface);
	border: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-photos__item img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

.fitpro-app .fitpro-photos__placeholder {
	display: block;
	width: 100%;
	height: 100%;
	background: linear-gradient(135deg, var(--fp-surface), var(--fp-line));
}

/*
 * The remove control sits on the image rather than under it, and carries a
 * shadow scrim so it stays legible over a light photo — a white × on a pale
 * background is invisible exactly when somebody is trying to delete something
 * they did not mean to send.
 */
.fitpro-app .fitpro-photos__remove {
	position: absolute;
	top: 2px;
	right: 2px;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 26px;
	height: 26px;
	border: 0;
	border-radius: 50%;
	background: rgba(38, 45, 63, 0.82);
	color: #fff;
	font-size: 17px;
	line-height: 1;
	cursor: pointer;
}

.fitpro-app .fitpro-photos__add {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 2px;
	width: 88px;
	height: 88px;
	border: 1px dashed var(--fp-line);
	border-radius: var(--fp-radius);
	color: var(--fp-muted);
	cursor: pointer;
	font-size: 20px;
}

.fitpro-app .fitpro-photos__add:hover {
	border-color: var(--fp-accent);
	color: var(--fp-accent);
}

/* The real input is hidden; the label is the control. */
.fitpro-app .fitpro-photos__add input {
	position: absolute;
	width: 1px;
	height: 1px;
	overflow: hidden;
	clip-path: inset(50%);
}

/* Keyboard focus has to be visible on the label, since the input it wraps is
   clipped out of sight. */
.fitpro-app .fitpro-photos__add:focus-within {
	outline: 2px solid var(--fp-accent);
	outline-offset: 2px;
}

.fitpro-app .fitpro-photos__addlabel {
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.5px;
	text-transform: uppercase;
}

.fitpro-app .fitpro-photos__busy {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-photos__note {
	margin: var(--fp-space-2) 0 0;
	font-size: 12px;
	color: var(--fp-muted);
}

/* Read-only, in the coach's answer list: smaller, and each one opens full size. */
.fitpro-app .fitpro-photos--read .fitpro-photos__item {
	width: 72px;
	height: 72px;
}

@media (prefers-reduced-motion: reduce) {
	.fitpro-app *,
	.fitpro-boot__mark {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
	}
}

/* ---------------------------------------------------------------------------
 * Client health score
 *
 * Its own namespace, not `.fitpro-score__*` — that belongs to the attention
 * scorecard on the dashboard, and the two are different features that happen to
 * read the same tables. Bug 46 was a list class named after the first screen
 * that used it; naming this one after the panel it resembles would set up the
 * same trap for whoever writes the next rule.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-health__head {
	display: flex;
	align-items: baseline;
	gap: 12px;
	flex-wrap: wrap;
	padding: 4px 0 14px;
}

.fitpro-app .fitpro-health__value {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: 44px;
	line-height: 1;
	color: var(--fp-heading);
}

/*
 * The band colours the words, never the number.
 *
 * A big red 48 reads as an alarm about the person. The number is the
 * measurement and the label is the interpretation, so the interpretation is
 * what carries the colour — and the label is always present, so the meaning
 * never depends on colour alone.
 */
.fitpro-app .fitpro-health__band {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: 13px;
	letter-spacing: 2px;
	text-transform: uppercase;
	padding: 5px 12px;
	border-radius: 100px;
}

.fitpro-app .fitpro-health--on_track .fitpro-health__band {
	background: color-mix(in srgb, var(--fp-success) 12%, transparent);
	color: var(--fp-success);
}

.fitpro-app .fitpro-health--watch .fitpro-health__band {
	background: rgba(var(--fp-accent-rgb), 0.1);
	color: var(--fp-accent);
}

.fitpro-app .fitpro-health--at_risk .fitpro-health__band {
	background: var(--fp-danger-soft);
	color: var(--fp-danger);
}

.fitpro-app .fitpro-health__window,
.fitpro-app .fitpro-health__note {
	font-size: 13px;
	color: var(--fp-body);
	margin: 0;
}

.fitpro-app .fitpro-health__empty {
	margin: 0 0 12px;
	color: var(--fp-body);
	font-size: 14px;
}

.fitpro-app .fitpro-health__trend {
	padding-bottom: 14px;
}

/*
 * `preserveAspectRatio: none` stretches the viewBox to the box, which is what
 * lets a 100-unit-wide sketch fill any column. It also stretches the stroke,
 * so the polyline sets `vector-effect: non-scaling-stroke` — without it the
 * line renders as a hairline wedge, thick at one end.
 */
.fitpro-app .fitpro-health__spark {
	display: block;
	width: 100%;
	/*
	 * Capped for the same reason the component bars are, and for one more: the
	 * y-axis is fixed at 0-100 on purpose, so stretching six points across
	 * 900px flattens a 25-point climb into a horizontal line. Narrowing the box
	 * restores the slope without touching the scale — a fitted axis would make
	 * a wobble between 78 and 82 look like a crisis, which is the misreading
	 * this whole feature exists to avoid.
	 */
	max-width: 460px;
	height: 40px;
	overflow: visible;
}

.fitpro-app .fitpro-health__spark polyline {
	stroke: var(--fp-accent);
	stroke-width: 2;
	stroke-linejoin: round;
	stroke-linecap: round;
}

.fitpro-app .fitpro-health__list {
	list-style: none;
	margin: 0;
	padding: 0;
	border-top: 1px solid var(--fp-line);
}

/*
 * The bar is capped rather than fluid.
 *
 * At `1fr` on a 1440px card the track ran the full width and parked its
 * percentage a hand's breadth away from it, so the eye had to travel to pair a
 * number with its bar. A comparison between four rows only works if all four
 * are the same length, and 320px is enough to read a 5% sliver.
 */
.fitpro-app .fitpro-health__row {
	display: grid;
	grid-template-columns: 104px minmax(0, 320px) 34px;
	justify-content: start;
	align-items: center;
	gap: 10px;
	padding: 10px 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-health__label {
	font-weight: 600;
	font-size: 14px;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-health__bar {
	display: block;
	height: 8px;
	border-radius: 100px;
	background: var(--fp-line-strong);
	overflow: hidden;
}

.fitpro-app .fitpro-health__bar > span {
	display: block;
	height: 100%;
	border-radius: 100px;
	background: var(--fp-accent);
}

.fitpro-app .fitpro-health--on_track .fitpro-health__bar > span {
	background: var(--fp-success);
}

.fitpro-app .fitpro-health--at_risk .fitpro-health__bar > span {
	background: var(--fp-danger);
}

.fitpro-app .fitpro-health__pct {
	font-variant-numeric: tabular-nums;
	text-align: right;
	font-size: 14px;
	color: var(--fp-heading);
}

/*
 * A component nobody asked for is stated in words across the whole row.
 *
 * An empty bar would be indistinguishable from a score of zero, which is the
 * single misreading this feature exists to prevent.
 */
.fitpro-app .fitpro-health__na {
	grid-column: 2 / span 2;
	font-size: 13px;
	color: var(--fp-body);
	font-style: italic;
}

.fitpro-app .fitpro-health__tally {
	grid-column: 1 / -1;
	font-size: 12px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-health__tally:empty {
	display: none;
}

/*
 * Below 420px the label column would squeeze "Measurements" into a two-word
 * stack beside a 40px bar. The row becomes two lines instead: name and score on
 * top, the bar under them at full width.
 */
@media (max-width: 420px) {
	.fitpro-app .fitpro-health__row {
		grid-template-columns: 1fr 34px;
	}

	.fitpro-app .fitpro-health__bar,
	.fitpro-app .fitpro-health__na {
		grid-column: 1 / -1;
	}

	/*
	 * The one explicit placement, and it is load-bearing.
	 *
	 * The children are label, bar, percentage, tally in that order, so
	 * auto-placement puts the full-width bar on row two and then drops the
	 * percentage onto row three — a number floating under its own bar with
	 * nothing beside it. Pinning it to row one beside the label is enough; the
	 * bar then takes row two and the tally row three, both by auto-flow, so
	 * nothing here depends on a row number that changes when a component turns
	 * out to be not applicable.
	 */
	.fitpro-app .fitpro-health__pct {
		grid-row: 1;
		grid-column: 2;
	}

	.fitpro-app .fitpro-health__value {
		font-size: 36px;
	}
}

/* ---------------------------------------------------------------------------
 * Progress photos
 *
 * Two blocks: the labelled pose slots inside a check-in form, and the gallery
 * that collects them. `.fitpro-photos` above is the older unposed field and
 * stays as it is — a question that just wants some pictures attached.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-poses {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: 10px;
}

.fitpro-app .fitpro-poses__slot {
	display: flex;
	flex-direction: column;
	gap: 6px;
	min-width: 0;
}

.fitpro-app .fitpro-poses__label {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: 12px;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--fp-body);
}

.fitpro-app .fitpro-poses__shot,
.fitpro-app .fitpro-poses__add {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 4px;
	/*
	 * 3:4 rather than square. A person photographed head to knee is portrait,
	 * and a square thumbnail crops exactly the part being compared — the same
	 * mistake the exercise player was making with ymove's vertical clips.
	 */
	aspect-ratio: 3 / 4;
	border-radius: var(--fp-radius);
	overflow: hidden;
	background: var(--fp-surface);
	border: 1px dashed var(--fp-line-strong);
}

.fitpro-app .fitpro-poses__shot {
	border-style: solid;
	border-color: var(--fp-line);
}

.fitpro-app .fitpro-poses__shot img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.fitpro-app .fitpro-poses__add {
	cursor: pointer;
	color: var(--fp-body);
	font-size: 22px;
	/* The label and the box are the tap target; the input itself is hidden. */
	min-height: var(--fp-tap-min);
}

.fitpro-app .fitpro-poses__add input {
	position: absolute;
	width: 1px;
	height: 1px;
	opacity: 0;
	pointer-events: none;
}

.fitpro-app .fitpro-poses__addlabel {
	font-size: 12px;
	letter-spacing: 1px;
	text-transform: uppercase;
}

.fitpro-app .fitpro-poses__placeholder {
	width: 100%;
	height: 100%;
	background: var(--fp-line);
}

/* --- the gallery ------------------------------------------------------- */

.fitpro-app .fitpro-gallery__add {
	padding-bottom: 14px;
	border-bottom: 1px solid var(--fp-line);
	margin-bottom: 14px;
}

.fitpro-app .fitpro-gallery__addrow {
	display: flex;
	align-items: flex-end;
	gap: 12px;
	flex-wrap: wrap;
}

.fitpro-app .fitpro-gallery__date {
	display: flex;
	flex-direction: column;
	gap: 4px;
	font-size: 12px;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-body);
}

.fitpro-app .fitpro-gallery__date input {
	font-family: var(--fp-font-body);
	font-size: 14px;
	padding: 10px 12px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-heading);
	text-transform: none;
	letter-spacing: 0;
}

/*
 * A date label column and one column per pose. The count is fixed at four
 * because the pose set is closed and this is not a place a fourth column can
 * appear without a schema change; a custom property would imply otherwise.
 */
.fitpro-app .fitpro-gallery__head,
.fitpro-app .fitpro-gallery__row {
	display: grid;
	grid-template-columns: 92px repeat(3, minmax(0, 1fr));
	gap: 8px;
	align-items: center;
}

.fitpro-app .fitpro-gallery__head {
	padding-bottom: 6px;
}

.fitpro-app .fitpro-gallery__head span {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: 11px;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--fp-body);
	text-align: center;
}

.fitpro-app .fitpro-gallery__row {
	padding: 8px 0;
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-gallery__row > .fitpro-gallery__date {
	display: block;
	font-size: 13px;
	letter-spacing: 0;
	text-transform: none;
	color: var(--fp-heading);
	font-weight: 600;
}

.fitpro-app .fitpro-gallery__cell {
	display: block;
	width: 100%;
	padding: 0;
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	overflow: hidden;
	background: var(--fp-surface);
	aspect-ratio: 3 / 4;
	cursor: pointer;
}

.fitpro-app .fitpro-gallery__cell img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

.fitpro-app .fitpro-gallery__cell--empty {
	display: flex;
	align-items: center;
	justify-content: center;
	border-style: dashed;
	cursor: default;
}

.fitpro-app .fitpro-gallery__missing {
	font-size: 11px;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

/* --- compare ----------------------------------------------------------- */

/*
 * Two columns at every width, including a 320px phone.
 *
 * Stacking them below a breakpoint is the obvious responsive move and it
 * destroys the feature: two photographs of the same person eight weeks apart
 * are only a comparison while they are beside each other, and one above the
 * other is two photographs. 150px each is small and it is still a comparison.
 */
.fitpro-app .fitpro-compare {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 10px;
}

.fitpro-app .fitpro-compare__side {
	display: flex;
	flex-direction: column;
	gap: 8px;
	min-width: 0;
}

.fitpro-app .fitpro-compare__side img {
	width: 100%;
	border-radius: var(--fp-radius);
	border: 1px solid var(--fp-line);
	/* `contain`, never `cover`: cropping to a shared box would trim different
	   amounts off two photographs and invent a difference between them. */
	object-fit: contain;
	background: var(--fp-navy);
	max-height: 60vh;
}

.fitpro-app .fitpro-compare__pick {
	font-family: var(--fp-font-body);
	font-size: 13px;
	padding: 8px 10px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-heading);
	width: 100%;
}

.fitpro-app .fitpro-compare__none {
	margin: 0;
	padding: 24px 8px;
	text-align: center;
	font-size: 13px;
	color: var(--fp-body);
	border: 1px dashed var(--fp-line-strong);
	border-radius: var(--fp-radius);
}

/* --- lightbox ---------------------------------------------------------- */

.fitpro-app .fitpro-lightbox {
	position: fixed;
	inset: 0;
	z-index: 60;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 16px;
	background: var(--fp-scrim-strong);
}

.fitpro-app .fitpro-lightbox__inner {
	display: flex;
	flex-direction: column;
	gap: 12px;
	max-width: 720px;
	width: 100%;
}

.fitpro-app .fitpro-lightbox__inner img {
	width: 100%;
	/* `dvh`, not `vh`: iOS Safari counts the collapsing URL bar in `vh`, so the
	   action bar below would sit under the chrome and be unreachable. */
	max-height: 74dvh;
	object-fit: contain;
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-lightbox__bar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 10px;
	flex-wrap: wrap;
	color: var(--fp-inverse);
	font-size: 14px;
}

@media (max-width: 480px) {
	.fitpro-app .fitpro-gallery__head,
	.fitpro-app .fitpro-gallery__row {
		grid-template-columns: 64px repeat(3, minmax(0, 1fr));
		gap: 5px;
	}

	.fitpro-app .fitpro-gallery__row > .fitpro-gallery__date {
		font-size: 11px;
	}
}

/* ---------------------------------------------------------------------------
 * Body-measurement chart
 *
 * Drawn as plain SVG. One line, one trend line and three gridlines is not worth
 * a charting library — the same call the tab-bar icons made about a CDN.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-chart {
	margin: 4px 0 8px;
}

/*
 * The viewBox is 640×200 and the box is fluid, so the drawing scales. Stroke
 * widths would scale with it — a thin line on a phone and a fat one on a
 * desktop — which is what `vector-effect: non-scaling-stroke` on each polyline
 * prevents.
 */
.fitpro-app .fitpro-chart__svg {
	display: block;
	width: 100%;
	height: auto;
	overflow: visible;
}

.fitpro-app .fitpro-chart__grid {
	stroke: var(--fp-line);
	stroke-width: 1;
	vector-effect: non-scaling-stroke;
}

/*
 * No `font-size` here, deliberately.
 *
 * The component sets it as an SVG presentation attribute, sized in viewBox units
 * so it survives the drawing being scaled down on a phone. A `font-size` in this
 * rule would win — CSS beats a presentation attribute — and silently pin every
 * label back to 11 units, which renders at about five pixels in a 293px column.
 * The same shape as a duplicate selector quietly overriding the rule that
 * carried the reasoning.
 */
.fitpro-app .fitpro-chart__tick {
	fill: var(--fp-muted);
	font-family: var(--fp-font-body);
}

/*
 * The raw series is thin and the 7-day average is heavy, which is the opposite
 * of the obvious choice and the right one: the average is the thing to read and
 * the daily readings are the evidence behind it. Making the jagged line the
 * loud one is what turns a kilo of water into a story.
 */
.fitpro-app .fitpro-chart__line {
	stroke: var(--fp-line-strong);
	stroke-width: 1.5;
	stroke-linejoin: round;
	stroke-linecap: round;
}

.fitpro-app .fitpro-chart__trend {
	stroke: var(--fp-accent);
	stroke-width: 2.5;
	stroke-linejoin: round;
	stroke-linecap: round;
}

.fitpro-app .fitpro-chart__dot {
	fill: var(--fp-accent);
}

.fitpro-app .fitpro-chart__note {
	margin: 6px 0 0;
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-chart__change {
	display: flex;
	align-items: baseline;
	gap: 10px;
	flex-wrap: wrap;
	margin: 4px 0 10px;
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: 18px;
	color: var(--fp-heading);
}

/*
 * Direction is coloured, and neither colour means "good".
 *
 * Down is not success — a client building muscle wants the weight line going
 * up, and a waist measurement and a bicep measurement want opposite things.
 * Green-for-down would congratulate half the roster for the wrong thing, so
 * both directions use the same neutral accent and only the words say which way
 * it went.
 */
.fitpro-app .fitpro-chart__change--up,
.fitpro-app .fitpro-chart__change--down {
	color: var(--fp-heading);
}

.fitpro-app .fitpro-chart__change--level {
	color: var(--fp-body);
}

.fitpro-app .fitpro-chart__from {
	font-family: var(--fp-font-body);
	font-weight: 400;
	font-size: 13px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-chart__actions {
	margin-top: 12px;
	padding-top: 12px;
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-chart__form {
	margin-top: 12px;
	padding-top: 12px;
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-chart__fields {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
	gap: 10px;
}

/* ---------------------------------------------------------------------------
 * Streaks
 *
 * A strip of chips, not cards. One number per habit does not need a titled box
 * of its own, and giving it one would make the smallest thing on the client's
 * Today screen look like the biggest.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-streaks {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
	gap: 10px;
	margin-bottom: 16px;
}

.fitpro-app .fitpro-streak {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: 12px 14px;
	border-radius: var(--fp-radius);
	background: var(--fp-accent-soft);
	border: 1px solid transparent;
}

/*
 * A broken run is quiet, not red.
 *
 * Red is what the app uses for "this is a problem", and a lapsed streak is not
 * one — the client is being shown their own best run and invited to start
 * again. Colouring it like a failure is how a motivational feature turns into
 * one more thing telling somebody they are behind.
 */
.fitpro-app .fitpro-streak--broken {
	background: transparent;
	border-color: var(--fp-line);
}

.fitpro-app .fitpro-streak__habit {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: 11px;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--fp-body);
}

.fitpro-app .fitpro-streak__value {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: 24px;
	line-height: 1.1;
	color: var(--fp-accent);
}

.fitpro-app .fitpro-streak--broken .fitpro-streak__value {
	color: var(--fp-body);
	font-size: 18px;
}

.fitpro-app .fitpro-streak__note {
	font-size: 12px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-streak__note:empty {
	display: none;
}

/* ---------------------------------------------------------------------------
 * Activity feed
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-feed {
	list-style: none;
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-feed__row {
	display: grid;
	grid-template-columns: 10px minmax(0, 1fr) auto;
	align-items: baseline;
	gap: 10px;
	padding: 9px 0;
	border-bottom: 1px solid var(--fp-line);
	font-size: 14px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-feed__row:last-child {
	border-bottom: 0;
}

/*
 * The dot is the only thing carrying the event kind, and it is deliberately not
 * the only signal: the sentence names what happened in words, so the colour is
 * a scanning aid rather than the meaning. A legend nobody reads is what happens
 * when a colour has to be decoded.
 */
.fitpro-app .fitpro-feed__dot {
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--fp-line-strong);
	align-self: center;
}

.fitpro-app .fitpro-feed--training .fitpro-feed__dot {
	background: var(--fp-accent);
}

.fitpro-app .fitpro-feed--nutrition .fitpro-feed__dot {
	background: var(--fp-success);
}

/*
 * `--fp-disc`, not `--fp-navy`, for the same reason the identity circle uses it:
 * navy goes darker in dark mode because it is a backdrop colour, and an 8px dot
 * painted in it against the card behind is 1.21:1 — a bullet nobody can see.
 * This one inverts along with the circle.
 */
.fitpro-app .fitpro-feed--checkin .fitpro-feed__dot,
.fitpro-app .fitpro-feed--measurement .fitpro-feed__dot {
	background: var(--fp-disc);
}

.fitpro-app .fitpro-feed__text {
	min-width: 0;
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-feed__who {
	color: var(--fp-heading);
}

.fitpro-app .fitpro-feed__text strong {
	color: var(--fp-heading);
	font-weight: 600;
}

.fitpro-app .fitpro-feed__detail {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-feed__when {
	font-size: 12px;
	color: var(--fp-muted);
	white-space: nowrap;
}

/*
 * Below 480px the timestamp moves under the sentence rather than competing with
 * it for a 300px row. "2h ago" pinned to the right of a wrapping three-line
 * sentence reads as belonging to whichever line it happens to sit beside.
 */
@media (max-width: 480px) {
	.fitpro-app .fitpro-feed__row {
		grid-template-columns: 10px minmax(0, 1fr);
		row-gap: 2px;
	}

	.fitpro-app .fitpro-feed__when {
		grid-column: 2;
	}
}

/* ---------------------------------------------------------------------------
 * Daily habits
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-habits,
.fitpro-app .fitpro-habit-admin {
	list-style: none;
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-habit {
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-habit:last-child {
	border-bottom: 0;
}

/*
 * The whole row is the tap target, not the box.
 *
 * A checkbox is about 20px, which is under half the 44px floor the in-workout
 * logger settled on — and this is tapped one-handed, several times a day, often
 * while doing something else. The button carries the padding so the hit area is
 * the full width of the card.
 */
.fitpro-app .fitpro-habit__hit {
	display: flex;
	align-items: center;
	gap: 12px;
	width: 100%;
	min-height: var(--fp-tap-min);
	padding: 10px 2px;
	background: none;
	border: 0;
	text-align: left;
	cursor: pointer;
	font-family: var(--fp-font-body);
	color: inherit;
}

.fitpro-app .fitpro-habit__box {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 26px;
	height: 26px;
	border-radius: 8px;
	border: 2px solid var(--fp-line-strong);
	background: var(--fp-card);
	color: var(--fp-inverse);
	font-size: 15px;
	font-weight: 700;
}

.fitpro-app .fitpro-habit--done .fitpro-habit__box {
	background: var(--fp-accent);
	border-color: var(--fp-accent);
}

.fitpro-app .fitpro-habit__body {
	display: flex;
	flex-direction: column;
	gap: 1px;
	min-width: 0;
	flex: 1 1 auto;
}

.fitpro-app .fitpro-habit__title {
	font-size: 15px;
	color: var(--fp-heading);
	overflow-wrap: anywhere;
}

/*
 * A done habit is not struck through.
 *
 * Strikethrough reads as cancelled, and it makes the line harder to read at the
 * exact moment the client is checking they have not missed anything. The filled
 * box and the muted colour are enough, and the button's `aria-pressed` carries
 * the state for anyone not seeing either.
 */
.fitpro-app .fitpro-habit--done .fitpro-habit__title {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-habit__note {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-habit__streak {
	flex: 0 0 auto;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-accent);
}

/* A habit not asked for today: present, so the list keeps its length, but out
   of the way and not tappable. */
.fitpro-app .fitpro-habit--off {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 10px 2px;
	opacity: 0.55;
}

.fitpro-app .fitpro-habit-admin li {
	display: flex;
	align-items: baseline;
	gap: 10px;
	padding: 8px 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-habit-admin li .fitpro-habit__title {
	flex: 1 1 auto;
}

.fitpro-app .fitpro-habit-add {
	margin-top: 14px;
	padding-top: 14px;
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-habit-days {
	border: 0;
	margin: 0 0 10px;
	padding: 0;
}

.fitpro-app .fitpro-habit-days legend {
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-body);
	padding: 0;
}

.fitpro-app .fitpro-habit-days__help {
	margin: 2px 0 8px;
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-habit-days__row {
	display: flex;
	gap: 6px;
	flex-wrap: wrap;
}

/*
 * Seven day toggles, each exactly at the 44px tap floor.
 *
 * They wrap to 5 + 2 on a 320px phone — the card leaves 238px of usable width
 * there, measured, not the 316px the whole row would need. `flex-wrap` handles
 * it and two rows of readable day names is the better half of that trade; the
 * alternative is one row of single letters with two Ts and two Ss in it.
 */
.fitpro-app .fitpro-habit-days__day {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 2px;
	width: 40px;
	min-height: var(--fp-tap-min);
	border-radius: var(--fp-radius);
	border: 1px solid var(--fp-line-strong);
	font-size: 12px;
	cursor: pointer;
	background: var(--fp-card);
}

.fitpro-app .fitpro-habit-days__day:has(input:checked) {
	background: var(--fp-accent-soft);
	border-color: var(--fp-accent);
	color: var(--fp-accent);
	font-weight: 700;
}

.fitpro-app .fitpro-habit-days__day input {
	/* The label is the control; the box itself would be a second, smaller one. */
	position: absolute;
	width: 1px;
	height: 1px;
	opacity: 0;
	pointer-events: none;
}

/* ---------------------------------------------------------------------------
 * Session detail: prescribed against performed
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-logdetail__summary {
	margin: 0 0 10px;
	font-size: 14px;
	color: var(--fp-body);
}

/*
 * Skipped exercises are called out above the list, in the app's alert colour,
 * because they are the one thing on this screen a coach must not scroll past.
 */
.fitpro-app .fitpro-logdetail__missed {
	margin: 0 0 12px;
	padding: 8px 12px;
	border-radius: var(--fp-radius);
	background: var(--fp-danger-soft);
	color: var(--fp-danger);
	font-size: 13px;
	font-weight: 600;
}

.fitpro-app .fitpro-logdetail__clientnote {
	margin: 0 0 12px;
	padding: 10px 12px;
	border-left: 3px solid var(--fp-line-strong);
	background: var(--fp-surface);
	font-size: 14px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-logexs {
	list-style: none;
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-logex {
	padding: 12px 0;
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-logex--skipped {
	opacity: 0.72;
}

.fitpro-app .fitpro-logex__head {
	display: flex;
	align-items: center;
	gap: 10px;
}

.fitpro-app .fitpro-logex__thumb {
	flex: 0 0 auto;
	width: 44px;
	height: 44px;
	border-radius: var(--fp-radius);
	object-fit: cover;
	background: var(--fp-surface);
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-logex__names {
	display: flex;
	flex-direction: column;
	gap: 1px;
	min-width: 0;
	flex: 1 1 auto;
}

.fitpro-app .fitpro-logex__title {
	font-weight: 600;
	color: var(--fp-heading);
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-logex__target,
.fitpro-app .fitpro-logex__note {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-logex__note {
	margin: 8px 0 0;
	padding: 8px 10px;
	border-radius: var(--fp-radius);
	background: var(--fp-accent-soft);
	color: var(--fp-body);
	font-size: 13px;
}

.fitpro-app .fitpro-logex__flag {
	flex: 0 0 auto;
	font-size: 10px;
	font-weight: 800;
	letter-spacing: 1px;
	text-transform: uppercase;
	padding: 3px 8px;
	border-radius: var(--fp-radius-pill);
	background: var(--fp-line);
	color: var(--fp-body);
}

.fitpro-app .fitpro-logex--skipped .fitpro-logex__flag {
	background: var(--fp-danger-soft);
	color: var(--fp-danger);
}

/* --- the set table --------------------------------------------------- */

.fitpro-app .fitpro-logsets {
	margin-top: 10px;
}

.fitpro-app .fitpro-logsets__head,
.fitpro-app .fitpro-logset {
	display: grid;
	grid-template-columns: 62px 1fr 1fr 1fr;
	gap: 8px;
	align-items: center;
}

.fitpro-app .fitpro-logsets__head {
	padding-bottom: 4px;
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-logset {
	padding: 6px 0;
	border-top: 1px solid var(--fp-line);
	font-size: 14px;
}

.fitpro-app .fitpro-logset--missed {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-logset__no {
	font-size: 12px;
	color: var(--fp-muted);
}

/*
 * The performed number is the loud one and the target sits under it, greyed.
 *
 * The other way round — target first — reads as a plan rather than a record,
 * and the coach opening this screen already knows what they asked for. What
 * they came for is what happened.
 */
.fitpro-app .fitpro-logset__cell {
	display: flex;
	flex-direction: column;
	line-height: 1.15;
}

.fitpro-app .fitpro-logset__done {
	font-weight: 600;
	color: var(--fp-heading);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-logset__target {
	font-size: 11px;
	color: var(--fp-muted);
	font-variant-numeric: tabular-nums;
}

/*
 * Under is the alert colour; over is the app's accent, not green.
 *
 * Over target is not automatically good — 32.5kg against a prescribed 30 may be
 * a client ignoring a deload — so it is marked as *different*, not as a win.
 * Only "under" gets the colour that means "look at this".
 */
.fitpro-app .fitpro-logset--under .fitpro-logset__done {
	color: var(--fp-danger);
}

.fitpro-app .fitpro-logset--over .fitpro-logset__done {
	color: var(--fp-accent);
}

.fitpro-app .fitpro-logset__effort {
	font-size: 12px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-logset__note {
	grid-column: 1 / -1;
	font-size: 12px;
	color: var(--fp-body);
	font-style: italic;
	padding-top: 2px;
}

/* --- the food diary ---------------------------------------------------- */

.fitpro-app .fitpro-macros {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
	gap: 10px;
	margin-bottom: 14px;
}

.fitpro-app .fitpro-macro {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: 10px 12px;
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
}

.fitpro-app .fitpro-macro__name {
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-macro__value {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: 22px;
	color: var(--fp-heading);
	line-height: 1;
}

.fitpro-app .fitpro-macro__unit {
	font-size: 13px;
	margin-left: 1px;
}

.fitpro-app .fitpro-macro__bar {
	display: block;
	height: 5px;
	border-radius: 100px;
	background: var(--fp-line-strong);
	overflow: hidden;
	margin-top: 4px;
}

.fitpro-app .fitpro-macro__bar > span {
	display: block;
	height: 100%;
	background: var(--fp-accent);
}

.fitpro-app .fitpro-macro__target {
	font-size: 11px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-meals,
.fitpro-app .fitpro-meal__items {
	list-style: none;
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-meal {
	padding: 10px 0;
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-meal__head {
	display: flex;
	justify-content: space-between;
	gap: 10px;
	margin-bottom: 4px;
}

.fitpro-app .fitpro-meal__name {
	font-weight: 600;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-meal__kcal {
	font-size: 13px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-meal__items li {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	gap: 2px 10px;
	padding: 4px 0;
	font-size: 13px;
}

.fitpro-app .fitpro-meal__item {
	color: var(--fp-heading);
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-meal__item em {
	color: var(--fp-muted);
	font-style: normal;
}

.fitpro-app .fitpro-meal__qty,
.fitpro-app .fitpro-meal__macros {
	font-size: 12px;
	color: var(--fp-muted);
	white-space: nowrap;
}

.fitpro-app .fitpro-meal__macros {
	grid-column: 1 / -1;
	white-space: normal;
}

.fitpro-app .fitpro-meal__note {
	margin: 6px 0 0;
	font-size: 12px;
	font-style: italic;
	color: var(--fp-body);
}

/*
 * A meal's own four figures, above its components.
 *
 * Bigger than the per-item row beneath it, because this is the number somebody
 * reads and those are the working. The share line under it is the same figures
 * against the day's target.
 */
.fitpro-app .fitpro-meal__totals {
	margin-top: 2px;
	color: var(--fp-heading);
	font-weight: 600;
}

/*
 * Four columns, each a figure with its share of the day centred under it.
 * `minmax(0, 1fr)`, not `1fr`, so a four-digit calorie figure cannot widen its
 * column and push the rings off centre -- bug 59's automatic minimum.
 */
.fitpro-app .fitpro-meal__shares {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--fp-space-2);
	margin: 4px 0 8px;
}

.fitpro-app .fitpro-meal__share {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 6px;
	min-width: 0;
}

.fitpro-app .fitpro-meal__pct {
	font-size: 11px;
	font-weight: 700;
	color: var(--fp-heading);
	font-variant-numeric: tabular-nums;
}

/* The component is the way into its amount; it reads as text until pointed at. */
.fitpro-app .fitpro-meal__item--edit {
	padding: 0;
	border: 0;
	background: none;
	font: inherit;
	text-align: left;
	cursor: pointer;
	text-decoration: underline dotted var(--fp-line-strong);
	text-underline-offset: 3px;
}

.fitpro-app .fitpro-meal__item--edit:hover,
.fitpro-app .fitpro-meal__item--edit:focus-visible {
	color: var(--fp-accent);
}

/*
 * The remove control sits in its own column on the item's first line, placed
 * explicitly: auto-placement puts it on a third row under the macros, because
 * the macros span the whole row (bug 48's shape).
 */
.fitpro-app .fitpro-meal__items li {
	grid-template-columns: minmax(0, 1fr) auto auto;
	align-items: start;
}

.fitpro-app .fitpro-meal__remove {
	grid-row: 1;
	grid-column: 3;
	width: 28px;
	height: 28px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: var(--fp-surface);
	color: var(--fp-muted);
	cursor: pointer;
}

.fitpro-app .fitpro-meal__remove:hover,
.fitpro-app .fitpro-meal__remove:focus-visible {
	color: var(--fp-danger);
}

/* --- Copying foods from a meal ------------------------------------------ */

/* The meal's weight and the clipboard share the right of the heading. */
.fitpro-app .fitpro-meal__head {
	align-items: center;
}

.fitpro-app .fitpro-meal__head-end {
	display: inline-flex;
	align-items: center;
	gap: var(--fp-space-2);
}

/*
 * 36px, like the Move button beside it on the next line: a tool on a record,
 * not the thing being read. Pressed, it takes the accent, which is the only
 * sign besides the tick boxes that the meal is in a different mode.
 */
.fitpro-app .fitpro-meal__copy {
	width: 36px;
	height: 36px;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: var(--fp-surface);
	color: var(--fp-body);
	cursor: pointer;
}

.fitpro-app .fitpro-meal__copy:hover,
.fitpro-app .fitpro-meal__copy:focus-visible {
	color: var(--fp-accent);
}

.fitpro-app .fitpro-meal__copy[aria-pressed='true'] {
	background: var(--fp-accent);
	color: var(--fp-accent-contrast);
}

/* The whole name is the tick box's label, so the target is the row, not a 20px square. */
.fitpro-app .fitpro-meal__pick {
	display: flex;
	align-items: flex-start;
	gap: var(--fp-space-2);
	cursor: pointer;
}

.fitpro-app .fitpro-meal__pick input {
	flex: none;
	width: 20px;
	height: 20px;
	margin: 0;
	accent-color: var(--fp-accent);
}

.fitpro-app .fitpro-meal__copy-panel {
	margin-top: var(--fp-space-3);
	padding: var(--fp-space-3);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
}

.fitpro-app .fitpro-meal__copy-bar {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-meal__copy-bar .fitpro-field__help {
	margin: 0 0 var(--fp-space-2);
}

.fitpro-app .fitpro-meal__copy-bar .fitpro-btn {
	flex: none;
	min-height: 36px;
	padding: 6px 14px;
	font-size: 11px;
	letter-spacing: 2px;
}

/* --- a feed row that leads somewhere ----------------------------------- */

.fitpro-app .fitpro-feed__row--open {
	cursor: pointer;
}

.fitpro-app .fitpro-feed__row--open:hover,
.fitpro-app .fitpro-feed__row--open:focus-visible {
	background: var(--fp-surface);
}

.fitpro-app .fitpro-sheet__title {
	margin: 0 0 12px;
	padding-right: 32px;
	font-size: 18px;
}

@media (max-width: 480px) {
	.fitpro-app .fitpro-logsets__head,
	.fitpro-app .fitpro-logset {
		grid-template-columns: 52px 1fr 1fr;
	}

	/* Effort drops to its own line rather than squeezing a fourth column into
	   a 320px sheet; it is the least urgent of the four. */
	.fitpro-app .fitpro-logset__effort {
		grid-column: 2 / -1;
		font-size: 11px;
	}
}

/* --- session history --------------------------------------------------- */

.fitpro-app .fitpro-sessions {
	list-style: none;
	margin: 0;
	padding: 0;
}

.fitpro-app .fitpro-session {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	min-height: var(--fp-tap-min);
	padding: 10px 2px;
	background: none;
	border: 0;
	border-top: 1px solid var(--fp-line);
	text-align: left;
	cursor: pointer;
	font-family: var(--fp-font-body);
	color: inherit;
}

.fitpro-app .fitpro-sessions li:first-child .fitpro-session {
	border-top: 0;
}

.fitpro-app .fitpro-session__body {
	display: flex;
	flex-direction: column;
	gap: 1px;
	min-width: 0;
	flex: 1 1 auto;
}

.fitpro-app .fitpro-session__title {
	font-weight: 600;
	color: var(--fp-heading);
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-session__meta {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-session__go {
	flex: 0 0 auto;
	color: var(--fp-muted);
	font-size: 20px;
	line-height: 1;
}

/* ---------------------------------------------------------------------------
 * Terms, privacy policy and signatures
 * ------------------------------------------------------------------------- */

/*
 * A card that is asking for something.
 *
 * Orange rather than red: an unsigned agreement is an outstanding task, not a
 * fault, and the client did nothing wrong. Red here would read as an error on a
 * screen whose first job is to be reassuring.
 */
.fitpro-app .fitpro-card--attention {
	border-color: var(--fp-accent);
	background: var(--fp-accent-soft);
}

/*
 * A card that is asking for something, at the top of a screen full of cards.
 *
 * `.fitpro-card` carries no margin at all — the screens that stack cards space
 * them with explicit 16px spacers, which is fine while somebody remembers and
 * is exactly what was missed here: the outstanding-agreements card sat flush
 * against whatever followed it, so the one box on the screen that is asking for
 * a decision read as part of the panel underneath it.
 *
 * The margin lives on the card rather than in the screen, because this is the
 * card that must not be able to lose it. Its own class, not `--attention`,
 * which is a colour and belongs anywhere a card is asking for something.
 */
.fitpro-app .fitpro-prompt {
	margin-bottom: var(--fp-space-4);
}

/*
 * Why a consent is not on offer yet, standing where its button would be.
 *
 * Deliberately not a button and not the state pill either. It is a sentence
 * naming the document that has to be read first — the *Read and sign* control
 * for it is already in this same card, a few lines up, and a second copy here
 * would read as a second, different task.
 */
.fitpro-app .fitpro-consents__blocked {
	max-width: 26ch;
	color: var(--fp-muted);
	font-size: 12px;
	font-style: italic;
	line-height: 1.5;
}

.fitpro-app .fitpro-agreement__head {
	margin: 0 0 var(--fp-space-3);
	font-size: 16px;
	letter-spacing: 0.5px;
}

.fitpro-app .fitpro-agreement__list {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-3);
	margin: 0;
}

.fitpro-app .fitpro-agreement__list > li {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2) var(--fp-space-3);
}

.fitpro-app .fitpro-agreement__name {
	flex: 1 1 auto;
	/*
	 * Flex items default to `min-width: auto`, which refuses to shrink below
	 * their content — a long document title would push the button off a 320px
	 * screen and take the document sideways with it. Bug 52's family.
	 */
	min-width: 0;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-agreement__why {
	flex: 1 1 100%;
	font-size: 13px;
	color: var(--fp-body);
}

/*
 * The document itself: trainer-written prose, so it reads like prose.
 *
 * Everything else in the app is interface, where the list reset is right. This
 * is the one place a `<ul>` genuinely means a bulleted list — clauses in a legal
 * document — so the reset is answered here, with the same `!important` it used
 * and a more specific selector, which also clears Divi's `#left-area ul` (bug
 * 38) since that carries no `!important` at all.
 */
.fitpro-app .fitpro-agreement__body {
	max-height: 46vh;
	overflow-y: auto;
	padding: var(--fp-space-4);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-body);
	font-size: 14px;
	line-height: 1.7;
}

/* --- the document itself, read or written --------------------------------
 *
 * One block for both surfaces. `.fitpro-agreement__body` is the client reading
 * it; `.ql-editor.fitpro-doc` is the coach writing it, and the class is put
 * there by RichText.jsx precisely so the two cannot drift. A WYSIWYG editor
 * with its own typography is not what you get.
 *
 * The list rules carry `!important` because the app's own reset does — see the
 * note above `.fitpro-app ul`, which strips markers and padding from every list
 * in the app to defeat Divi's `#left-area ul`. A coach's bulleted list is
 * exactly the case where markers are the content, and without this it renders
 * flush left with nothing in front of it, in the editor as well as in the
 * client's copy.
 */

/*
 * Inline images and video.
 *
 * `max-width` rather than `width`, so a picture narrower than the column is
 * left at its own size instead of being blown up and softened. `height: auto`
 * is what stops a browser honouring an `height` attribute Quill may carry and
 * squashing the aspect ratio.
 *
 * Both surfaces again, and this is the case that proves the shared block is
 * worth it: a coach sizes a picture by eye in the editor, and it has to land
 * the same width in the post.
 *
 * The video rule is written against the element and the class both. The blot
 * sets `fitpro-inline`, but a `<video>` that arrived by paste before the
 * matcher ran, or from an older body, carries no class and must not overflow
 * the page either.
 */
.fitpro-app .fitpro-doc img,
.fitpro-app .fitpro-doc video {
	display: block;
	max-width: 100%;
	height: auto;
	margin: var(--fp-space-3) 0;
	border-radius: var(--fp-radius);
}

/*
 * A video with nothing loaded yet is a transparent box of zero height, which
 * reads as the editor having eaten it. The ground and the minimum give it a
 * shape while the first frame is fetched.
 */
.fitpro-app .fitpro-doc video {
	width: 100%;
	min-height: 120px;
	background: var(--fp-navy);
}

/*
 * Quill selects an embed by putting a class on it, and without a visible
 * outline a coach cannot tell a picture they have selected from one they have
 * not — so Backspace either deletes an image or does nothing, with no way to
 * predict which.
 */
.fitpro-app .ql-editor .ql-image-selected,
.fitpro-app .ql-editor img.ql-selected,
.fitpro-app .ql-editor video.ql-selected {
	outline: 2px solid var(--fp-accent);
	outline-offset: 2px;
}

.fitpro-app .fitpro-doc ul {
	list-style: disc !important;
	padding-left: 22px !important;
	line-height: 1.7 !important;
}

.fitpro-app .fitpro-doc ol {
	list-style: decimal !important;
	padding-left: 22px !important;
	line-height: 1.7 !important;
}

.fitpro-app .fitpro-doc h2,
.fitpro-app .fitpro-doc h3 {
	margin: var(--fp-space-4) 0 var(--fp-space-2);
	font-size: 15px;
	letter-spacing: 0.5px;
}

.fitpro-app .fitpro-doc p {
	margin: 0 0 var(--fp-space-3);
}

/* `.ql-snow .ql-editor blockquote` carries snow's own left rule at the same
   weight as a plain `.fitpro-app .fitpro-doc blockquote`, and snow loads after
   this file, so the shorter selector silently loses inside the editor. */
.fitpro-app .fitpro-doc blockquote,
.fitpro-app .ql-container.ql-snow .ql-editor.fitpro-doc blockquote {
	margin: 0 0 var(--fp-space-3);
	padding-left: var(--fp-space-3);
	border-left: 3px solid var(--fp-line);
	color: var(--fp-muted);
}

.fitpro-app .fitpro-doc a {
	color: var(--fp-accent);
	text-decoration: underline;
}

/*
 * Tables in a coach's document.
 *
 * Quill puts `style="border: 1px solid #000"` on the `<table>` element itself
 * and nothing at all on the cells, which renders as one hairline rectangle with
 * the text loose inside it — and a hard black line that disappears against a
 * dark card. `wp_kses_post` keeps that inline style, and an inline style beats
 * any selector, so `!important` on the border is the only thing that can
 * override it. Measured, not assumed: without it the computed border colour is
 * `rgb(0, 0, 0)` on a `rgb(18, 20, 26)` background.
 *
 * The wrapper is `overflow-x: auto` on the table itself rather than a container
 * element, because the stored HTML is a bare `<table>` — there is nothing to
 * wrap it in without rewriting a coach's markup on the way out. `display: block`
 * makes a table scrollable at the cost of shrink-to-fit sizing, which is the
 * right trade on a phone: a four-column price table that overflows silently is
 * worse than one that scrolls.
 */
.fitpro-app .fitpro-doc table,
.fitpro-app .ql-container.ql-snow .ql-editor.fitpro-doc table {
	display: block;
	width: 100%;
	overflow-x: auto;
	margin: 0 0 var(--fp-space-3);
	border: 1px solid var(--fp-line) !important;
	border-collapse: collapse;
	font-size: 14px;
}

.fitpro-app .fitpro-doc th,
.fitpro-app .fitpro-doc td,
.fitpro-app .ql-container.ql-snow .ql-editor.fitpro-doc th,
.fitpro-app .ql-container.ql-snow .ql-editor.fitpro-doc td {
	min-width: 80px;
	padding: var(--fp-space-2);
	border: 1px solid var(--fp-line);
	text-align: left;
	vertical-align: top;
}

.fitpro-app .fitpro-doc th,
.fitpro-app .ql-container.ql-snow .ql-editor.fitpro-doc th {
	background: var(--fp-surface);
	font-weight: 700;
}

/*
 * The first row reads as a header even though Quill cannot produce `<th>`.
 *
 * Its table module emits `<td>` throughout — there is no header format — so a
 * coach who types their column names into the top row gets no visual
 * distinction at all. `:first-child` gives them one without inventing markup
 * that the editor would drop on the next round trip. A pasted table that *does*
 * carry real `<th>` still gets the rule above, and both look the same.
 */
.fitpro-app .fitpro-doc table tr:first-child td,
.fitpro-app .ql-container.ql-snow .ql-editor.fitpro-doc table tr:first-child td {
	background: var(--fp-surface);
	font-weight: 700;
}

/* The invitation that stands in for the upload control until somebody has
   agreed to progress photos. Bordered rather than plain, so it reads as a thing
   to act on rather than as a paragraph above an empty gallery. */
.fitpro-app .fitpro-photoconsent {
	padding: var(--fp-space-4);
	margin-bottom: var(--fp-space-4);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
}

/* --- the rich text editor ------------------------------------------------
 *
 * Only what Quill's own stylesheet cannot know about this app: the colours, and
 * labels for the pickers whose values are CSS rather than words.
 *
 * **Every selector here is deliberately more specific than the snow rule it
 * answers, never merely later in the file.** The plugin's stylesheet is
 * enqueued by PHP at page load; snow.css arrives at runtime, injected by the
 * lazily-loaded editor chunk — so it is always *after* this file and wins every
 * tie on source order. A rule written to the same weight as its snow
 * counterpart looks right in isolation and does nothing in the app. Measured:
 * the first version of this block lost the expanded picker's border and the
 * blockquote rule that way, and only showed it once the probe page was
 * reordered to match how the app actually loads.
 *
 * That is why the selectors below carry `.ql-toolbar.ql-snow` and
 * `.ql-picker.ql-expanded` in full rather than the shortest thing that matches.
 */

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow,
.fitpro-app .fitpro-richtext .ql-container.ql-snow {
	border-color: var(--fp-line);
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow {
	border-radius: var(--fp-radius) var(--fp-radius) 0 0;
	background: var(--fp-surface);
	font-family: var(--fp-font-body);
}

.fitpro-app .fitpro-richtext .ql-container.ql-snow {
	border-radius: 0 0 var(--fp-radius) var(--fp-radius);
	background: var(--fp-card);
	font-family: var(--fp-font-body);
}

/*
 * The editor scrolls; the page does not grow with the document.
 *
 * Quill's own CSS already sets `overflow-y: auto` here — what it never had was a
 * ceiling, so the canvas grew without limit and the toolbar was pushed off the
 * top of the screen within a page or two of writing. Formatting a long document
 * then meant scrolling the *whole page* up to reach Bold, down to find the next
 * sentence, and up again: measured on the terms of service, which is exactly the
 * kind of document this editor exists for.
 *
 * `height: 100%` in Quill's core resolves to `auto` here — the container's own
 * height is `100%` of an auto-height parent — so the editor sizes to its content
 * between these two bounds and scrolls beyond the upper one.
 *
 * `min()` rather than a flat pixel cap: 640px is comfortable on a laptop and
 * taller than a 13-inch window, where 60vh keeps the toolbar and the surrounding
 * form on screen together.
 */
.fitpro-app .fitpro-richtext .ql-container.ql-snow .ql-editor {
	min-height: 320px;
	max-height: min(60vh, 640px);
	color: var(--fp-body);
	font-size: 14px;
	line-height: 1.7;
}

.fitpro-app .fitpro-richtext .ql-container.ql-snow .ql-editor.ql-blank::before {
	color: var(--fp-muted);
	font-style: normal;
}

/* --- toolbar chrome ------------------------------------------------------ */

/*
 * Snow draws its icons on hard-coded greys, which are invisible on a dark card.
 */
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-stroke {
	stroke: var(--fp-body);
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-fill,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-stroke.ql-fill {
	fill: var(--fp-body);
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker-label {
	color: var(--fp-body);
}

/* The open picker: snow greys the label to #ccc and borders both halves. */
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options {
	border-color: var(--fp-line);
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label {
	color: var(--fp-body);
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke {
	stroke: var(--fp-body);
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker-options {
	background: var(--fp-card);
	border-radius: var(--fp-radius);
	box-shadow: var(--fp-shadow-lg);
}

/* Snow's "this format is on" is #06c, a blue that belongs to no other pixel in
   this app. */
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button:hover,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button:focus,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button.ql-active,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker-label:hover,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker-label.ql-active,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker-item:hover,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker-item.ql-selected {
	color: var(--fp-accent);
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button:hover .ql-stroke,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button:focus .ql-stroke,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button.ql-active .ql-stroke,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker-label:hover .ql-stroke,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker-label.ql-active .ql-stroke {
	stroke: var(--fp-accent);
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button:hover .ql-fill,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button:focus .ql-fill,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button.ql-active .ql-fill {
	fill: var(--fp-accent);
}

/*
 * The link tooltip. Snow gives it a white background and #444 text, which on a
 * dark card is a white slab — and it is the one piece of Quill chrome that
 * contains a text input, so it is also where an unreadable colour costs
 * somebody the URL they are typing.
 */
.fitpro-app .fitpro-richtext .ql-container.ql-snow .ql-tooltip {
	background: var(--fp-card);
	border-color: var(--fp-line);
	border-radius: var(--fp-radius);
	box-shadow: var(--fp-shadow-lg);
	color: var(--fp-body);
}

.fitpro-app .fitpro-richtext .ql-container.ql-snow .ql-tooltip input[type='text'] {
	background: var(--fp-surface);
	border-color: var(--fp-line);
	border-radius: var(--fp-radius);
	color: var(--fp-body);
}

.fitpro-app .fitpro-richtext .ql-container.ql-snow .ql-tooltip a {
	color: var(--fp-accent);
}

/* --- picker labels ------------------------------------------------------- */

/*
 * `:not([data-value])` is load-bearing.
 *
 * A picker's label carries `data-value` once something is chosen, and every
 * item in the list always carries its own. A blanket `::before` on
 * `.ql-picker-item` therefore overwrites **every entry in the dropdown** — all
 * seven sizes read "Size", all six fonts read "Default", and the pickers become
 * lists of one repeated word. Measured, not guessed: the first version of this
 * block did exactly that.
 *
 * So the word is only the empty state, and anything with a value labels itself.
 */
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-font {
	width: 130px;
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-size {
	width: 78px;
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-label:not([data-value])::before,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-font .ql-picker-item:not([data-value])::before {
	content: 'Default';
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-size .ql-picker-label:not([data-value])::before,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-size .ql-picker-item:not([data-value])::before {
	content: 'Size';
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-header .ql-picker-label:not([data-value])::before,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-header .ql-picker-item:not([data-value])::before {
	content: 'Normal';
}

/* A size is its own best label. */
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-size .ql-picker-label[data-value]::before,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-size .ql-picker-item[data-value]::before {
	content: attr(data-value);
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-header [data-value='2']::before {
	content: 'Heading';
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-header [data-value='3']::before {
	content: 'Subheading';
}

/*
 * Each font named, and rendered in its own face — a font menu that does not
 * show you the fonts is a list of words. Snow labels an item from its
 * `data-value`, so without these a coach reads "Georgia, serif" and is being
 * shown the implementation.
 */
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-font [data-value='Georgia, serif']::before {
	content: 'Georgia';
	font-family: Georgia, serif;
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-font [data-value='Times New Roman, serif']::before {
	content: 'Times New Roman';
	font-family: 'Times New Roman', serif;
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-font [data-value='Arial, sans-serif']::before {
	content: 'Arial';
	font-family: Arial, sans-serif;
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-font [data-value='Tahoma, sans-serif']::before {
	content: 'Tahoma';
	font-family: Tahoma, sans-serif;
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-font [data-value='Verdana, sans-serif']::before {
	content: 'Verdana';
	font-family: Verdana, sans-serif;
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow .ql-picker.ql-font [data-value='Courier New, monospace']::before {
	content: 'Courier New';
	font-family: 'Courier New', monospace;
}

.fitpro-app .fitpro-agreement__version {
	margin: var(--fp-space-2) 0 var(--fp-space-4);
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-agreement__sign {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-3);
}

.fitpro-app .fitpro-agreement__stamp {
	margin: 0;
	font-size: 12px;
	line-height: 1.5;
	color: var(--fp-body);
}

/* --- The pad ------------------------------------------------------------ */

.fitpro-app .fitpro-sigpad {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-sigpad__canvas {
	display: block;
	width: 100%;
	border: 1px dashed var(--fp-line-strong);
	border-radius: var(--fp-radius);
	/*
	 * Paper, in both themes — the same white `.fitpro-signed__ink` puts under
	 * the stored drawing.
	 *
	 * The ink is black on purpose (see SignaturePad.jsx): the PNG is kept and
	 * shown on white, on the coach's record and in an export. This was
	 * `--fp-card`, which in dark mode is near-black, so somebody signing at
	 * night drew black on black and could not see their own signature. Ink
	 * that followed the theme instead would be invisible on the paper it is
	 * later shown on. Signing on the surface the signature will live on is
	 * the only arrangement where what you see is what is stored.
	 */
	background: #ffffff;
	/*
	 * Without this the browser claims the gesture for scrolling and the first
	 * downward stroke scrolls the page instead of drawing — on a phone, which
	 * is exactly where a finger signature happens. Not optional.
	 */
	touch-action: none;
	cursor: crosshair;
}

.fitpro-app .fitpro-sigpad__foot {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-3);
}

.fitpro-app .fitpro-sigpad__hint {
	font-size: 12px;
	color: var(--fp-muted);
}

/* --- The record --------------------------------------------------------- */

.fitpro-app .fitpro-signed {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-4);
	margin: 0;
}

.fitpro-app .fitpro-signed__row {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-1);
	padding-bottom: var(--fp-space-4);
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-signed__row:last-child {
	padding-bottom: 0;
	border-bottom: 0;
}

.fitpro-app .fitpro-signed__name {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--fp-space-2);
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-signed__name em {
	font-style: normal;
	font-weight: 400;
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-signed__when {
	font-size: 13px;
	color: var(--fp-body);
}

/*
 * The drawing, on white whatever the surrounding card is.
 *
 * The stored PNG is transparent except for the strokes, and the ink is black —
 * so a signature rendered straight onto a tinted card is legible by luck. This
 * gives it its own paper.
 */
.fitpro-app .fitpro-signed__ink {
	align-self: flex-start;
	max-width: 260px;
	margin-top: var(--fp-space-2);
	padding: var(--fp-space-2);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: #ffffff;
}

/* --- Translations -------------------------------------------------------- */

/*
 * The notice sits above the language switch, and owns the space under it:
 * spacing carried by the wrapper rather than hoped for from whatever follows,
 * the lesson the card header taught.
 */
.fitpro-app .fitpro-agreement__translated {
	margin: 0 0 var(--fp-space-3);
}

.fitpro-app .fitpro-translations {
	margin-top: var(--fp-space-6);
	padding-top: var(--fp-space-5);
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-translations__title,
.fitpro-app .fitpro-translation__title {
	margin: 0 0 var(--fp-space-2);
	font-size: 16px;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-translations__list {
	display: flex;
	flex-direction: column;
	margin: var(--fp-space-4) 0 0;
	padding: 0;
}

.fitpro-app .fitpro-translations__row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-3);
	padding: var(--fp-space-3) 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-translations__row:last-child {
	border-bottom: 0;
}

/* Takes the width and wraps its own text, so the button stays beside it on
   every row rather than dropping below whichever status happens to be longest. */
.fitpro-app .fitpro-translations__name {
	display: flex;
	flex: 1 1 0;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-translations__name em {
	font-style: normal;
	font-weight: 400;
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-translation__deepl {
	margin: var(--fp-space-3) 0;
}

/* ---------------------------------------------------------------------------
 * Macros
 * ------------------------------------------------------------------------- */

/*
 * The glyph takes the macro's colour; the number beside it does not.
 *
 * Colouring the figure as well was the first attempt and it reads badly: four
 * differently-coloured numbers in a row look like four states of one value —
 * something is wrong with the red one — rather than four separate quantities.
 * The icon is the key, the number is data, and data is body text.
 */
.fitpro-app .fitpro-macroicon--calories {
	color: var(--fp-macro-calories);
}

.fitpro-app .fitpro-macroicon--protein {
	color: var(--fp-macro-protein);
}

.fitpro-app .fitpro-macroicon--carbs {
	color: var(--fp-macro-carbs);
}

.fitpro-app .fitpro-macroicon--fat {
	color: var(--fp-macro-fat);
}

.fitpro-app .fitpro-macroicon--water {
	color: var(--fp-macro-water);
}

/*
 * One macro and its figure. `nowrap` so a line break never lands between the
 * glyph and the number it belongs to — which would put a flame at the end of one
 * line and its calorie count at the start of the next, silently reassigning
 * every number in the row to the wrong macro.
 */
.fitpro-app .fitpro-macroval {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	white-space: nowrap;
}

/*
 * The whole row. A flex wrap with a gap rather than the `·` separators the
 * strings used to carry: the icons already separate the values, and a middot
 * between two glyphs is noise.
 *
 * `row-gap` is smaller than the column gap because when this wraps on a phone it
 * wraps into two lines of two, and equal gaps there read as a 2x2 grid of
 * unrelated things rather than one run of four.
 */
/* --- A data source's required credit -------------------------------- */

/*
 * Styled from the wrapper only: the link inside is FatSecret's snippet and
 * their terms forbid changing it, so it gets no class or attribute of ours.
 * Quiet, but a real, readable link — a credit shrunk until nobody can see it
 * is not a credit.
 */
.fitpro-app .fitpro-attribution {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--fp-space-3);
	margin: var(--fp-space-4) 0 0;
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-attribution a {
	color: var(--fp-muted);
	text-decoration: underline;
}

.fitpro-app .fitpro-attribution a:hover {
	color: var(--fp-accent);
}

/* --- A client's plan, on their page ---------------------------------- */

.fitpro-app .fitpro-plancard__none {
	margin: 0 0 var(--fp-space-3);
	color: var(--fp-body);
}

.fitpro-app .fitpro-plancard__held {
	margin: 0 0 var(--fp-space-3);
	padding: 0;
	list-style: none;
}

.fitpro-app .fitpro-plancard__held li {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: var(--fp-space-2) 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-plancard__held strong {
	color: var(--fp-heading);
}

.fitpro-app .fitpro-plancard__meta {
	font-size: 13px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-plancard__note {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-plancard__form {
	margin-top: var(--fp-space-3);
}

/* --- A client's targets, on their page ------------------------------- */

/*
 * A fixed 120px minimum per track rather than a bare `1fr`, whose automatic
 * minimum would let the widest cell — water, with both systems in it, "108 fl
 * oz (3.2 L)" — push the grid past a phone's edge (bug 59). The cell's own
 * `min-width: 0` and the value's `overflow-wrap` let that figure wrap instead.
 */
.fitpro-app .fitpro-targets__grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-targets__cell {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-1);
	min-width: 0;
	padding: var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-targets__value {
	font-size: 20px;
	font-weight: 700;
	color: var(--fp-heading);
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-targets__value small {
	font-size: 12px;
	font-weight: 400;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-targets__source {
	margin: 0 0 var(--fp-space-1);
	font-size: 13px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-targets__head {
	margin: var(--fp-space-4) 0 var(--fp-space-2);
	font-size: 13px;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-targets__history {
	margin: 0;
	padding: 0;
	list-style: none;
}

.fitpro-app .fitpro-targets__history li {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: var(--fp-space-2) 0;
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-targets__date {
	font-size: 13px;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-targets__line {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 2px var(--fp-space-3);
}

.fitpro-app .fitpro-targets__muted {
	font-size: 12px;
	color: var(--fp-muted);
}

/* The client's water line under their rings. */
.fitpro-app .fitpro-water {
	display: flex;
	justify-content: center;
	margin: var(--fp-space-2) 0 0;
	color: var(--fp-body);
}

.fitpro-app .fitpro-macrorow {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 2px var(--fp-space-3);
	color: var(--fp-body);
}

/* A glyph beside the spelled-out name. */
.fitpro-app .fitpro-macroname {
	display: inline-flex;
	align-items: center;
	gap: 6px;
}

/*
 * The icon must not shrink when the label beside it is long.
 *
 * A flex item's default `min-width: auto` lets an SVG with a width attribute be
 * squeezed, and a squashed 15px glyph is unrecognisable rather than merely
 * small. Bug 34's guarantee, stated where it is actually needed.
 */
.fitpro-app .fitpro-macroname > .fitpro-icon,
.fitpro-app .fitpro-macroval > .fitpro-icon {
	flex: 0 0 auto;
}

/*
 * Sign out, in the top bar.
 *
 * A 44px tap target around a 20px glyph — the icon is what you see, the button
 * is what you hit, and on a phone those must not be the same size. It sits at
 * the very edge of the bar, which is the hardest place on a touchscreen to hit
 * accurately.
 *
 * Slightly dimmed until hovered, because it is the one control in the header
 * nobody is looking for and a full-white icon competes with the brand.
 */
.fitpro-app .fitpro-signout {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--fp-tap-min);
	height: var(--fp-tap-min);
	margin-right: -10px;
	border-radius: var(--fp-radius);
	color: rgba(255, 255, 255, 0.75);
	text-decoration: none;
	transition: color 0.15s ease, background-color 0.15s ease;
}

.fitpro-app .fitpro-signout:hover,
.fitpro-app .fitpro-signout:focus-visible {
	color: var(--fp-inverse);
	background: rgba(255, 255, 255, 0.12);
}

/* ---------------------------------------------------------------------------
 * Appearance controls
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-themes {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-3);
}

/*
 * Each option is a small drawing of what it does.
 *
 * "Light" and "Dark" are only meaningful once you have seen them, and "Match my
 * device" is meaningless until you have. The swatch answers all three before
 * anybody presses anything, which is worth more here than in most settings —
 * this is the one control whose entire subject is what things look like.
 */
.fitpro-app .fitpro-theme {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 6px;
	padding: var(--fp-space-2);
	border: 2px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: transparent;
	color: var(--fp-body);
	font: inherit;
	font-size: 12px;
	cursor: pointer;
}

.fitpro-app .fitpro-theme.is-active {
	border-color: var(--fp-accent);
	color: var(--fp-heading);
}

.fitpro-app .fitpro-theme__swatch {
	position: relative;
	display: block;
	width: 62px;
	height: 42px;
	overflow: hidden;
	border: 1px solid var(--fp-line-strong);
	border-radius: 5px;
}

/*
 * The swatches show real colours, not the current theme's.
 *
 * A "Light" preview that is dark because you are currently in dark mode tells
 * you nothing about what pressing it will do, which is the only thing the
 * control is for. So these are literals on purpose — the one place in the file
 * where not using a token is the correct choice.
 */
.fitpro-app .fitpro-theme--light .fitpro-theme__swatch {
	background: #ffffff;
}

.fitpro-app .fitpro-theme--dark .fitpro-theme__swatch {
	background: #1b1e26;
}

/* Half and half, cut on the diagonal: it follows whichever the device is. */
.fitpro-app .fitpro-theme--system .fitpro-theme__swatch {
	background: linear-gradient(115deg, #ffffff 0 50%, #1b1e26 50% 100%);
}

.fitpro-app .fitpro-theme__bar {
	position: absolute;
	inset: 0 0 auto 0;
	height: 11px;
	background: var(--fp-navy);
}

.fitpro-app .fitpro-theme__line {
	position: absolute;
	left: 6px;
	right: 6px;
	height: 4px;
	border-radius: 2px;
	background: var(--fp-accent);
}

.fitpro-app .fitpro-theme__line:not(.fitpro-theme__line--short) {
	top: 18px;
}

.fitpro-app .fitpro-theme__line--short {
	top: 27px;
	right: 26px;
	background: currentColor;
	opacity: 0.4;
}

/* --- The brand colour --------------------------------------------------- */

.fitpro-app .fitpro-accents {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-2);
}

/*
 * A 44px hit area around a 26px disc. The swatches sit in a tight row, so the
 * gap between what you see and what you press is what stops a coach setting
 * their brand to the wrong colour with a thumb.
 */
.fitpro-app .fitpro-accent {
	position: relative;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--fp-tap-min);
	height: var(--fp-tap-min);
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: none;
	cursor: pointer;
}

.fitpro-app .fitpro-accent::before {
	content: '';
	width: 26px;
	height: 26px;
	border: 1px solid rgba(0, 0, 0, 0.16);
	border-radius: 50%;
	/*
	 * The fallback is not decoration. `--fp-swatch` is set inline on each button,
	 * so it is invisible to the stylesheet — and a custom property that turns out
	 * to be undefined does not fall back to nothing, it destroys the whole
	 * declaration (bug 47). With a fallback the worst case is a grey disc; without
	 * one it is a row of swatches with no swatches in them.
	 */
	background: var(--fp-swatch, var(--fp-line-strong));
}

/*
 * The selected ring is drawn outside the disc, in the *text* colour rather than
 * in the swatch's own. A ring in the swatch colour is invisible against the
 * swatch, which is precisely the one it needs to mark.
 */
.fitpro-app .fitpro-accent.is-active::after {
	content: '';
	position: absolute;
	inset: 6px;
	border: 2px solid var(--fp-heading);
	border-radius: 50%;
}

.fitpro-app .fitpro-accent--custom {
	overflow: hidden;
}

/*
 * The native colour input, shrunk to nothing behind its own swatch.
 *
 * `opacity: 0` rather than `display: none`: a hidden input cannot be opened by
 * clicking its label in every browser, and the point of using the native
 * control is that it works everywhere without a library.
 */
.fitpro-app .fitpro-accent--custom input[type='color'] {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	padding: 0;
	border: 0;
	opacity: 0;
	cursor: pointer;
}

/* A colour wheel, so the custom slot does not read as an unset preset. */
.fitpro-app .fitpro-accent--custom::before {
	background: conic-gradient(#ff4c00, #e11d48, #7c3aed, #1c6fd4, #0d9488, #16a34a, #ca8a04, #ff4c00);
}

/*
 * The document behind the app has to know the theme too.
 *
 * `light-dark()` reads the *element's* computed `color-scheme`, and the theme
 * attribute lives on `.fitpro-app` — a descendant. So `body { background:
 * var(--fp-surface) }` resolved against the document's default and painted the
 * page white behind a dark app: invisible until you overscroll, or on the strip
 * beside the shell on a wide screen, or for the instant before the app paints.
 *
 * The body carries its own class for the same choice, which also lets the
 * browser theme the scrollbar and the overscroll gutter — the two pieces of
 * furniture the app cannot draw itself.
 */
body.fitpro-theme-light {
	color-scheme: light;
}

body.fitpro-theme-dark {
	color-scheme: dark;
}

body.fitpro-theme-system {
	color-scheme: light dark;
}

/* ---------------------------------------------------------------------------
 * Plans, and their data sources
 * ------------------------------------------------------------------------- */

/*
 * The source list is numbered, because the number *is* the meaning: a capability
 * goes to the first source that has it. A bulleted list would say "these
 * sources" where the truth is "these sources, in this order".
 */
/*
 * Named for the plan editor, not for "sources".
 *
 * `.fitpro-source` was already taken by the provider cards on the Data sources
 * screen — a different component that happens to be about the same subject. Two
 * components sharing a class name is bug 46 exactly: the rule written for one
 * silently reshapes the other, and here it stacked every row into a column.
 */
.fitpro-app .fitpro-plan-sources {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-2);
	margin: 0 0 var(--fp-space-3);
}

.fitpro-app .fitpro-plan-source {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2) var(--fp-space-3);
	padding: var(--fp-space-2) var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
}

.fitpro-app .fitpro-plan-source__rank {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 22px;
	height: 22px;
	flex: 0 0 auto;
	border-radius: 50%;
	background: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
	font-size: 12px;
	font-weight: 700;
}

.fitpro-app .fitpro-plan-source__name {
	flex: 1 1 auto;
	/* Bug 52's family: a flex item will not shrink below its content. */
	min-width: 0;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-plan-source__moves {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
}

/*
 * What the ordering actually resolves to, shown live.
 *
 * The design rests on a rule the coach cannot see operating, so this is not a
 * nicety — it is where "nothing in this list has recipes" becomes visible before
 * a client is the one who finds out.
 */
.fitpro-app .fitpro-resolved {
	margin-top: var(--fp-space-3);
	padding-top: var(--fp-space-3);
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-resolved--none {
	color: var(--fp-muted);
	font-style: italic;
}

.fitpro-app .fitpro-plan-row {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	width: 100%;
	padding: 0;
	border: 0;
	background: none;
	font: inherit;
	text-align: left;
	color: inherit;
	cursor: pointer;
}

.fitpro-app .fitpro-plan-row__main {
	display: flex;
	flex-direction: column;
	gap: 2px;
	flex: 1 1 auto;
	min-width: 0;
}

.fitpro-app .fitpro-plan-row__name {
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-plan-row__meta,
.fitpro-app .fitpro-plan-row__sources {
	font-size: 13px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-plan-row__sources {
	color: var(--fp-muted);
}

/*
 * An action bar sitting *above* a list, rather than closing off a form.
 *
 * `.fitpro-actions` carries a top margin only, which is right at the end of a
 * card where the card's own padding provides the rest. Above a list there is
 * nothing underneath to provide it, so the buttons ended up against the first
 * row's border and read as part of it. Both places this modifier is used —
 * Programs and Plans — are that shape.
 */
.fitpro-app .fitpro-actions--end {
	justify-content: flex-end;
	margin-bottom: var(--fp-space-4);
}

/*
 * The plan's "what's included" table.
 *
 * Derived rows are read-only and the last row is the only editable cell, so the
 * table has to *look* mostly settled — otherwise a coach reads it as a form
 * they are expected to fill in and wonders which cells they may touch.
 */
/*
 * A plain horizontal scroller, deliberately not `.fitpro-table-wrap`.
 *
 * That class hides its contents below 900px, because it is the desktop half of
 * a card/table swap and the cards take over on a phone. This table has no card
 * counterpart, so it needs to survive at every width and scroll sideways inside
 * itself rather than pushing the page (bug 23's rule).
 */
.fitpro-app .fitpro-includes-wrap {
	overflow-x: auto;
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-includes {
	width: 100%;
	min-width: 340px;
}

.fitpro-app .fitpro-includes td:first-child {
	width: 55%;
}

/*
 * A capability no source on this plan answers. Dimmed rather than hidden,
 * because on the coach's screen the gap is the information: "Recipe library —
 * not included" is what stops somebody selling a tier they have not set up.
 * The client's card leaves these rows out entirely.
 */
.fitpro-app .fitpro-includes--off td {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-includes--new td {
	background: var(--fp-surface);
}

.fitpro-app .fitpro-includes--new .fitpro-input {
	margin: 0;
}

.fitpro-app .fitpro-muted {
	color: var(--fp-muted);
	font-style: normal;
	font-size: 12px;
}

/* ---------------------------------------------------------------------------
 * Attention thresholds
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-thresholds {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-4);
	margin: 0 0 var(--fp-space-4);
}

/*
 * Rewards — what the coach owes a client.
 *
 * A list of debts rather than a trophy cabinet, so it reads like the
 * thresholds list above it: a row per thing, a rule between them, and the
 * status carrying weight as well as colour, because colour is never the only
 * signal here.
 */
/*
 * The progression rule, in words, under an exercise row.
 *
 * Muted and small: it explains the numbers above it rather than competing
 * with them, and it is on every row of a generated block.
 */
/*
 * The projected walk: what the weeks look like if every session is met.
 *
 * Boxed and led by a sentence saying it is a plan rather than a fact, because
 * that distinction is the whole of it -- a client who missed week 1 is not on
 * 11, and a list of week numbers with no caveat reads as a schedule.
 */
.fitpro-app .fitpro-walk {
	padding: var(--fp-space-4);
	margin-bottom: var(--fp-space-4);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-walk__lead {
	margin: 0 0 var(--fp-space-3);
	font-size: 13px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-walk__weeks {
	display: grid;
	gap: var(--fp-space-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

/* The week label never shrinks, so the column of them reads down the page. */
.fitpro-app .fitpro-walk__week {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-2) var(--fp-space-3);
	font-size: 14px;
}

.fitpro-app .fitpro-walk__n {
	flex: none;
	min-width: 7ch;
	font-weight: 600;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-walk__what {
	color: var(--fp-body);
}

/* A week the coach planned to be lighter reads as one, not as a gap. */
.fitpro-app .fitpro-walk__week.is-deload .fitpro-walk__what {
	font-style: italic;
	opacity: 0.8;
}

.fitpro-app .fitpro-progline {
	margin: var(--fp-space-2) 0 0;
	font-size: 13px;
	color: var(--fp-body);
}

/* The phase note on the coach card, which is prose rather than a figure. */
.fitpro-app .fitpro-phase-note {
	font-style: italic;
}

/*
 * What one client will be shown, on the screen where a coach is deciding what
 * to give them.
 *
 * A read-only list rather than the builder's rows: nothing here is editable, so
 * the name and the figures are the whole of it and they sit on one line each
 * until the line runs out. Every row is its own grid so the figures line up
 * down the page -- a column is the thing a reader scans, and a list whose
 * numbers start wherever the name above them happened to end is not one.
 */
.fitpro-app .fitpro-numbers {
	display: grid;
	gap: var(--fp-space-3);
	margin: 0;
	padding: 0;
	list-style: none;
}

.fitpro-app .fitpro-numbers__row {
	display: grid;
	/* Never a bare 1fr: a long exercise title sets the automatic minimum and
	   pushes the figures off the end (bug 59), on the column somebody came to
	   read. */
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: baseline;
	gap: var(--fp-space-2) var(--fp-space-3);
	padding-bottom: var(--fp-space-3);
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-numbers__row:last-child {
	padding-bottom: 0;
	border-bottom: 0;
}

.fitpro-app .fitpro-numbers__name {
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-numbers__figures {
	font-variant-numeric: tabular-nums;
	color: var(--fp-heading);
	white-space: nowrap;
}

/*
 * Why this number and not the template's, and the rule it is walking. Both span
 * the row: they are sentences rather than figures, and a sentence squeezed into
 * the narrow half of a two-column grid wraps to four words a line.
 */
.fitpro-app .fitpro-numbers__why,
.fitpro-app .fitpro-numbers__rule {
	grid-column: 1 / -1;
	font-size: 13px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-numbers__why {
	font-style: italic;
}

/* Below 420px the figures take their own line rather than crushing the name. */
@media (max-width: 420px) {
	.fitpro-app .fitpro-numbers__row {
		grid-template-columns: minmax(0, 1fr);
	}

	.fitpro-app .fitpro-numbers__figures {
		white-space: normal;
	}
}

.fitpro-app .fitpro-rewards {
	display: grid;
	gap: var(--fp-space-4);
	margin: 0;
	padding: 0;
	list-style: none;
}

.fitpro-app .fitpro-reward {
	padding-bottom: var(--fp-space-4);
	border-bottom: 1px solid var(--fp-line);
}

/*
 * One point rule, on one line: what it is, what it pays, and its ceiling.
 *
 * It was a heading, two full-width fields and a rule -- four lines and most of
 * a screen for two numbers, down a list of nine. The boxes hold at most four
 * digits, so they are sized for that and the name takes everything left over.
 *
 * `align-items: start` rather than `end`: the two fields carry a hint only
 * sometimes ("Default: 10", "Zero switches this off"), and aligning on the
 * bottom edge would shunt the boxes up and down as those appear -- which is
 * the misalignment `.fitpro-builder-row__fields` already documents from the
 * other direction. The name is brought down to the inputs' line by the spacer
 * above instead, which cannot be thrown off by a hint on either side.
 */
.fitpro-app .fitpro-pointrow {
	display: grid;
	/* Never a bare 1fr: a long rule name would set the automatic minimum and
	   push the boxes off the card (bug 59). */
	grid-template-columns: minmax(0, 1fr) 7rem 7rem;
	align-items: start;
	gap: 0 var(--fp-space-3);
}

.fitpro-app .fitpro-pointrow__name {
	/*
	 * Two rows: the label-shaped spacer above, then the name itself in a box
	 * the height of the input beside it, centred in it.
	 *
	 * Without that second row the name sits on the *top edge* of the input
	 * rather than on its line -- 21px out, measured, which reads as a mistake
	 * rather than as a design. `--fp-tap-min` is where the input gets its own
	 * height, so the two cannot drift apart.
	 */
	display: grid;
	grid-template-rows: auto minmax(var(--fp-tap-min), auto);
	align-items: center;
}

.fitpro-app .fitpro-pointrow__name::before {
	/*
	 * A non-breaking space rather than nothing: an empty block has no line box
	 * and so no height, and the name would sit back up on the labels' line.
	 */
	content: '\00a0';
	visibility: hidden;
}

/*
 * An uncapped rule has no second box, and its Points field must not stretch
 * across both columns -- "10" in a 15rem box beside a 7rem one reads as two
 * different kinds of field.
 */
.fitpro-app .fitpro-pointrow > .fitpro-field:only-of-type {
	grid-column: 2;
}

/*
 * On a phone the name takes its own line and the boxes sit under it. Three
 * columns inside a card at 375px leaves the name about six characters wide,
 * which is worse than the wrap.
 */
@media (max-width: 560px) {
	.fitpro-app .fitpro-pointrow {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--fp-space-2) var(--fp-space-3);
	}

	.fitpro-app .fitpro-pointrow__name {
		grid-column: 1 / -1;
	}

	/* On its own line there is no label to clear and no box to centre on. */
	.fitpro-app .fitpro-pointrow__name {
		display: block;
	}

	.fitpro-app .fitpro-pointrow__name::before {
		display: none;
	}

	.fitpro-app .fitpro-pointrow > .fitpro-field:only-of-type {
		grid-column: auto;
	}
}

.fitpro-app .fitpro-reward:last-child {
	padding-bottom: 0;
	border-bottom: 0;
}

/*
 * The name and its state on one line, and the state never shrinks.
 *
 * A wrap between "Needs a" and "coupon" would put the one word that says what
 * to do on a line of its own, and the whole row is read for that word.
 */
.fitpro-app .fitpro-reward__head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--fp-space-2) var(--fp-space-3);
}

.fitpro-app .fitpro-reward__name {
	flex: 1 1 12ch;
	min-width: 0;
	font-weight: 600;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-reward__state {
	flex: none;
	white-space: nowrap;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
}

.fitpro-app .fitpro-reward__state--due {
	color: var(--fp-accent);
}

.fitpro-app .fitpro-reward__state--warn {
	color: var(--fp-danger);
}

.fitpro-app .fitpro-reward__state--done {
	color: var(--fp-body);
	opacity: 0.7;
}

.fitpro-app .fitpro-reward__meta {
	margin: var(--fp-space-1) 0 0;
	font-size: 13px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-reward__detail {
	margin: var(--fp-space-2) 0 0;
	font-size: 13px;
	color: var(--fp-danger);
	font-weight: 600;
}

/* The coach's own line about how they delivered it. */
.fitpro-app .fitpro-reward__note {
	margin: var(--fp-space-2) 0 0;
	padding-left: var(--fp-space-3);
	border-left: 2px solid var(--fp-line-strong);
	font-size: 13px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-reward__give {
	margin-top: var(--fp-space-3);
}

/* The roster-wide list of what is owed, above the rewards the coach wrote. */
.fitpro-app .fitpro-reward__owedlist {
	padding: var(--fp-space-4);
	margin-bottom: var(--fp-space-4);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-reward__owedlist > .fitpro-field__help {
	margin-top: 0;
}

.fitpro-app .fitpro-reward__owed {
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--fp-accent);
	white-space: nowrap;
}

.fitpro-app .fitpro-threshold {
	padding-bottom: var(--fp-space-4);
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-threshold:last-child {
	padding-bottom: 0;
	border-bottom: 0;
}

/*
 * A threshold whose check is muted for this client. Dimmed rather than removed:
 * hiding it would leave a coach who later un-mutes the check wondering where the
 * setting went, and the number is still stored and still correct.
 */
.fitpro-app .fitpro-threshold--muted {
	opacity: 0.55;
}

.fitpro-app .fitpro-threshold__head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--fp-space-2) var(--fp-space-3);
}

.fitpro-app .fitpro-threshold__name {
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-threshold__off {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-threshold__levels {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-3) var(--fp-space-5);
	margin-top: var(--fp-space-2);
}

/*
 * Label, box and unit on one line, so "Flag at [ 3 ] exercises" reads as a
 * sentence rather than as a form field with two orphaned words near it.
 */
.fitpro-app .fitpro-threshold__level {
	display: flex;
	align-items: center;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-threshold__level .fitpro-input {
	width: 84px;
	margin: 0;
	/* Wide enough for three digits without the stepper clipping the value. */
	min-width: 0;
}

.fitpro-app .fitpro-threshold__level-label,
.fitpro-app .fitpro-threshold__unit {
	font-size: 13px;
	color: var(--fp-body);
	white-space: nowrap;
}

/* ---------------------------------------------------------------------------
 * Programs
 * ------------------------------------------------------------------------- */

/*
 * The builder grid: one row per week, one column per day of the training week.
 *
 * Columns are numbered Day 1–7, **not** named Mon–Sun, and this comment used to
 * claim the opposite as though it were the point of the design. It was true
 * only of a block started on a Monday: day 1 is whatever day the block begins
 * on, so a block started on a Friday showed that Friday under "Mon" here while
 * the client's calendar showed the same date under "Tue". The weekday travels
 * with the date it belongs to instead, where it cannot disagree with itself.
 *
 * The concern the old heading answered still stands and is still answered: a
 * coach has to see that day 3 lands on a Saturday for *this* client, and the
 * run view says "Sat, Sep 19, 2026" for the client it is actually about rather
 * than for a date this screen made up.
 */
.fitpro-app .fitpro-pgrid {
	width: 100%;
	/*
	 * 660, and the 40 above 620 is the whole cost of the info button.
	 *
	 * In the flow beside the picker it cost 22px of a 78px cell and the grid
	 * had to go to 800 to keep "Push 2" readable — a third more sideways
	 * scrolling on a phone. Overlaid in the corner it costs the width of one
	 * icon, and measuring said 620 left 38px for a name that needs 39. One
	 * pixel, so: 660.
	 */
	min-width: 660px;
	table-layout: fixed;
}

.fitpro-app .fitpro-pgrid th[scope='row'] {
	width: 52px;
	color: var(--fp-muted);
	font-size: 12px;
}

/*
 * Which workouts the day pickers offer, above the grid on the right.
 *
 * `Segmented` centres itself in a full-width wrapper, which is right under a
 * page heading and wrong for a control that belongs to the table beneath it —
 * centred over a 620px grid it reads as a second set of tabs. Pushed right by
 * the flex parent, and allowed to wrap onto its own line on a phone rather than
 * squeezing the rail it sits next to.
 */
.fitpro-app .fitpro-pgrid__scope {
	display: flex;
	justify-content: flex-end;
	flex-wrap: wrap;
	margin-bottom: 12px;
}

.fitpro-app .fitpro-pgrid__scope .fitpro-segmented-wrap {
	margin-bottom: 0;
}

/*
 * The cell holds the picker and, on a day that has a session, a circled i.
 *
 * A flex row rather than the button floating over the select: seven columns on
 * a 620px grid leave about 100px a cell, and an overlaid control would sit on
 * top of the one part of the select a coach actually reads — the workout's
 * name, which is already ellipsised at that width.
 */
/*
 * `td.` on purpose, and it is the whole reason this rule works.
 *
 * `.fitpro-app .fitpro-table td` is two classes and an element; a bare
 * `.fitpro-app .fitpro-pday` is two classes, so the generic table padding won
 * and this declaration did nothing at all — measured as a cell still 12px/16px
 * while the file plainly said otherwise. The same shape as `.fitpro-picker
 * button` beating `.fitpro-picker__option`, met twice in one day.
 */
.fitpro-app td.fitpro-pday {
	position: relative;
	vertical-align: top;
	/*
	 * Tighter than the table's own 12px/16px, and stated rather than inherited.
	 *
	 * Seven columns share about 760px on a desktop, so a cell has roughly 100px
	 * and a `<select>` spends 16px of it on padding and another 18 on the arrow
	 * the browser draws. With the info button beside it there were **15px** of
	 * room for the name — measured — and "Push 2" needs 39, so every cell read
	 * "Pu…" and the one thing a coach scans the grid for was gone.
	 */
	padding: 10px 6px;
}

/*
 * The button is in the corner of the cell, not beside the picker.
 *
 * Beside it, it cost 22px of a cell that has about 78 on a phone — so the name
 * went from 40px of room to 18, every session read "Pus…", and the grid had to
 * be widened to 800px to buy it back, which is a third more sideways scrolling
 * on the one screen that can least afford it. `.fitpro-calday__add` already
 * solves this the other way in the calendar: absolute, in the corner, costing
 * the content no width at all.
 *
 * The picker loses its own arrow in a workout cell, because the two would sit
 * on top of each other. Nothing is lost: a filled cell already reads as a
 * control through its solid border and its bold name, and rest and empty cells
 * — the ones where there is a choice to be discovered rather than changed —
 * keep theirs.
 */
.fitpro-app .fitpro-pday--workout .fitpro-pday__pick {
	width: 100%;
	padding-left: 24px;
	padding-right: 4px;
	appearance: none;
	-webkit-appearance: none;
	background-image: none;
}

.fitpro-app .fitpro-pday__peek {
	position: absolute;
	/*
	 * Left, because the calendar beside it puts **add** in the top right and
	 * two grids in one product should not disagree about which corner means
	 * what. It also keeps the button off the side a `<select>` draws its arrow
	 * on, which is what made the first attempt have to choose between the two.
	 *
	 * Centred on the picker's axis rather than pinned to the cell's corner: the
	 * picker is 44px tall inside a cell with 10px of padding, so a corner
	 * button would sit against its top edge instead of on it.
	 */
	top: 50%;
	left: 8px;
	transform: translateY(-50%);
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/* Clears the documented 24px minimum in both directions once the cell's own
	   padding is counted. The 44px floor is for the in-workout logger,
	   one-handed and mid-set; this is a desk. */
	width: 20px;
	height: 20px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: none;
	color: var(--fp-muted);
	cursor: pointer;
}

.fitpro-app .fitpro-pday__peek:hover,
.fitpro-app .fitpro-pday__peek:focus-visible {
	color: var(--fp-accent);
}

/* ---------------------------------------------------------------------------
 * What is in a session, read-only
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-peek {
	margin: 0;
	/* The app-wide reset strips markers from every list to defeat Divi's
	   `#left-area ul`, and this is a genuinely numbered list of exercises in
	   the order they are performed — the same exception the agreement body and
	   the exercise instructions already take. */
	padding-left: 22px !important;
	list-style: decimal !important;
}

.fitpro-app .fitpro-peek__row {
	padding: 8px 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-peek__row:last-child {
	border-bottom: 0;
}

.fitpro-app .fitpro-peek__name {
	display: block;
	font-weight: 600;
}

.fitpro-app .fitpro-peek__super {
	margin-left: 8px;
	padding: 1px 6px;
	border-radius: var(--fp-radius-pill);
	background: var(--fp-surface);
	color: var(--fp-muted);
	font-size: 10px;
	font-weight: 600;
	white-space: nowrap;
}

.fitpro-app .fitpro-peek__facts,
.fitpro-app .fitpro-peek__detail {
	display: block;
	color: var(--fp-muted);
	font-size: 12px;
}

/*
 * The numbers in their own colour, because they are the line a coach is
 * actually checking — "is this the 5 × 5 one" — and they sit under a muted
 * line that would otherwise read as the same kind of information.
 */
.fitpro-app .fitpro-peek__detail {
	color: var(--fp-body);
	font-variant-numeric: tabular-nums;
}

/*
 * The row is text on the left and its two tools on the right.
 *
 * A flex row with the body taking the space, rather than the tools floated or
 * absolutely placed: a row is two or three lines tall depending on whether the
 * exercise has facts and a prescription, and anything positioned would have to
 * guess at that height.
 */
.fitpro-app .fitpro-peek__row {
	display: flex;
	align-items: flex-start;
	gap: 8px;
}

/* Bug 52 again: a flex item will not shrink below its content unless told, and
   a long exercise name would widen the sheet rather than wrap inside it. */
.fitpro-app .fitpro-peek__body {
	flex: 1;
	min-width: 0;
}

.fitpro-app .fitpro-peek__tools {
	display: flex;
	flex: none;
	gap: 2px;
}

/*
 * Icon-only, so each carries its name twice — `title` for a pointer and
 * `aria-label` for a reader. The circled i and the two arrows are close to
 * universal and "close to" is not a reason to make replacing an exercise a
 * guess.
 *
 * 32px rather than the 44px floor: that floor is for the in-workout logger,
 * operated one-handed mid-set, and this is a sheet a coach reads at a desk.
 * Both are well past the 24px minimum.
 */
.fitpro-app .fitpro-peek__tool {
	display: inline-flex;
	width: 32px;
	height: 32px;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 0;
	border-radius: 8px;
	background: none;
	color: var(--fp-muted);
	cursor: pointer;
}

.fitpro-app .fitpro-peek__tool:hover {
	background: var(--fp-line);
	color: var(--fp-accent);
}

/* One option in the swap sheet: the whole row is the control. */
.fitpro-app .fitpro-swapopt {
	display: flex;
	width: 100%;
	align-items: center;
	gap: 8px;
	padding: 0;
	border: 0;
	background: none;
	color: inherit;
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-swapopt:disabled {
	cursor: default;
	opacity: 0.6;
}

.fitpro-app .fitpro-swapopt__action {
	flex: none;
	color: var(--fp-accent);
	font-size: 12px;
	font-weight: 700;
}

.fitpro-app .fitpro-pday__date {
	display: block;
	margin-bottom: 2px;
	color: var(--fp-muted);
	font-size: 11px;
}

/*
 * A native select, not a button that opens a popover.
 *
 * The grid lives in an `overflow-x: auto` wrapper so seven columns can scroll
 * sideways on a phone — and an element with `overflow-x: auto` and
 * `overflow-y: visible` computes `overflow-y` to `auto` too, because CSS does
 * not allow one axis to be visible while the other clips. So the wrapper is a
 * scroll container in *both* directions, and any absolutely positioned popover
 * inside it is trapped by it. A native select's list is drawn by the browser
 * outside the page, so no ancestor can clip it.
 */
.fitpro-app .fitpro-pday__pick {
	display: block;
	width: 100%;
	/*
	 * The tap minimum, not an eyeballed 38px. A coach filling four weeks makes
	 * 28 of these choices, and on a phone every one of them is a thumb on a cell
	 * in a seven-column grid — the tightest target in the product.
	 */
	min-height: var(--fp-tap-min);
	padding: var(--fp-space-2);
	border: 1px dashed var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: transparent;
	color: var(--fp-body);
	font: inherit;
	font-size: 12px;
	text-align: left;
	cursor: pointer;
	/*
	 * The browser's own arrow is dropped: at this width it eats most of the cell
	 * and leaves no room for a workout name. The cell reads as a control from
	 * its border and its hover, and it is still a select in every other respect.
	 */
	appearance: none;
	text-overflow: ellipsis;
}

.fitpro-app .fitpro-pday__pick:disabled {
	opacity: 0.6;
	cursor: progress;
}

.fitpro-app .fitpro-pday--workout .fitpro-pday__pick {
	border-style: solid;
	border-color: var(--fp-accent);
	background: var(--fp-accent-soft);
	color: var(--fp-heading);
	font-weight: 700;
}

/*
 * Rest is drawn as a real state, not as an empty cell. It is an instruction the
 * coach gave, and a programme that reads as "nothing here" on its rest days is
 * one a client fills in with their own ideas.
 */
.fitpro-app .fitpro-pday--rest .fitpro-pday__pick {
	border-style: solid;
	border-color: var(--fp-line);
	background: var(--fp-surface);
	color: var(--fp-muted);
	font-style: italic;
}

/* --- One client's run ---------------------------------------------------- */

.fitpro-app .fitpro-prun {
	width: 100%;
	min-width: 560px;
}

.fitpro-app .fitpro-prun--done td {
	color: var(--fp-muted);
}

/*
 * A day whose date has moved because the client is behind. Amber rather than
 * red: falling two days behind is not a failure, it is the normal shape of a
 * month, and the panel's job is to say where things now land.
 */
.fitpro-app .fitpro-prun--moved td {
	background: color-mix(in srgb, var(--fp-warning) 8%, transparent);
}

.fitpro-app .fitpro-prun__drift {
	display: block;
	color: var(--fp-warning);
	font-size: 11px;
}

/*
 * Body-part pills on a workout card.
 *
 * Bottom right, after the name and the meta line, because they answer a
 * different question from either: not "what is this called" or "how long is it",
 * but "what does it actually train". A coach scanning the library for a leg day
 * reads this column and nothing else.
 */
.fitpro-app .fitpro-card-row__parts {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	align-items: center;
	gap: 4px;
	/* Never squeezes the workout name; the name is what identifies the row. */
	flex: 0 1 auto;
	max-width: 45%;
}

.fitpro-app .fitpro-part {
	padding: 2px 8px;
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius-pill);
	background: var(--fp-surface);
	color: var(--fp-body);
	font-size: 11px;
	white-space: nowrap;
}

/*
 * Four pills, then a count. A full-body session can touch seven groups, and
 * seven pills stop being scannable — which is the only thing they are for.
 */
.fitpro-app .fitpro-part--more {
	border-style: dashed;
	color: var(--fp-muted);
}

/*
 * Both modifiers live **after** `.fitpro-part`, and that is load-bearing.
 *
 * They were written next to the builder styles four thousand lines above the
 * base class. Same specificity, so source order decided it and the base won:
 * every declaration in both blocks was silently discarded, and a superset pill
 * rendered byte-identical to a plain one. Nothing errors, the rules read
 * perfectly on their own, and it is invisible unless you compare the computed
 * styles of two pills — which is how it was found, after a screenshot was read
 * as showing the fill it did not have.
 *
 * Fourth occurrence of this shape in this stylesheet (bugs 34, 41, and the
 * picker peek). A modifier belongs after the class it modifies.
 */
/*
 * A body part trained in a superset, on the workout card.
 *
 * A solid fill rather than the tint the builder's badge uses, and deliberately
 * a different rule from `--superset`: that one is a label saying "this exercise
 * is paired", sitting alone beside a name where a tint is enough. This one has
 * to stand out from three or four pills beside it at a glance, which is the
 * only reason a coach looks at that corner of the card.
 *
 * Fill, not hue. A reader who cannot separate the accent from the card surface
 * still sees filled against outlined, which is the rule the macro icons already
 * follow — colour is never the only signal.
 */
.fitpro-app .fitpro-part--paired {
	border-color: var(--fp-accent-fill);
	background: var(--fp-accent-fill);
	color: var(--fp-accent-contrast);
	font-weight: 700;
}

.fitpro-app .fitpro-part--superset {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	flex: none;
	border-color: var(--fp-accent);
	background: var(--fp-accent-soft);
	color: var(--fp-accent);
	font-weight: 700;
}

/*
 * Picking which workouts a generated program may use.
 *
 * The body parts sit under each name because they are what the choice is
 * actually about: a coach assembling a week is looking for one that trains legs,
 * and the title may not say so.
 */
.fitpro-app .fitpro-picklist {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-2);
	margin: 0 0 var(--fp-space-3);
	max-height: 260px;
	overflow-y: auto;
}

.fitpro-app .fitpro-picklist label {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2);
	min-height: var(--fp-tap-min);
	cursor: pointer;
}

.fitpro-app .fitpro-picklist__parts {
	flex: 1 1 100%;
	padding-left: 26px;
	color: var(--fp-muted);
	font-size: 12px;
}

/*
 * Variants & assignments.
 *
 * Declared after `.fitpro-card-row` and `.fitpro-part`, which is not a matter
 * of taste: a modifier at the same specificity as its base loses on source
 * order, so one written earlier in the file reads perfectly and never applies.
 * That has happened four times in this stylesheet — see bug 34.
 */
.fitpro-app .fitpro-card-row--static {
	/* Nothing on the row is clickable; the buttons inside it are. A hand cursor
	   over dead space promises an action that is not there. */
	cursor: default;
}

/*
 * `align-items: center`, and it is the difference between a pill and a blob.
 *
 * A flex row stretches its children by default, so the phase-kind badge in here
 * — a `.fitpro-part`, which is `border-radius: 100px` because it is normally a
 * short pill — grew to the full 50px height of the row beside it and painted
 * its whole box. It rendered as a dark lozenge with the word floating in the
 * middle of it. The buttons were stretched too and hid it, because a button
 * centres its own text; a background and a radius cannot.
 */
.fitpro-app .fitpro-variant-tools {
	display: flex;
	flex: none;
	flex-wrap: wrap;
	align-items: center;
	justify-content: flex-end;
	gap: var(--fp-space-2);
}

/*
 * On a phone the tools take their own line under the row they belong to.
 *
 * **Pre-existing, and found while fixing the badge above rather than caused by
 * it** — measured at 420px of document against a 375px screen with the old
 * `align-items` put back, so it is the width that was wrong, not the change.
 * A phase row is a name plus a kind badge plus Edit plus Delete, which is 347px
 * of `flex: none` beside a body in a 293px card, and the parent row is
 * `nowrap`, so the whole page slid sideways.
 *
 * `flex-basis: 100%` rather than letting them shrink: squeezing two buttons and
 * a pill into what is left produces three unreadable stubs, which is the lesson
 * the tab rail already records about squeezing seven labels into 375px.
 */
@media (max-width: 560px) {
	.fitpro-app .fitpro-card-row .fitpro-variant-tools {
		flex-basis: 100%;
		justify-content: flex-start;
	}

	/*
	 * ...and the two buttons share that line rather than stacking on it.
	 *
	 * Giving the tools their own line was only half the job. At their desktop
	 * size the pair is **249px** -- Edit 109 and Delete 132, measured, because a
	 * `.fitpro-btn` is padded for a form's primary action -- against a 227px
	 * line on a 375px phone, and 175px once a row is nested. So they wrapped,
	 * one per line, and a single phase came to 248px of a 812px screen. That is
	 * the "the whole UI is wonky" half of the phases report.
	 *
	 * `flex: 1 1 0` rather than a smaller padding alone: two equal halves of
	 * whatever the line is cannot wrap at any width, where a fixed padding only
	 * moves the width at which it starts wrapping again.
	 *
	 * **`white-space: nowrap` is load-bearing.** Without it "Delete" -- the
	 * longer word, and uppercase with 3px of tracking -- broke to a second line
	 * inside its own button, so the pair came out 50px and 68px tall and sat on
	 * one line looking like a mistake. Measured after: both 50px, neither
	 * clipped, the stage row 190px and a nested one 148px.
	 */
	.fitpro-app .fitpro-card-row .fitpro-variant-tools > button {
		flex: 1 1 0;
		min-width: 0;
		padding-left: var(--fp-space-2);
		padding-right: var(--fp-space-2);
		white-space: nowrap;
	}

	.fitpro-app .fitpro-card-row:has(> .fitpro-variant-tools) {
		flex-wrap: wrap;
	}

	/*
	 * The kind badge takes a line of its own and the two buttons share the one
	 * under it.
	 *
	 * Left to wrap on their own the three ran together, so "Deload" sat against
	 * Edit and read as a third control rather than as a label. They are
	 * different kinds of thing: one says what this phase *is*, the other two do
	 * something to it.
	 *
	 * The line is taken by the **wrapper**, never by the pill. `flex-basis: 100%`
	 * on a `.fitpro-part` stretches it right across the card, and a pill as wide
	 * as its container stops looking like a pill at all — which is what the first
	 * attempt did.
	 */
	.fitpro-app .fitpro-card-row .fitpro-phase-kind {
		flex-basis: 100%;
	}
}

/*
 * The scope toggle, centred and smaller on a phone.
 *
 * Right-aligned it belongs to the grid beneath it, which is the desktop case.
 * On a phone it was **342px of control inside a 293px card** -- measured -- so
 * it only stayed on screen because the segmented rail scrolls, and a coach had
 * to swipe a two-item switch to find out what the second item was. Centred,
 * and the labels lose their desktop tracking and padding, which is where the
 * width was going: "PROGRAM WORKOUTS" at 1px of letter-spacing is a third wider
 * than the words in it.
 */
@media (max-width: 560px) {
	.fitpro-app .fitpro-pgrid__scope {
		justify-content: center;
	}

	.fitpro-app .fitpro-pgrid__scope .fitpro-segmented button {
		padding: 0 10px;
		font-size: 11px;
		letter-spacing: 0.4px;
	}
}

/*
 * Holds the phase's kind badge. It exists so a line break can be asked for
 * without stretching the pill inside it — see the phone rule below.
 */
/*
 * A stage and the weeks inside it, in one box.
 *
 * They used to be two boxes: a card row for the stage, and a separate bordered
 * panel under it holding the fold. Nothing on screen said the second belonged
 * to the first -- reported as "it's not super clear the weeks belong to the
 * parent" -- and the two boxes did not even share an edge, because of the
 * `::details-content` break recorded at the top of this file.
 *
 * The group owns the box now, the stage's row is its heading, and the weeks sit
 * in from it behind a rule. `list-style: none` because the app's own reset
 * strips markers from `ul` and this `li` is a direct child of one that has
 * them back.
 */
.fitpro-app .fitpro-phase-group {
	list-style: none;
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius-lg);
	background: var(--fp-card);
	box-shadow: var(--fp-shadow);
}

/* The stage's own row gives its box up: the group is the box now. */
.fitpro-app .fitpro-phase-group > .fitpro-card-row {
	border: 0;
	border-radius: 0;
	background: none;
	box-shadow: none;
}

/*
 * The fold, indented inside the group.
 *
 * The rule down its left is what carries the nesting at a glance; the indent
 * alone reads as a stray margin. Its right margin matches the row's padding
 * above it, so the two line up rather than nearly lining up.
 */
.fitpro-app .fitpro-phase-weeks {
	margin: 0 var(--fp-space-4) var(--fp-space-3) calc(var(--fp-space-4) + var(--fp-space-3));
	padding-left: var(--fp-space-3);
	border-left: 2px solid var(--fp-line);
}

.fitpro-app .fitpro-phase-weeks > summary {
	cursor: pointer;
	color: var(--fp-accent);
	font-size: 13px;
	font-weight: 600;
}

.fitpro-app .fitpro-phase-weeks[open] > summary {
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-phase-weeks__list {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

/*
 * A week reads as a note inside the stage rather than as a card beside it: the
 * surface colour rather than the card's, and no shadow, because a shadow inside
 * a box it belongs to makes it look like it is sitting on top of one.
 */
.fitpro-app .fitpro-phase-weeks__list > .fitpro-card-row {
	min-height: 0;
	padding: var(--fp-space-2) var(--fp-space-3);
	background: var(--fp-surface);
	box-shadow: none;
}

.fitpro-app .fitpro-phase-kind {
	display: flex;
	flex: none;
	align-items: center;
}

.fitpro-app .fitpro-variant-parts {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-2);
	margin: 0 0 var(--fp-space-4);
}

/*
 * One exercise inside the variant editor.
 *
 * A ruled block rather than a card: these are rows of a single form, and a card
 * apiece would make each exercise look like a separate thing to save.
 */
/* A list of stated outcomes, each its own button with the consequence under it.

   Not a radio group and not a `<Segmented>`: there are four of them, each needs
   a sentence to be answerable, and picking one *is* the decision — a coach who
   has read four outcomes does not then want a Save button. The help line owns
   the space under its own button so a long one cannot push its neighbour's
   button out of line (the rule `.fitpro-field__help` already follows). */
.fitpro-app .fitpro-choices {
	display: grid;
	/* `minmax(0, …)`, never a bare `auto`: a long label would otherwise set the
	   grid's minimum and push the sheet sideways on a phone (bug 59). */
	grid-template-columns: minmax(0, 1fr);
	gap: var(--fp-space-4);
	margin: var(--fp-space-4) 0;
}

.fitpro-app .fitpro-choice > .fitpro-field__help {
	margin: var(--fp-space-2) 0 0;
}

.fitpro-app .fitpro-variant-ex {
	padding-top: var(--fp-space-4);
	border-top: 1px solid var(--fp-line);
	margin-top: var(--fp-space-4);
}

.fitpro-app .fitpro-variant-ex__head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2);
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-variant-ex__name {
	/* Never the thing that gives way when the badge needs room, and never wide
	   enough to push the row past the card either — bug 52's rule. */
	flex: 1 1 12ch;
	min-width: 0;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-variant-ex__fields {
	display: grid;
	grid-template-columns: 1fr 1fr;
	align-items: start;
	gap: var(--fp-space-3);
}

@media (min-width: 720px) {
	.fitpro-app .fitpro-variant-ex__fields {
		grid-template-columns: repeat(5, 1fr);
	}
}

.fitpro-app .fitpro-variant-count {
	/* Pushed left of the buttons it explains, since `.fitpro-actions` aligns to
	   the end and this is a sentence rather than a control. */
	margin-right: auto;
	color: var(--fp-muted);
	font-size: 13px;
}

/*
 * One day inside a program variant's editor.
 *
 * Two columns rather than the five the workout editor uses: these are selects
 * holding workout names, not two-digit numbers, and squeezing "Upper Body —
 * Push" into a fifth of a card ellipsises the only part that identifies it.
 */
.fitpro-app .fitpro-variant-day__fields {
	display: grid;
	grid-template-columns: 1fr;
	align-items: start;
	gap: var(--fp-space-3);
}

@media (min-width: 720px) {
	.fitpro-app .fitpro-variant-day__fields {
		grid-template-columns: 2fr 1fr;
	}
}

/*
 * Periodization.
 *
 * Declared after `.fitpro-part` and the program grid for bug 34's reason: a
 * modifier at the same specificity as its base loses on source order, so one
 * written earlier reads correctly and never applies.
 */
.fitpro-app .fitpro-phase__fields {
	display: grid;
	grid-template-columns: 1fr 1fr;
	align-items: start;
	gap: var(--fp-space-3);
}

@media (min-width: 720px) {
	.fitpro-app .fitpro-phase__fields {
		grid-template-columns: repeat(4, 1fr);
	}
}

/* Which phase a week belongs to, under its number in the grid's row heading. */
/*
 * The phase name under the week number, wrapped rather than cut off.
 *
 * It was `max-width: 9ch` with an ellipsis, which is shorter than three of the
 * four names this product's **own** generator writes: "Accumulation" is twelve
 * characters and "Intensification" fifteen, so a generated block read
 * "Accumul…", "Intensifi…", "Accumul…" down its whole left edge and the one
 * column that says what a week is for said almost nothing.
 *
 * Two lines of 10px fit the row the day cells already set, so there is nothing
 * to buy back. A coach's own long name wraps further and grows the row, which
 * is the right way round: the label is the point of the column.
 */
.fitpro-app .fitpro-pgrid__phase {
	display: block;
	overflow-wrap: break-word;
	line-height: 1.3;
	font-size: 10px;
	font-weight: 600;
	letter-spacing: 0;
	text-transform: none;
	color: var(--fp-muted);
}

/*
 * A deload week is tinted, because it is the one week whose numbers look wrong
 * at a glance and are not. The tint is a wash of the accent rather than a
 * colour of its own, so it follows the trainer's brand and never collides with
 * the red this app reserves for a problem.
 */
.fitpro-app .fitpro-pgrid__row--deload > th,
.fitpro-app .fitpro-pgrid__row--deload > td {
	background: color-mix(in srgb, var(--fp-accent) 7%, transparent);
}

/* The same sentence on the client's side, above their first set. */
/*
 * The phase's own sentence, under the line that names it.
 *
 * Quieter than the name and wider than the pill beside it: it is the reason,
 * not the label, and somebody reads it once at the top of a session.
 */
.fitpro-app .fitpro-logger__phase-note {
	margin: 0 0 var(--fp-space-4);
	color: var(--fp-body);
	font-size: 13px;
	line-height: 1.5;
}

.fitpro-app .fitpro-logger__phase {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2);
	margin: 0 0 var(--fp-space-4);
	color: var(--fp-muted);
	font-size: 13px;
}

/*
 * Automated progressive overload, under the thresholds it sits beside.
 *
 * Set apart with a rule rather than a card of its own: it is the same kind of
 * decision — how patient to be with this person — and a second card with a
 * second Save button is how half an edit gets lost, which is the mistake the
 * mute switches were moved here to fix.
 */
.fitpro-app .fitpro-auto {
	padding-top: var(--fp-space-4);
	border-top: 1px solid var(--fp-line);
	margin-top: var(--fp-space-4);
}

.fitpro-app .fitpro-auto__fields {
	display: grid;
	grid-template-columns: 1fr 1fr;
	align-items: start;
	gap: var(--fp-space-3);
	margin-top: var(--fp-space-3);
}

@media (min-width: 720px) {
	.fitpro-app .fitpro-auto__fields {
		grid-template-columns: repeat(4, 1fr);
	}
}

/*
 * One notification.
 *
 * The unread mark is a bar down the leading edge rather than a background wash:
 * the row already carries a pill on the right, and two tinted things in one row
 * read as two states of the same thing. It uses the accent, never red — a client
 * whose bench was eased has not done anything wrong.
 */
.fitpro-app .fitpro-note {
	align-items: flex-start;
	cursor: default;
}

.fitpro-app .fitpro-note--unread {
	border-inline-start: 3px solid var(--fp-accent);
}


/* ---------------------------------------------------------------------------
 * Branding
 *
 * Whose product this is, on the screens with no app around them. Signed out,
 * `/pro/` and `/portal/` showed a sign-in box on an empty page — no name, no
 * mark — which is the first screen a prospective client ever sees and the one
 * place the app has no chrome of its own to carry the identity.
 *
 * One block for both renderers: `Support\Branding::header_html()` builds this
 * markup in PHP for the gates, and `Brand.jsx` builds the same in React for the
 * client's own signed-out screen. Two stylesheets for one thing is how they
 * drift.
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-brand,
.fitpro-gate .fitpro-brand {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-5);
	text-align: center;
}

/*
 * Bounded by height, not width.
 *
 * A logo is any shape, and the one thing that has to stay predictable is how
 * much vertical room it takes above a form. Capping the width instead lets a
 * tall narrow mark push the sign-in button below the fold on a phone.
 */
.fitpro-app .fitpro-brand__logo,
.fitpro-gate .fitpro-brand__logo {
	display: block;
	width: auto;
	height: auto;
	max-height: 96px;
	max-width: 100%;
	object-fit: contain;
}

.fitpro-app .fitpro-brand__title,
.fitpro-gate .fitpro-brand__title {
	font-family: var(--fp-font-heading);
	font-weight: 700;
	font-size: clamp(18px, 4vw, 24px);
	letter-spacing: clamp(1px, 0.6vw, 3px);
	text-transform: uppercase;
	color: var(--fp-heading);
}

/* The sidebar's copy: smaller, and no title — the app around it already says. */
.fitpro-app .fitpro-brand--small {
	margin-bottom: 0;
}

.fitpro-app .fitpro-brand--small .fitpro-brand__logo {
	max-height: 40px;
	opacity: 0.85;
}

/* ---------------------------------------------------------------------------
 * The bottom of the sidebar
 *
 * Desktop only, and hidden below 980px rather than left out of the markup: down
 * there the rail is a fixed bar across the bottom of a phone, where a logo and
 * a copyright line would take a third of it and push the tabs off screen.
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-navfoot {
	display: none;
}

.fitpro-app .fitpro-navfoot__credit {
	margin: 0;
	color: var(--fp-muted);
	font-size: 11px;
	line-height: 1.5;
	text-align: center;
}

/*
 * Stated rather than inherited.
 *
 * `.fitpro-app a` is already the accent and already covers `:visited`, because
 * an author rule beats the user agent's regardless of specificity. This says it
 * anyway, at the same weight, so the one link in the product that sits outside
 * every card cannot be turned purple by a theme that decides to style visited
 * links — which Divi does, from an ID selector no class of ours can outrank.
 */
.fitpro-app .fitpro-navfoot__credit a,
.fitpro-app .fitpro-navfoot__credit a:visited {
	color: var(--fp-accent);
	text-decoration: none;
}

.fitpro-app .fitpro-navfoot__credit a:hover,
.fitpro-app .fitpro-navfoot__credit a:focus-visible {
	color: var(--fp-accent-dark);
	text-decoration: underline;
}

@media (min-width: 980px) {
	/*
	 * The rail becomes a column so the footer can sit at the bottom of it. The
	 * wrapper was a row holding one child; the scroll carets inside it are
	 * `display: none` at this width, so nothing else is affected.
	 */
	.fitpro-app .fitpro-tabs-wrap {
		flex-direction: column;
	}

	.fitpro-app .fitpro-navfoot {
		display: flex;
		flex: none;
		flex-direction: column;
		align-items: center;
		gap: var(--fp-space-2);
		padding: var(--fp-space-3) var(--fp-space-3) var(--fp-space-4);
		border-top: 1px solid var(--fp-line);
	}
}

/* The chosen logo, on the settings screen.

   A checkerboard behind it, because a logo is usually a transparent PNG and a
   white mark on a white card is an empty box that reads as a failed upload. */
.fitpro-app .fitpro-logofield__preview {
	display: flex;
	align-items: center;
	justify-content: center;
	margin-top: var(--fp-space-3);
	padding: var(--fp-space-4);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background-color: var(--fp-card);
	background-image:
		linear-gradient(45deg, var(--fp-line) 25%, transparent 25%),
		linear-gradient(-45deg, var(--fp-line) 25%, transparent 25%),
		linear-gradient(45deg, transparent 75%, var(--fp-line) 75%),
		linear-gradient(-45deg, transparent 75%, var(--fp-line) 75%);
	background-size: 16px 16px;
	background-position: 0 0, 0 8px, 8px -8px, -8px 0;
}

.fitpro-app .fitpro-logofield__preview img {
	max-height: 88px;
	max-width: 100%;
	width: auto;
	height: auto;
	object-fit: contain;
}

/* ---------------------------------------------------------------------------
 * The unsaved-changes question
 *
 * `.fitpro-confirm` is an inline strip inside a title bar — it grows to the
 * card's width and sits under the controls it belongs to. This one is raised
 * by navigation rather than by a button on a card, so it has no card to sit in
 * and is centred over the page instead. Only the differences are declared; the
 * border, the wash and the type all come from the rule above.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-modal {
	position: fixed;
	inset: 0;
	z-index: var(--fp-z-modal);
	display: flex;
	align-items: center;
	justify-content: center;
	padding: var(--fp-space-4);
}

.fitpro-app .fitpro-modal__backdrop {
	position: absolute;
	inset: 0;
	background: var(--fp-scrim);
}

.fitpro-app .fitpro-confirm--modal {
	position: relative;
	flex-basis: auto;
	width: min(420px, 100%);
	margin-top: 0;
	background: var(--fp-card);
	box-shadow: var(--fp-shadow-lg, 0 12px 32px rgb(0 0 0 / 28%));
}

/* The two answers are equally reachable rather than right-aligned: this is a
   question, not a form being submitted, and the safe answer should not be the
   one furthest from the thumb on a phone. */
.fitpro-app .fitpro-confirm--modal .fitpro-actions {
	flex-wrap: wrap;
	justify-content: stretch;
}

.fitpro-app .fitpro-confirm--modal .fitpro-actions > * {
	flex: 1 1 auto;
}

/* ---------------------------------------------------------------------------
 * The card header
 *
 * It carries the space beneath itself rather than leaning on the title's
 * margin. The title keeps its own margin for the many cards that have no
 * header row at all, and gives it up inside one — otherwise the gap is applied
 * twice on a card with a title and no actions.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-card__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-card__head .fitpro-card__title {
	margin-bottom: 0;
}

/* A long title and a button do not fit side by side on a phone; the button
   drops under it rather than crushing the words to two characters. */
@media (max-width: 560px) {
	.fitpro-app .fitpro-card__head {
		flex-wrap: wrap;
	}
}

/* ---------------------------------------------------------------------------
 * A list row that opens something
 *
 * The base row is already `cursor: pointer`; what this adds is the part a
 * keyboard user needs. These rows carry `role="button"` and `tabIndex`, so
 * without a focus style the only way to tell where you are is to press Enter
 * and find out.
 *
 * Declared after `.fitpro-card-row--static`, which is not a matter of taste: a
 * modifier at the same specificity loses on source order, and this file has
 * paid for that four times (bug 34).
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-card-row--open:hover {
	border-color: var(--fp-accent);
}

.fitpro-app .fitpro-card-row--open:focus-visible {
	outline: 2px solid var(--fp-accent);
	outline-offset: 2px;
}

/* A hint that belongs above the control it describes rather than under the
   label — the field primitives put help under the input, and here the sentence
   explains the whole group. */
.fitpro-app .fitpro-field__help--above {
	margin: 0 0 var(--fp-space-2);
}

/* ---------------------------------------------------------------------------
 * A metric with only one reading
 *
 * There is no line to draw yet and there is still a number worth reading, so
 * it is shown at the size the chart's own figures are shown at rather than
 * buried in the sentence explaining why there is no chart.
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-chart__solo {
	padding: var(--fp-space-3) 0;
}

.fitpro-app .fitpro-chart__solo-value {
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: var(--fp-space-2);
	margin: 0 0 var(--fp-space-2);
}

.fitpro-app .fitpro-chart__solo-value strong {
	font-family: var(--fp-font-heading);
	font-size: 24px;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-chart__solo-when {
	font-size: 12px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-chart__solo .fitpro-chart__note {
	margin: 0;
}

/* The automation block's explanation, now a sibling of the switch rather than
   a child of the flex row it used to share with it. */
.fitpro-app .fitpro-auto__help {
	margin: var(--fp-space-2) 0 0;
}

/* ---------------------------------------------------------------------
 * A folded-away escape hatch
 *
 * Used where an advanced path exists and almost nobody should take it — the
 * "use my own Google project" credentials, which are a real option and a bad
 * default. A native <details> rather than a toggle in JavaScript: it is
 * keyboard operable, announced correctly, and works before the bundle loads.
 *
 * The summary needs an explicit cursor and the accent, or it reads as a
 * heading somebody has failed to style rather than as something to press.
 * ------------------------------------------------------------------ */

.fitpro-app .fitpro-details {
	margin: var(--fp-space-3) 0 0;
	padding: var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-surface);
}

.fitpro-app .fitpro-details > summary {
	cursor: pointer;
	color: var(--fp-accent);
	font-weight: 600;
}

.fitpro-app .fitpro-details[open] > summary {
	margin-bottom: var(--fp-space-3);
}

/*
 * The source-code view inside the rich text editor.
 *
 * `.fitpro-field__label` is `display: block` and uppercase, so the toggle sits
 * inside it on the same line via `float` rather than a flex row: turning the
 * label into a flex container would inherit the uppercase, letter-spaced
 * treatment onto a control that reads as a link, and every other field label in
 * the app would have to change with it.
 *
 * `text-transform: none` on the button for the same reason — it inherits the
 * label's, and "EDIT HTML" shouts.
 */
.fitpro-app .fitpro-richtext__source {
	float: right;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0;
	text-transform: none;
}

/*
 * Monospace, and a real tab stop. Reading HTML in a proportional face is what
 * makes a stray `<br />` hard to spot, which is the entire reason this view
 * exists.
 *
 * `white-space: pre` with horizontal scroll rather than wrapping: a wrapped
 * line looks like a line break in the document, and telling those apart is the
 * job here. The container scrolls instead — the same rule the wide tables
 * follow.
 */
.fitpro-app .fitpro-richtext__code {
	font-family: var(--fp-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
	font-size: 13px;
	line-height: 1.6;
	tab-size: 2;
	white-space: pre;
	overflow-x: auto;
	resize: vertical;
}

/*
 * The table buttons.
 *
 * Snow ships no table icons, so these six get theirs from the app's own set,
 * injected through Quill's icon registry in `RichText.jsx`.
 *
 * ## Colour, and why it is stated rather than inherited
 *
 * These were CSS `content:` labels, and the rule drawing them set
 * `color: var(--fp-body)`. That selector is four classes and an element, and so
 * is the toolbar's own `button:hover { color: accent }` — a specificity tie,
 * resolved by source order, and the label rule came later. So the one control in
 * the row that did not light up on hover was the one that had been given a
 * colour of its own.
 *
 * Inheriting `currentColor` from the button fixed the hover and broke the
 * resting state instead: the button's colour is white, where a built-in glyph
 * draws at `--fp-body`. Measured, side by side: native `rgb(178, 184, 193)`,
 * ours `rgb(255, 255, 255)`.
 *
 * So the fill is stated here in exactly the shape Snow states its own — a
 * resting rule, then hover, focus and active — which is what makes both states
 * match the built-ins instead of one or the other.
 *
 * ## Size, measured from the ink rather than the box
 *
 * Both sets of buttons are 28px with 5px of padding, so on paper the spacing
 * already matched. It did not look like it, and `getBBox()` says why: Quill's
 * glyphs are sparse line drawings whose **drawn extent** is 8px for bold and
 * italic and 12px at the widest, the link. Bootstrap's are solid shapes that
 * fill their 16px viewBox edge to edge. Same boxes, a third more ink, and six of
 * them in a row read as crowded.
 *
 * 12px matches the widest built-in exactly, which also — since the icon is
 * centred in a 28px button with 5px padding — puts its left edge on 274px, the
 * same place the header picker's text starts. That is the alignment asked for,
 * and the harness asserts it so a later change to this number cannot quietly
 * undo it.
 */
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button.ql-table .fitpro-icon,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button[class^='ql-table-'] .fitpro-icon {
	/*
	 * `float: none` first, or none of the rest happens. Snow floats every
	 * toolbar SVG left and sizes it to the button's full height, and a floated
	 * element ignores `margin: auto` — so the icon sat flush against the padding
	 * edge, three pixels left of where the picker's text starts above it.
	 */
	float: none;
	width: 12px;
	height: 12px;
	margin: 0 auto;
	fill: var(--fp-body);
}

.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button:hover .fitpro-icon,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button:focus .fitpro-icon,
.fitpro-app .fitpro-richtext .ql-toolbar.ql-snow button.ql-active .fitpro-icon {
	fill: var(--fp-accent);
}

/*
 * One field and the button that acts on it, on a line.
 *
 * `.fitpro-actions` is `justify-content: flex-end` and `align-items: center`,
 * which is right for a pair of buttons and wrong for a field: the input ended up
 * at the right-hand end, vertically centred against its own label, with the
 * button stretched beside it. This is the same shape as `.fitpro-filters` — the
 * field takes the room, the button takes what it needs.
 */
.fitpro-app .fitpro-addrow {
	/*
	 * Stated, not inherited. `.fitpro-app *` sets `box-sizing: inherit` at a
	 * specificity of one class, so any theme rule that names an element or a
	 * class beats it — and Divi has one that lands on a plain `div` here. The
	 * chain then propagates `content-box` to everything inside, which turned a
	 * 44px min-height button into a 76px one beside a 50px twin. Same fight as
	 * the list markers above.
	 */
	box-sizing: border-box;
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-3);
	align-items: flex-end;
	margin-top: var(--fp-space-4);
}

.fitpro-app .fitpro-addrow > .fitpro-field {
	flex: 1 1 200px;
	/* Flex items default to `min-width: auto` and refuse to shrink — bug 52. */
	min-width: 0;
	margin-bottom: 0;
}

.fitpro-app .fitpro-addrow > .fitpro-btn {
	flex: 0 0 auto;
	/*
	 * The label is one word and must stay on one line. Shrink-to-fit measures a
	 * letter-spaced run including the trailing space after the last character,
	 * then lays it out without — so the box comes out a pixel under what the
	 * text needs, "Add" wraps, and the button grows to 76px against a 50px one
	 * beside it.
	 */
	white-space: nowrap;
}

/*
 * A post row that is both a link and a set of actions.
 *
 * `.fitpro-card-row` is a flex row and, everywhere else in the app, is itself
 * the button. A post cannot be: Edit, Pin, Archive and Delete sit inside it,
 * and a button inside a button is invalid HTML that browsers resolve by
 * dropping one of them. So the row became a `<div>` and the *body* became the
 * button — which is why this rule has to put back the reset the row used to
 * carry for it.
 */
.fitpro-app .fitpro-card-row__body--button {
	appearance: none;
	border: 0;
	padding: 0;
	background: none;
	color: inherit;
	font: inherit;
	text-align: left;
	cursor: pointer;
}

/*
 * The row's actions.
 *
 * Wrapping rather than shrinking: on a phone the four words go under the title
 * instead of squeezing into a column two characters wide. `flex-shrink: 0`
 * would win the argument against the title and lose the war.
 */
.fitpro-app .fitpro-card-row__tools {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-3);
	align-items: center;
	font-size: 13px;
}

/*
 * Search and category side by side, stacking when there is no room.
 *
 * `min-width: 0` on the children because a flex item's default `min-width` is
 * `auto` — bug 52 — and a `<select>` holding a long category name would
 * otherwise refuse to shrink and push the search box off the row.
 */
.fitpro-app .fitpro-filters {
	/* Stated for the same reason as `.fitpro-addrow` below — see the note there. */
	box-sizing: border-box;
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-3);
	align-items: flex-end;
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-filters > * {
	flex: 1 1 200px;
	min-width: 0;
	margin-bottom: 0;
}

/*
 * Destructive actions read as destructive.
 *
 * Colour alone would fail anyone who cannot see the difference, so Delete also
 * says "Delete" — this only reinforces a word that is already explicit.
 */
.fitpro-app .fitpro-linkish.is-danger {
	color: var(--fp-danger);
}

/* ---------------------------------------------------------------------------
 * Payments: a plan's terms, billing lists, and the notice that asks first.
 *
 * One row shape for subscriptions, payments, promo codes and tax registrations,
 * on the client's profile, the coach's client page and the Payments tab: the
 * facts on the left and the controls on the right, wrapping under the facts on a
 * phone rather than squeezing a button against a line of text (bug 52's shape).
 * ------------------------------------------------------------------------- */
.fitpro-app .fitpro-plan__terms {
	margin: calc(-1 * var(--fp-space-2)) 0 var(--fp-space-3);
	font-size: 13px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-billing-list {
	list-style: none !important;
	padding: 0 !important;
	margin: 0 0 var(--fp-space-3);
}

.fitpro-app .fitpro-billing-row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-2) var(--fp-space-3);
	padding: var(--fp-space-3) 0;
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-billing-row:last-child {
	border-bottom: 0;
}

/*
 * Blocks and subscriptions are two lists with nothing between them: read as
 * one, with the divider the rows inside a list already have, not a gap.
 */
.fitpro-app .fitpro-billing-list:has(+ .fitpro-billing-list) {
	margin-bottom: 0;
}

.fitpro-app .fitpro-billing-list:has(+ .fitpro-billing-list) > .fitpro-billing-row:last-child {
	border-bottom: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-billing-row__main {
	flex: 1 1 220px;
	min-width: 0;
	overflow-wrap: anywhere;
}

.fitpro-app .fitpro-billing-row__main p {
	margin: 4px 0 0;
	font-size: 13px;
}

.fitpro-app .fitpro-billing-row__actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-billing-row__link {
	font-weight: 700;
	white-space: nowrap;
}

.fitpro-app .fitpro-billing__heading {
	margin: var(--fp-space-4) 0 0;
	font-size: 14px;
}

.fitpro-app .fitpro-billing__form {
	margin-top: var(--fp-space-3);
	padding-top: var(--fp-space-3);
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-notice--warning {
	margin-top: var(--fp-space-3);
	background: var(--fp-warning-wash);
	color: var(--fp-warning-ink);
}

.fitpro-app .fitpro-notice--warning p {
	margin: 0 0 var(--fp-space-2);
}

.fitpro-app .fitpro-code {
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	letter-spacing: 1px;
}

/* A question about one row sits under that row, across its whole width. */
.fitpro-app .fitpro-billing-row > .fitpro-notice {
	flex: 1 1 100%;
	margin-top: 0;
}

/*
 * Tools on a record, sized like the meal row's: at the page's full pill size
 * "Cancel subscription" broke over two lines at 375px and read as the row's
 * main content.
 */
.fitpro-app .fitpro-billing-row__actions .fitpro-btn {
	min-height: 36px;
	padding: 8px 16px;
	font-size: 11px;
	letter-spacing: 1.5px;
	white-space: nowrap;
}

/* A form's own heading needs the space its first label would otherwise take. */
.fitpro-app .fitpro-billing__form > .fitpro-billing__heading {
	margin: 0 0 var(--fp-space-3);
}

/* --- Changing plan ------------------------------------------------------- */

.fitpro-app .fitpro-change__lines {
	margin: 0 0 var(--fp-space-4);
	padding-left: 1.2em;
	line-height: 1.6;
}

.fitpro-app .fitpro-change__lines li + li {
	margin-top: 4px;
}

/* --- Adding a food from its label --------------------------------------- */

/* A food of their own, with Edit beside the row that picks it. */
.fitpro-app .fitpro-foodlist--mine li {
	display: flex;
	align-items: stretch;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-foodlist--mine li > button:not(.fitpro-btn) {
	flex: 1;
	min-width: 0;
}

.fitpro-app .fitpro-foodlist__edit {
	flex: none;
	align-self: center;
}

/*
 * The My foods tab: a row that is not a button, because there is nothing to
 * pick there -- Edit and Delete are the actions. Drawn like a search result so
 * the same food looks the same in both places.
 */
.fitpro-app .fitpro-foodlist__row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--fp-space-2);
	padding: 10px var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
}

.fitpro-app .fitpro-foodlist__body {
	display: flex;
	flex: 1 1 14rem;
	flex-direction: column;
	gap: 3px;
	min-width: 0;
}

.fitpro-app .fitpro-foodlist__tools {
	display: flex;
	flex: none;
	gap: var(--fp-space-1);
	margin-left: auto;
}

/* Its own line under the food, so the question sits beside what it is about. */
.fitpro-app .fitpro-foodlist__confirm {
	flex: 1 1 100%;
}

.fitpro-app .fitpro-foodlist__confirm .fitpro-actions {
	margin-top: var(--fp-space-2);
}

.fitpro-app .fitpro-missing {
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-missing p {
	margin: 0 0 var(--fp-space-2);
}

.fitpro-app .fitpro-labelscan {
	display: flex;
	flex-direction: column;
	gap: var(--fp-space-2);
}

.fitpro-app .fitpro-labelscan .fitpro-btn .fitpro-icon {
	vertical-align: -3px;
	margin-right: 4px;
}

.fitpro-app .fitpro-labelscan__bar {
	height: 8px;
	border-radius: 4px;
	background: var(--fp-surface);
	overflow: hidden;
}

.fitpro-app .fitpro-labelscan__bar span {
	display: block;
	height: 100%;
	background: var(--fp-accent);
	transition: width 0.2s ease;
}

.fitpro-app .fitpro-foodform__barcode,
.fitpro-app .fitpro-items__barcode {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	align-items: start;
	gap: var(--fp-space-2);
}

/* Level with the input, not with its label: the label is the first row of the field. */
.fitpro-app .fitpro-foodform__barcode > .fitpro-btn,
.fitpro-app .fitpro-items__barcode > .fitpro-btn {
	margin-top: 26px;
}

.fitpro-app .fitpro-foodform__serving {
	display: grid;
	grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
	gap: var(--fp-space-2);
}

/* Two columns even on a phone: seven short figures in one column is a long scroll away from the photo. */
.fitpro-app .fitpro-foodform__figures,
.fitpro-app .fitpro-items__figures {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 0 var(--fp-space-2);
}

.fitpro-app .fitpro-check small {
	display: block;
	margin-top: 2px;
}

.fitpro-app .fitpro-check--disabled {
	cursor: default;
	opacity: 0.75;
}

/* A food read from a label, in the list being checked: named in the row, with its extras under the macros. */
.fitpro-app .fitpro-items__name-input {
	flex: 1;
	min-width: 0;
	font-weight: 700;
}

.fitpro-app .fitpro-items__custom {
	margin-top: var(--fp-space-3);
	padding-top: var(--fp-space-3);
	border-top: 1px dashed var(--fp-line);
}

.fitpro-app .fitpro-items__fix {
	display: inline-block;
	margin: 0 0 var(--fp-space-3);
	font-size: 13px;
}

.fitpro-app .fitpro-items__figures-note {
	grid-column: 1 / -1;
	margin-top: 0;
}


/* ---------------------------------------------------------------------------
 * Financials (`Financials.jsx`)
 * ------------------------------------------------------------------------ */

.fitpro-app .fitpro-visually-hidden {
	position: absolute !important;
	top: 0;
	left: 0;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
	border: 0;
}

.fitpro-app .fitpro-fin-figures {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
	gap: var(--fp-space-4);
}

.fitpro-app .fitpro-fin-figure {
	display: flex;
	flex-direction: column;
	gap: 6px;
	min-width: 0;
	padding: var(--fp-space-4);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius-lg);
	background: var(--fp-card);
}

.fitpro-app .fitpro-fin-figure__top {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--fp-space-3);
}

.fitpro-app .fitpro-fin-figure__label {
	font-size: 13px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-fin-figure__value {
	font-size: 26px;
	font-weight: 800;
	color: var(--fp-heading);
	line-height: 1.1;
}

.fitpro-app .fitpro-fin-figure__note {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-fin-figure__note.is-good {
	color: var(--fp-success);
}

.fitpro-app .fitpro-fin-figure__note.is-bad {
	color: var(--fp-danger);
}

.fitpro-app .fitpro-fin-spark {
	flex: none;
	width: 90px;
	height: 30px;
	stroke-width: 2;
}

.fitpro-app .fitpro-fin-spark--good {
	stroke: var(--fp-success);
}

.fitpro-app .fitpro-fin-spark--bad {
	stroke: var(--fp-danger);
}

/* The chart beside the memberships on a wide screen, above them on anything narrower. */
.fitpro-app .fitpro-fin-split {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--fp-space-5);
	align-items: start;
}

@media (min-width: 1100px) {
	.fitpro-app .fitpro-fin-split {
		grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
	}
}

.fitpro-app .fitpro-fin-legend {
	display: flex;
	flex-wrap: wrap;
	gap: var(--fp-space-4);
	list-style: none !important;
	padding: 0 !important;
	margin: 0 0 var(--fp-space-3);
	font-size: 12px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-fin-legend__item::before {
	content: '';
	display: inline-block;
	width: 10px;
	height: 10px;
	margin-right: 6px;
	border-radius: 50%;
	background: currentColor;
	vertical-align: -1px;
}

.fitpro-app .fitpro-fin-legend__item--revenue::before,
.fitpro-app .fitpro-fin-chart__line--revenue {
	color: var(--fp-muted);
	stroke: var(--fp-muted);
}

.fitpro-app .fitpro-fin-legend__item--net::before,
.fitpro-app .fitpro-fin-chart__line--net {
	color: var(--fp-success);
	stroke: var(--fp-success);
}

.fitpro-app .fitpro-fin-legend__item--refunded::before,
.fitpro-app .fitpro-fin-chart__line--refunded {
	color: var(--fp-danger);
	stroke: var(--fp-danger);
}

.fitpro-app .fitpro-fin-chart {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	grid-template-rows: 220px auto;
	column-gap: var(--fp-space-3);
}

.fitpro-app .fitpro-fin-chart__y {
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	font-size: 11px;
	color: var(--fp-muted);
	text-align: right;
	line-height: 1;
}

.fitpro-app .fitpro-fin-chart__plot {
	width: 100%;
	height: 100%;
	overflow: visible;
}

.fitpro-app .fitpro-fin-chart__grid {
	stroke: var(--fp-line);
	stroke-width: 1;
}

.fitpro-app .fitpro-fin-chart__line {
	stroke-width: 2.5;
	stroke-linejoin: round;
}

.fitpro-app .fitpro-fin-chart__x {
	grid-column: 2;
	display: flex;
	justify-content: space-between;
	margin-top: 6px;
	font-size: 11px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-fin-chart__x span {
	flex: 0 0 0;
	white-space: nowrap;
	display: flex;
	justify-content: center;
}

@media (max-width: 600px) {
	.fitpro-app .fitpro-fin-chart__x .is-minor {
		visibility: hidden;
	}
}

.fitpro-app .fitpro-fin-members {
	list-style: none !important;
	padding: 0 !important;
	margin: 0;
}

.fitpro-app .fitpro-fin-members li {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: var(--fp-space-3) 0;
}

.fitpro-app .fitpro-fin-members li + li {
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-fin-members__terms {
	font-weight: 700;
	color: var(--fp-heading);
	font-size: 13px;
}

.fitpro-app .fitpro-fin-rows {
	list-style: none !important;
	padding: 0 !important;
	margin: 0;
}

.fitpro-app .fitpro-fin-row {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	gap: 4px var(--fp-space-3);
	align-items: center;
	padding: var(--fp-space-3) 0;
}

.fitpro-app .fitpro-fin-row + .fitpro-fin-row {
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-fin-row > * {
	justify-self: start;
	text-align: left;
}

.fitpro-app .fitpro-fin-row > .fitpro-fin-status {
	justify-self: end;
}

.fitpro-app .fitpro-fin-members li > .fitpro-linkish {
	align-self: flex-start;
	text-align: left;
}

.fitpro-app .fitpro-fin-row__name {
	font-weight: 700;
	color: var(--fp-heading);
	text-align: left;
}

.fitpro-app .fitpro-fin-row__date,
.fitpro-app .fitpro-fin-row__plan {
	font-size: 13px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-fin-row__amount {
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-fin-row--head {
	display: none;
}

/* A table's columns once there is room for them; a two-column card below. */
@media (min-width: 760px) {
	.fitpro-app .fitpro-fin-row {
		/* Every column a fixed share, never `auto`: each row is its own grid, so an
		   auto column sized to one row's status left the others out of line. */
		grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1.4fr) minmax(0, 1fr) 9rem;
	}

	.fitpro-app .fitpro-fin-rows--plans .fitpro-fin-row {
		grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
	}

	.fitpro-app .fitpro-fin-rows--plans .fitpro-fin-row--head {
		display: grid;
		font-size: 12px;
		text-transform: uppercase;
		letter-spacing: 1px;
		color: var(--fp-muted);
	}
}

.fitpro-app .fitpro-fin-status--succeeded {
	background: var(--fp-success-wash, rgb(26 127 55 / 12%));
	color: var(--fp-success);
}

.fitpro-app .fitpro-fin-status--failed,
.fitpro-app .fitpro-fin-status--refunded {
	background: var(--fp-danger-wash, rgb(209 36 47 / 12%));
	color: var(--fp-danger);
}


/* The default coach sits under the default units in Settings -> Account -> Clients. */
.fitpro-app .fitpro-default-coach {
	margin-top: var(--fp-space-5);
	padding-top: var(--fp-space-4);
	border-top: 1px solid var(--fp-line);
}

/* ---------------------------------------------------------------------------
 * Analytics: figures, charts, habit calendars, bars and dials.
 *
 * Thin marks and a recessive grid; values and labels in ink, never in the
 * series colour -- a short key beside them carries which series it is.
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-analytics__section {
	margin: var(--fp-space-4) 0 0;
	padding: 0;
}

/* A new range loading: the previous numbers stay, dimmed, rather than a skeleton flash. */
.fitpro-app .fitpro-analytics__group.is-loading {
	opacity: 0.55;
	transition: opacity 0.2s ease;
}

.fitpro-app .fitpro-analytics__muted {
	color: var(--fp-muted);
	font-size: 12px;
}

.fitpro-app .fitpro-figures {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 140px), 1fr));
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-figure {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}

.fitpro-app .fitpro-figure__label {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-figure__value {
	font-family: var(--fp-font-heading);
	font-size: 24px;
	font-weight: 700;
	line-height: 1.1;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-figure__note {
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-viz {
	position: relative;
	margin: var(--fp-space-2) 0;
}

.fitpro-app .fitpro-viz__plot {
	position: relative;
	touch-action: pan-y;
	border-radius: var(--fp-radius);
}

.fitpro-app .fitpro-viz__plot:focus-visible {
	outline: 2px solid var(--fp-accent);
	outline-offset: 4px;
}

.fitpro-app .fitpro-viz__svg {
	display: block;
	width: 100%;
	height: auto;
	overflow: visible;
}

/* No font-size: it is set in viewBox units on the element, which CSS would override (see .fitpro-chart__tick). */
.fitpro-app .fitpro-viz__tick {
	fill: var(--fp-muted);
	font-family: var(--fp-font-body);
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-viz__grid {
	stroke: var(--fp-line);
	stroke-width: 1;
	vector-effect: non-scaling-stroke;
}

.fitpro-app .fitpro-viz__line {
	stroke-width: 2;
	stroke-linejoin: round;
	stroke-linecap: round;
	vector-effect: non-scaling-stroke;
}

.fitpro-app .fitpro-viz__cross {
	stroke: var(--fp-line-strong);
	stroke-width: 1;
	vector-effect: non-scaling-stroke;
}

.fitpro-app .fitpro-viz__target {
	stroke: var(--fp-heading);
	stroke-width: 2;
	stroke-linecap: round;
	vector-effect: non-scaling-stroke;
}

/* The ring in the card colour keeps a dot legible where it crosses a line. */
.fitpro-app .fitpro-viz__dot {
	stroke: var(--fp-card);
	stroke-width: 2;
	vector-effect: non-scaling-stroke;
}

.fitpro-app .fitpro-viz__bar.is-active {
	opacity: 0.75;
}

/* The series colours, one class per tone. */
.fitpro-app .fitpro-viz__line--accent {
	stroke: var(--fp-accent);
}

.fitpro-app .fitpro-viz__line--muted {
	stroke: var(--fp-muted);
}

.fitpro-app .fitpro-viz__dot--accent,
.fitpro-app .fitpro-viz__bar--accent {
	fill: var(--fp-accent);
}

.fitpro-app .fitpro-viz__fill--accent {
	background: var(--fp-accent);
}

.fitpro-app .fitpro-viz__dot--muted {
	fill: var(--fp-muted);
}

.fitpro-app .fitpro-viz__bar--calories {
	fill: var(--fp-macro-calories);
}

.fitpro-app .fitpro-viz__legend {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 16px;
	margin: 0 0 var(--fp-space-2) !important;
	padding: 0 !important;
	list-style: none !important;
	font-size: 12px;
	color: var(--fp-body);
}

.fitpro-app .fitpro-viz__legend li {
	display: inline-flex;
	align-items: center;
	gap: 6px;
}

/* A short stroke for a line, a dot for dots, a square for a bar. */
.fitpro-app .fitpro-viz__key {
	display: inline-block;
	flex: none;
	width: 14px;
	height: 3px;
	border-radius: 2px;
	background: var(--fp-muted);
}

.fitpro-app .fitpro-viz__key--dots {
	width: 8px;
	height: 8px;
	border-radius: 50%;
}

.fitpro-app .fitpro-viz__key--bar {
	width: 10px;
	height: 10px;
}

.fitpro-app .fitpro-viz__key--accent {
	background: var(--fp-accent);
}

.fitpro-app .fitpro-viz__key--muted {
	background: var(--fp-muted);
}

.fitpro-app .fitpro-viz__key--calories {
	background: var(--fp-macro-calories);
}

.fitpro-app .fitpro-viz__key--target {
	background: var(--fp-heading);
}

.fitpro-app .fitpro-viz__tip {
	position: absolute;
	top: 0;
	z-index: 2;
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 120px;
	max-width: 70%;
	padding: 8px 10px;
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	box-shadow: var(--fp-shadow);
	font-size: 12px;
	color: var(--fp-body);
	pointer-events: none;
	transform: translateY(-4px);
}

.fitpro-app .fitpro-viz__tip-title {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-viz__tip-row {
	display: flex;
	align-items: center;
	gap: 6px;
	white-space: nowrap;
}

.fitpro-app .fitpro-viz__tip-row strong {
	color: var(--fp-heading);
}

.fitpro-app .fitpro-viz__spark {
	display: block;
	width: 120px;
	height: 32px;
	overflow: visible;
}

.fitpro-app .fitpro-analytics__changes {
	width: 100%;
	margin-top: var(--fp-space-3);
	border-collapse: collapse;
	font-size: 14px;
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-analytics__changes caption {
	margin-bottom: var(--fp-space-2);
	text-align: left;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-analytics__changes th,
.fitpro-app .fitpro-analytics__changes td {
	padding: 6px 0;
	border-top: 1px solid var(--fp-line);
	text-align: left;
	font-weight: 400;
}

/*
 * The change is right-aligned in a column of its own, so the unit sits in the
 * same place on every row: left-aligned, "−0.3 lb" pushed its "lb" past the
 * "lb" of "0 lb" above it.
 */
.fitpro-app .fitpro-analytics__changes td:nth-child(2) {
	width: 8rem;
	padding-right: var(--fp-space-5);
	text-align: right;
	white-space: nowrap;
}

.fitpro-app .fitpro-analytics__changes td:last-child {
	text-align: right;
}

/*
 * A habit calendar: a column a week, a square a day. Columns are at most 14px
 * and shrink on a phone, so a year still fits and a month is not stretched into
 * squares with gulfs between them.
 */
.fitpro-app .fitpro-habitcal {
	display: grid;
	grid-template-columns: repeat(var(--fp-habit-weeks, 13), minmax(0, 14px));
	justify-content: start;
	gap: 3px;
	max-width: 100%;
}

.fitpro-app .fitpro-habitcal__week {
	display: grid;
	grid-template-rows: repeat(7, auto);
	gap: 3px;
}

.fitpro-app .fitpro-habitcal__day {
	display: block;
	width: 100%;
	aspect-ratio: 1;
	border-radius: 3px;
	background: var(--fp-line);
}

/* After the grey above, which a fill class declared earlier in this file would otherwise lose to. */
.fitpro-app .fitpro-habitcal__day--done {
	background: var(--fp-accent);
}

.fitpro-app .fitpro-habitcal__day--outside {
	background: transparent;
}

.fitpro-app .fitpro-habits-analytics {
	display: grid;
	gap: var(--fp-space-4);
}

.fitpro-app .fitpro-habits-analytics__head {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: 4px 12px;
	margin-bottom: 6px;
}

.fitpro-app .fitpro-barlist {
	display: grid;
	gap: 10px;
	margin: 0 !important;
	padding: 0 !important;
	list-style: none !important;
}

.fitpro-app .fitpro-barlist li {
	display: grid;
	grid-template-columns: minmax(70px, 7rem) minmax(0, 1fr) auto;
	align-items: center;
	gap: 10px;
	font-size: 14px;
}

.fitpro-app .fitpro-barlist__track {
	display: block;
	height: 10px;
}

.fitpro-app .fitpro-barlist__fill {
	display: block;
	height: 100%;
	border-radius: 0 4px 4px 0;
}

.fitpro-app .fitpro-barlist__value {
	color: var(--fp-body);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

.fitpro-app .fitpro-lifts {
	display: grid;
	margin: 0 !important;
	padding: 0 !important;
	list-style: none !important;
}

.fitpro-app .fitpro-lifts__row {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto auto;
	align-items: center;
	gap: 12px;
	padding: 10px 0;
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-lifts__row:first-child {
	border-top: 0;
}

.fitpro-app .fitpro-lifts__name,
.fitpro-app .fitpro-lifts__figure {
	display: flex;
	flex-direction: column;
	min-width: 0;
}

.fitpro-app .fitpro-lifts__figure {
	align-items: flex-end;
	white-space: nowrap;
}

@media (max-width: 400px) {
	.fitpro-app .fitpro-lifts__row {
		grid-template-columns: minmax(0, 1fr) auto;
	}

	.fitpro-app .fitpro-lifts__row .fitpro-viz__spark {
		display: none;
	}
}

.fitpro-app .fitpro-dials {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--fp-space-3);
	margin: var(--fp-space-3) 0;
}

.fitpro-app .fitpro-dial {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 4px;
	text-align: center;
}

.fitpro-app .fitpro-dial__value {
	font-family: var(--fp-font-heading);
	font-size: 18px;
	font-weight: 700;
	color: var(--fp-heading);
}

.fitpro-app .fitpro-dial__pct {
	font-size: 12px;
	color: var(--fp-muted);
}


/* ---------------------------------------------------------------------------
 * Day tasks: the program days view, the picker, and the lists
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-programdays {
	min-width: 0;
	margin-top: var(--fp-space-3);
}

/* ---------------------------------------------------------------------------
 * A chart or a list (`DataView.jsx`)
 *
 * The switch sits in the card's header beside the title, so it takes the
 * segmented control's own bottom margin off; on a phone the header wraps and it
 * drops under the title.
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-viewtoggle .fitpro-segmented-wrap {
	margin-bottom: 0;
}

.fitpro-app .fitpro-datatable {
	margin-top: var(--fp-space-3);
}

/*
 * Its own scroller, so a list with an extra column scrolls inside the card on
 * a narrow phone instead of pushing the page sideways (bug 52).
 */
.fitpro-app .fitpro-datatable__scroll {
	overflow-x: auto;
}

.fitpro-app .fitpro-datatable table {
	width: 100%;
	border-collapse: collapse;
	font-size: 14px;
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-datatable th,
.fitpro-app .fitpro-datatable td {
	padding: 8px 10px 8px 0;
	border-top: 1px solid var(--fp-line);
	text-align: left;
	vertical-align: top;
	font-weight: 400;
	color: var(--fp-body);
}

.fitpro-app .fitpro-datatable thead th {
	border-top: 0;
	padding-top: 0;
	font-size: 12px;
	font-weight: 600;
	color: var(--fp-muted);
	white-space: nowrap;
}

.fitpro-app .fitpro-datatable tbody th {
	color: var(--fp-heading);
}

.fitpro-app .fitpro-datatable th:last-child,
.fitpro-app .fitpro-datatable td:last-child {
	padding-right: 0;
}

/* Figures right-aligned, so units and decimals line up down the column. */
.fitpro-app .fitpro-datatable .fitpro-datatable__num {
	text-align: right;
	white-space: nowrap;
}

/* When it was entered, under the date. */
.fitpro-app .fitpro-datatable__sub {
	display: block;
	margin-top: 2px;
	font-size: 12px;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-datatable__yes {
	color: var(--fp-heading);
	font-weight: 700;
}

.fitpro-app .fitpro-datatable__no {
	color: var(--fp-muted);
}

.fitpro-app .fitpro-analytics__lift-list + .fitpro-analytics__lift-list {
	margin-top: var(--fp-space-4);
}

.fitpro-app .fitpro-analytics__lift-name {
	margin: 0;
}

/* The run's sentence on the left, the calendar/list toggle on the right. */
.fitpro-app .fitpro-programdays__head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--fp-space-2) var(--fp-space-3);
}

.fitpro-app .fitpro-programdays__head .fitpro-chart__note {
	margin: 0;
}

.fitpro-app .fitpro-programdays__head .fitpro-segmented-wrap {
	margin-bottom: 0;
}

.fitpro-app .fitpro-programdays .fitpro-cal-wrap {
	margin-top: var(--fp-space-3);
}

/* Two across on a phone, three from a tablet: six kinds as even rows. */
.fitpro-app .fitpro-taskpick {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--fp-space-2);
	margin: var(--fp-space-3) 0;
}

@media (min-width: 600px) {
	.fitpro-app .fitpro-taskpick {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

.fitpro-app .fitpro-taskpick__option {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 8px;
	min-height: 92px;
	padding: 12px 8px;
	border: 1px solid var(--fp-line-strong);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
	color: var(--fp-heading);
	font-family: var(--fp-font-body);
	font-size: 13px;
	font-weight: 700;
	text-align: center;
	cursor: pointer;
}

.fitpro-app .fitpro-taskpick__option .fitpro-icon {
	color: var(--fp-accent);
}

.fitpro-app .fitpro-taskpick__option:hover {
	border-color: var(--fp-accent);
}

/* Chosen: a heavier accent border and a wash, not the wash alone. */
.fitpro-app .fitpro-taskpick__option[aria-pressed='true'] {
	border: 2px solid var(--fp-accent);
	padding: 11px 7px;
	background: var(--fp-accent-soft);
}

.fitpro-app .fitpro-taskpick__form {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: var(--fp-space-2) var(--fp-space-3);
}

.fitpro-app .fitpro-taskpick__form .fitpro-field {
	flex: 1 1 220px;
	margin-bottom: 0;
}

.fitpro-app .fitpro-daytask__line {
	display: inline-flex;
	align-items: center;
	gap: 8px;
}

.fitpro-app .fitpro-daytask__line .fitpro-icon {
	flex: none;
	color: var(--fp-accent);
}

.fitpro-app .fitpro-daytask-list {
	margin: var(--fp-space-3) 0 0 !important;
	padding: 0 !important;
	list-style: none !important;
}

.fitpro-app .fitpro-daytask-list li + li {
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-daytask-list__row {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr) auto auto;
	align-items: center;
	gap: var(--fp-space-2);
	width: 100%;
	min-height: var(--fp-tap-min);
	padding: 6px 0;
	border: 0;
	background: none;
	color: var(--fp-heading);
	font: inherit;
	text-align: left;
	cursor: pointer;
}

.fitpro-app .fitpro-daytask-list__row .fitpro-icon {
	color: var(--fp-accent);
}

.fitpro-app .fitpro-daytask-list__what {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.fitpro-app .fitpro-daytask-list__when {
	color: var(--fp-muted);
	font-size: 12px;
	white-space: nowrap;
}

/* The client's Today: a task's icon beside its instruction. */
.fitpro-app .fitpro-tasks__icon {
	display: inline-flex;
	flex: none;
	color: var(--fp-accent);
}

.fitpro-app .fitpro-tasks__row .fitpro-tasks__body {
	flex: 1 1 auto;
}

/* ---------------------------------------------------------------------------
 * The workout clock, pause, and the finished workout
 * ------------------------------------------------------------------------- */

/* Time so far and Pause, one line under the title. */
.fitpro-app .fitpro-logger__clock {
	display: flex;
	align-items: center;
	gap: var(--fp-space-3);
	margin: 0 0 var(--fp-space-4);
	padding: var(--fp-space-2) var(--fp-space-3);
	border: 1px solid var(--fp-line);
	border-radius: var(--fp-radius);
	background: var(--fp-card);
}

.fitpro-app .fitpro-logger__clock-label {
	color: var(--fp-muted);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
}

/* Tabular figures, so the time does not jitter as it counts. */
.fitpro-app .fitpro-logger__clock-value {
	margin-right: auto;
	color: var(--fp-heading);
	font-size: 22px;
	font-variant-numeric: tabular-nums;
}

.fitpro-app .fitpro-logger__clock .fitpro-btn {
	min-height: var(--fp-tap-min);
}

/*
 * On a phone the two choices stack full width, Resume first: side by side,
 * "Leave and resume later" wraps to two lines beside a one-line button.
 */
@media (max-width: 559px) {
	.fitpro-app .fitpro-logger__paused-actions {
		flex-direction: column-reverse;
		align-items: stretch;
	}

	.fitpro-app .fitpro-logger__paused-actions .fitpro-btn {
		width: 100%;
	}
}

/* Paused: the label in the warning ink, as well as the word. */
.fitpro-app .fitpro-logger__clock.is-paused .fitpro-logger__clock-label {
	color: var(--fp-warning);
}

.fitpro-app .fitpro-logger__done-title {
	margin: 0 0 var(--fp-space-3);
	color: var(--fp-heading);
	font-weight: 700;
}

.fitpro-app .fitpro-logger__done {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--fp-space-3);
	margin-bottom: var(--fp-space-3);
}

.fitpro-app .fitpro-logger__done-label {
	display: block;
	color: var(--fp-muted);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 1px;
	text-transform: uppercase;
}

.fitpro-app .fitpro-logger__done-value {
	display: block;
	color: var(--fp-heading);
	font-size: 24px;
	font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
 * PickerGrid: "which of these, as many as you like"
 * ------------------------------------------------------------------------- */

.fitpro-app .fitpro-multipick {
	border: 0;
	margin: 0 0 20px;
	padding: 0;
	/*
	 * A fieldset refuses to shrink below its content's min-content width unless
	 * told, and pushes the page sideways instead. Bug 52's family, and the one
	 * element with its own rule for it — `HeightField` already carries the same
	 * line for the same reason.
	 */
	min-inline-size: 0;
}

.fitpro-app .fitpro-multipick__legend {
	padding: 0;
	margin-bottom: 4px;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.3px;
	text-transform: uppercase;
	color: var(--fp-muted);
}

.fitpro-app .fitpro-multipick__help,
.fitpro-app .fitpro-multipick__empty {
	margin: 0 0 8px;
}

.fitpro-app .fitpro-multipick__empty {
	margin: 8px 0 0;
}

/*
 * `auto-fill`, not `auto-fit`. With `auto-fit` a row holding three tiles
 * stretches them across the whole width, so the same seven options render at
 * wildly different sizes depending on the viewport — and a two-item row would
 * be two half-page buttons. `auto-fill` keeps the track size and leaves the
 * remainder empty, so a tile is the same tile everywhere.
 */
.fitpro-app .fitpro-multipick__grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
	gap: 8px;
}

.fitpro-app .fitpro-multipick__option {
	position: relative;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 6px;
	/* Comfortably past the 24px minimum in both directions; this is a control
	   a coach uses at a desk rather than one-handed mid-set. */
	min-height: 44px;
	padding: 10px 8px;
	border: 1px solid var(--fp-line);
	border-radius: 10px;
	background: var(--fp-card);
	color: var(--fp-body);
	font: inherit;
	font-size: 12px;
	text-align: center;
	cursor: pointer;
}

.fitpro-app .fitpro-multipick__option:hover {
	border-color: var(--fp-accent);
}

/*
 * Picked is a filled border **and** a tick **and** `aria-pressed`. Never colour
 * alone — the rule the macro icons and the superset pills already follow, so a
 * reader who cannot separate the accent from the card still sees which are on.
 */
.fitpro-app .fitpro-multipick__option.is-picked {
	border-color: var(--fp-accent-fill);
	box-shadow: inset 0 0 0 1px var(--fp-accent-fill);
	background: color-mix(in srgb, var(--fp-accent) 8%, var(--fp-card));
}

/*
 * The pictures are product shots of black rubber and dark steel, so the plate
 * is white in **both** themes. On a dark card they would be very nearly
 * invisible — bug 63's shape, and why the signature pad is white in both
 * themes too. Drawn here rather than flattened into the file, so the artwork
 * keeps its transparency and a theme can still answer for it.
 */
.fitpro-app .fitpro-multipick__plate {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	aspect-ratio: 1;
	max-width: 72px;
	border-radius: 8px;
	background: #ffffff;
}

.fitpro-app .fitpro-multipick__plate img {
	width: 100%;
	height: 100%;
	object-fit: contain;
}

/*
 * `width: 100%`, and it is load-bearing rather than tidying.
 *
 * The tile is a column flex container with `align-items: center`, so a child
 * is sized to its **min-content** width on the cross axis. `overflow-wrap:
 * anywhere` sets min-content to one character — that is what the property
 * means — so the two together collapsed this span to 32px inside a 117px tile
 * and broke every label mid-word: "Bodywei ght", "Kettlebe ll", "Shoulde rs".
 * Measured, because it reads as a tile too narrow for its text and is the
 * opposite: the text box is narrow and the tile has room to spare.
 *
 * `break-word` rather than `anywhere` for the same reason — it leaves
 * min-content at the longest word, so a long provider label ("none (bodyweight
 * exercise)") still wraps inside its tile without every other label paying for
 * it.
 */
.fitpro-app .fitpro-multipick__name {
	width: 100%;
	line-height: 1.25;
	overflow-wrap: break-word;
}

.fitpro-app .fitpro-multipick__tick {
	position: absolute;
	top: 6px;
	right: 6px;
	display: flex;
	width: 18px;
	height: 18px;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	color: var(--fp-accent-contrast);
}

.fitpro-app .fitpro-multipick__option.is-picked .fitpro-multipick__tick {
	background: var(--fp-accent-fill);
}

.fitpro-app .fitpro-multipick__count {
	margin: 8px 0 0;
	font-size: 12px;
	color: var(--fp-muted);
}

/*
 * How many exercises sit behind a tile. Second line, muted, never appended to
 * the label: "Chest (127)" is one string a translator has to keep a number
 * inside, and it makes a two-word label wrap where the bare word would not.
 */
.fitpro-app .fitpro-multipick__hint {
	font-size: 11px;
	line-height: 1.2;
	color: var(--fp-muted);
}

/*
 * The rail is the positioning context for the carets and nothing else.
 *
 * It is `position: relative` at every width even though the carets only ever
 * show on a phone: an absolutely positioned child looks for its nearest
 * positioned ancestor, and leaving that to whichever card happens to be above
 * would put a stray caret somewhere else on the page entirely the first time
 * one was shown by mistake.
 */
.fitpro-app .fitpro-multipick__rail {
	position: relative;
}

/*
 * Scroll hints, overlaid on the ends of the track.
 *
 * A round chip rather than the fading gradient the tab bar and the segmented
 * control use, and that is not a style preference. Those two sit on a track
 * whose colour is known -- the pill, the bar -- so a gradient in that colour
 * dissolves into it. This grid sits on a card in the Generate forms and
 * directly on the page in the exercise library, so there is no one colour to
 * fade to and a gradient would read as a smudge on whichever of the two it
 * guessed wrong.
 *
 * `display: none` by default and `flex` only where the rail exists, so a
 * mis-measurement cannot float a caret over a grid that does not scroll. The
 * `[hidden]` rule outranks both, being one class heavier.
 */
.fitpro-app .fitpro-multipick__nudge {
	position: absolute;
	top: 50%;
	z-index: 1;
	display: none;
	width: 28px;
	height: 28px;
	align-items: center;
	justify-content: center;
	padding: 0;
	border: 1px solid var(--fp-line);
	border-radius: 50%;
	background: var(--fp-card);
	color: var(--fp-heading);
	box-shadow: 0 1px 4px rgb(0 0 0 / 0.18);
	transform: translateY(-50%);
	cursor: pointer;
}

.fitpro-app .fitpro-multipick__nudge--left {
	left: -2px;
}

.fitpro-app .fitpro-multipick__nudge--right {
	right: -2px;
}

/* An author `display` beats the user agent's `[hidden] { display: none }`, so
   without this both carets would sit there permanently -- including a left one
   pointing at nothing. Bug 23's neighbour, in a third place. */
.fitpro-app .fitpro-multipick__nudge[hidden] {
	display: none;
}

.fitpro-app .fitpro-multipick__nudge svg {
	fill: currentColor;
}

/*
 * On a phone the grid is one scrolling row.
 *
 * Fourteen body parts two-up is seven rows of tiles between somebody and the
 * exercises they came to look at. The tab bar already solves this shape, so
 * this is the same solution rather than a second one: scroll sideways, hint
 * with carets, and keep every tile the size it is everywhere else.
 */
@media (max-width: 560px) {
	.fitpro-app .fitpro-multipick__grid {
		display: flex;
		flex-wrap: nowrap;
		overflow-x: auto;
		overflow-y: hidden;
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
		overscroll-behavior-x: contain;
	}

	.fitpro-app .fitpro-multipick__grid::-webkit-scrollbar {
		display: none;
	}

	/*
	 * Never shrink, and a stated width so a one-word tile and a three-word one
	 * are the same tile. On the child rather than on `__option`, which is the
	 * lesson the segmented control records: a rule written against the item
	 * class loses to anything naming an element, and the guarantee is then not
	 * in force wherever that other rule lives.
	 */
	.fitpro-app .fitpro-multipick__grid > * {
		flex: 0 0 auto;
		width: 104px;
	}

	.fitpro-app .fitpro-multipick__nudge {
		display: flex;
	}
}

/*
 * Moving a library between data sources.
 *
 * Sits under the Data sources grid and is read far more often than it is used:
 * the panel exists so a coach can see what a migration would do, and the shape
 * follows that -- a sentence, then the workouts that keep an exercise, then the
 * per-reference detail folded away because almost nobody needs it.
 */
.fitpro-app .fitpro-migration__source + .fitpro-migration__source {
	margin-top: 20px;
	padding-top: 20px;
	border-top: 1px solid var(--fp-line);
}

.fitpro-app .fitpro-migration__preview {
	margin-top: 12px;
}

.fitpro-app .fitpro-migration__preview h5 {
	margin: 16px 0 8px;
	font-size: 13px;
}

/*
 * The bullets are stripped app-wide to defeat Divi's `#left-area ul` (bug 38),
 * and these are rows rather than prose, so nothing is put back.
 */
.fitpro-app .fitpro-migration__workouts,
.fitpro-app .fitpro-migration__refs {
	display: grid;
	gap: 6px;
	margin: 0;
}

.fitpro-app .fitpro-migration__workouts li {
	display: grid;
	gap: 2px;
}

/*
 * `minmax(0, …)` on the flexible column, not a bare fraction: a long reference
 * sets the automatic minimum and pushes the answer off the end (bug 59), on the
 * one row whose right-hand side is what somebody came to read.
 */
.fitpro-app .fitpro-migration__refs li {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	gap: 4px 12px;
	align-items: baseline;
}

.fitpro-app .fitpro-migration__refs code {
	overflow-wrap: anywhere;
	font-size: 12px;
}

/*
 * The answer is its own element rather than "whatever is last".
 *
 * A mapped row carries a name after it and an undecided one does not, so a
 * check written against the last child was comparing two different things and
 * could not fail -- which is what the harness reported when one was pushed out
 * on purpose. Naming it is what makes the column checkable.
 */
.fitpro-app .fitpro-migration__state {
	justify-self: end;
	text-align: right;
	font-size: 13px;
}

.fitpro-app .fitpro-migration__refs .fitpro-muted {
	grid-column: 1 / -1;
	font-size: 12px;
}

.fitpro-app .fitpro-migration__detail {
	margin: 16px 0;
}

.fitpro-app .fitpro-migration__detail summary {
	cursor: pointer;
	color: var(--fp-body);
	font-size: 13px;
}
