/*gg-src:1787700382*/
/* ── The page gutter ──
 * --gg-page-gutter is declared with the layout tokens in the :root block
 * above. It narrows on a phone, where 24px a side is 12% of the screen.
 * Declared here rather than up there because :root is built from the brand
 * record and carries no media queries. */
@media (max-width: 600px) {
	:root { --gg-page-gutter: 16px; }
}

/* .gg-page-gutter — the same floor, for a page that is NOT on .gg-page-bg.
 * Email and Finance sit on WordPress's own `.wrap` and nothing else, and WP
 * gives that 0 on the left against 12px on the right once the window is
 * narrow — so their cards touched one edge and not the other (measured at
 * 390px, 16 Aug 2026). A page adds this class next to `.wrap` and gets the
 * platform's gutter, evenly, instead of each plugin writing the same three
 * lines into its own stylesheet. */
.gg-page-gutter {
	box-sizing: border-box;
	margin-left: 0;
	margin-right: 0;
	padding-left: var(--gg-page-gutter);
	padding-right: var(--gg-page-gutter);
}

/* ── Icon registry (backlog #17, audited 2026-07-15/16) ──
 * Baseline only — every icon is a self-contained inline <svg> sized via its
 * own width/height attributes and coloured via currentColor, so it never
 * needs a bespoke CSS rule. This just normalises how it sits next to text.
 * See gg_ui_icon()'s docblock (gg-brand/inc/components.php) for the full
 * catalogue and render signature. */
.gg-icon {
	display: inline-block;
	vertical-align: middle;
	flex-shrink: 0;
}

/* ── Buttons (donor: gg-brand-showcase.php) ── */
/* A DISABLED SHARED BUTTON HAS TO LOOK DISABLED (2026-08-20). There was no rule
   for it anywhere, so every screen that needed one hand-wrote its own opacity
   and cursor - and a button that did not was drawn at full contrast, with the
   pointer and the hover lift, and did nothing when pressed. That is the exact
   fault the platform keeps guarding against, in the one component every screen
   borrows. Named once here so no page has to say it again. */
.gg-btn:disabled,
.gg-btn[aria-disabled="true"] {
	opacity: .45;
	cursor: default;
	box-shadow: none;
}
.gg-btn:disabled:hover,
.gg-btn[aria-disabled="true"]:hover {
	transform: none;
	box-shadow: none;
}

.gg-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: calc(var(--gg-space-base) * 0.75);
	padding: calc(var(--gg-space-base) * 1.5) calc(var(--gg-space-base) * 3);
	/* Gradient-aware: --gg-gradient-primary is always emitted (flat hex when
	   the gradient toggle is off), primary stays the fallback. Border below
	   deliberately stays the flat colour. */
	background: var(--gg-gradient-primary, var(--gg-colour-primary));
	color: #ffffff;
	font-family: var(--gg-font-heading);
	font-weight: var(--gg-weight-heading);
	font-size: var(--gg-body-size);
	line-height: 1.2;
	text-align: center;
	text-decoration: none;
	border: var(--gg-border-width) solid var(--gg-colour-primary);
	border-radius: var(--gg-radius-md);
	box-shadow: var(--gg-shadow-sm);
	cursor: pointer;
	transition: transform var(--gg-transition-speed) var(--gg-transition-easing),
	            box-shadow var(--gg-transition-speed) var(--gg-transition-easing),
	            background var(--gg-transition-speed) var(--gg-transition-easing),
	            color var(--gg-transition-speed) var(--gg-transition-easing),
	            border-color var(--gg-transition-speed) var(--gg-transition-easing);
}
/* .gg-btn--large — the one action a screen is FOR (2026-08-03, Sam: "Go Build
   This button should be bigger, and centered"). Not a second button style: the
   same button, sized for the moment a coach commits. Added here rather than on
   the project page so the next screen with a single decisive action inherits it
   instead of inventing its own. */
.gg-btn--large {
	padding: calc(var(--gg-space-base) * 2.25) calc(var(--gg-space-base) * 5);
	font-size: 16px;
	letter-spacing: 0.01em;
}

/* .gg-big-action — the arrangement AROUND that button (2026-08-21, Sam).
   The size was already shared; the centring, the spacing and the small line
   under it were hand-written on three separate screens with three inline
   <style> blocks. Component: gg_ui_big_action() in components.php. */
.gg-big-action {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 10px;
	padding: calc(var(--gg-space-base) * 3.25) 0 calc(var(--gg-space-base) * 4.25);
	border-top: 1px solid var(--gg-surface-border);
	text-align: center;
}
.gg-big-action--bare { border-top: 0; }
.gg-big-action .gg-btn--large { display: inline-flex; align-items: center; gap: 10px; }
.gg-big-action__note {
	margin: 0;
	max-width: 46ch;
	font-size: var(--gg-small-size, 13px);
	color: var(--gg-on-surface-muted);
}
.gg-big-action__status { font-size: 13px; color: var(--gg-on-surface-muted); }
.gg-big-action__status.is-ok { color: var(--gg-status-ok); }
.gg-big-action__status.is-err { color: var(--gg-status-danger); }

.gg-btn:hover { box-shadow: var(--gg-shadow-md); transform: translateY(-1px); color: #ffffff; }
.gg-btn--secondary { background: transparent; color: var(--gg-colour-primary); }
.gg-btn--secondary:hover { color: var(--gg-colour-primary); }
/* Ghost rides currentColor so it stays legible on dark AND light surfaces. */
.gg-btn--ghost {
	background: transparent;
	color: inherit;
	border-color: color-mix(in srgb, currentColor 35%, transparent);
	box-shadow: none;
}
.gg-btn--ghost:hover {
	background: color-mix(in srgb, currentColor 10%, transparent);
	color: inherit;
	box-shadow: none;
}
.gg-btn--small { padding: var(--gg-space-base) calc(var(--gg-space-base) * 2); font-size: var(--gg-small-size); }
.gg-btn--pill  { border-radius: var(--gg-radius-pill); }

/* .gg-btn--accent — the CALL-TO-ACTION button on dark chrome (slice 17,
   9 Aug 2026). An outlined gold button, not a filled one: on a dark panel a
   solid fill fights the surface, and every hand-rolled copy of this had
   independently arrived at the same outline treatment.
   Earned its place: gg-connections had drawn it three times over
   (inc/connections.php:962-970 .gg-cn-btn-connect, plus the guide page's
   .gg-gmg-btn and the Connect links rendered as buttons), each typing
   #f5d28a and its two alpha variants by hand. Donor for the exact values:
   .gg-cn-btn-connect, READ-ONLY.
   Small caps and letter-spacing come from the donor because on a dark panel
   this button is often the only interactive thing in view and reads as a
   label rather than a control without them. */
/* THE OUTLINED SET — one treatment, three meanings, expressed as a tone.
   Same shape as .gg-banner below: the rules live once and each variant sets
   --gg-btn-tone, so a fourth meaning is one line and can never drift in
   padding or weight from the other three.
   These are SMALL CAPS, unlike .gg-btn--ghost. That is not decoration: they
   appear in card footers and modal action rows where two or three sit side by
   side, and at body size they read as sentences rather than controls. Ghost is
   deliberately left alone — it is used 74 times across the platform at body
   size, and restyling it here to save a rule would restyle all of them. */
.gg-btn--accent,
.gg-btn--danger,
.gg-btn--quiet {
	/* currentColor is the fallback throughout, so a tone that is missing or
	   misspelt degrades to the surface's own ink rather than to nothing. */
	background: color-mix(in srgb, var(--gg-btn-tone, currentColor) 8%, transparent);
	color: var(--gg-btn-tone, currentColor);
	border-color: color-mix(in srgb, var(--gg-btn-tone, currentColor) 55%, transparent);
	box-shadow: none;
	font-size: var(--gg-small-size);
	font-weight: 700;
	letter-spacing: .06em;
	text-transform: uppercase;
}
.gg-btn--accent:hover,
.gg-btn--danger:hover,
.gg-btn--quiet:hover:not(:disabled) {
	background: color-mix(in srgb, var(--gg-btn-tone, currentColor) 16%, transparent);
	border-color: color-mix(in srgb, var(--gg-btn-tone, currentColor) 80%, transparent);
	color: var(--gg-btn-tone, currentColor);
	box-shadow: none;
	transform: none;
}
/* Gold — asking a coach to hand something over: connect, authorise, begin. */
.gg-btn--accent { --gg-btn-tone: var(--gg-accent-gold); }
/* Red — undoing something they will not get back: disconnect, delete, revoke.
   Reads --gg-status-danger, so it is the same red as the banner that reports
   the failure, and a coach restyling danger moves both together. */
.gg-btn--danger { --gg-btn-tone: var(--gg-status-danger); }
/* Neutral — the one you take if you take none of the others: cancel, dismiss,
   or a state the coach cannot act on. Tone is currentColor, so it borrows the
   surface's own ink and works on a card, a modal or a page without a variant.
   Distinct from --ghost, which is the body-size quiet button used everywhere
   else; this is the small-caps member of the row above. */
.gg-btn--quiet { --gg-btn-tone: currentColor; opacity: .72; }
.gg-btn--quiet:hover:not(:disabled) { opacity: 1; }
/* A button that reports rather than invites — no pointer, no hover. */
.gg-btn[aria-disabled="true"],
.gg-btn--static { cursor: default; }
.gg-btn--static:hover { background: color-mix(in srgb, var(--gg-btn-tone, currentColor) 8%, transparent); transform: none; }

/* .gg-banner — a status message at PROSE scale (slice 17, 9 Aug 2026).
   Not a toast and not a pill: it sits in the flow of a form or a panel and
   explains something in a sentence. Distinct from .gg-pill (a one-word state
   marker) and from the notification components, which are a feed.
   Earned its place: three copies in gg-connections alone —
   .gg-cn-modal-error (inc/connections.php:1092), .gg-gmg-error and
   .gg-gmg-saved (assets/gmail-setup-guide.css:105,151). The two error boxes
   were byte-identical to each other; the saved box is the same construction in
   green. What makes all three drift rather than three separate decisions is
   that each had hand-typed rgba values that --gg-status-danger and
   --gg-status-ok already held.
   Colour comes from the semantic status tokens, so a coach restyling status
   restyles these too, and there is no fourth palette to keep in step. */
.gg-banner {
	display: block;
	padding: calc(var(--gg-space-base) * 1.5) calc(var(--gg-space-base) * 2);
	font-size: var(--gg-small-size);
	line-height: 1.5;
	border: var(--gg-border-width) solid;
	border-radius: var(--gg-radius-md);
	background: color-mix(in srgb, var(--gg-banner-tone) 8%, transparent);
	border-color: color-mix(in srgb, var(--gg-banner-tone) 20%, transparent);
	color: var(--gg-banner-tone);
	--gg-banner-tone: var(--gg-status-info);
}
.gg-banner--ok     { --gg-banner-tone: var(--gg-status-ok); }
.gg-banner--warn   { --gg-banner-tone: var(--gg-status-warn); }
.gg-banner--danger { --gg-banner-tone: var(--gg-status-danger); }
.gg-banner--info   { --gg-banner-tone: var(--gg-status-info); }
/* Both ways of hiding one, because the two donors disagreed: the modal error
   used display:none toggled from script, the saved notice used [hidden]. */
.gg-banner[hidden] { display: none; }

/* .gg-spinner — the one busy indicator (slice 17, 9 Aug 2026).
   Takes its leading edge from currentColor rather than naming a colour, so
   the SAME component is gold inside a Connections panel and blue inside
   Pocket without a variant for each. That is the whole reason there is one
   spinner here and not three.
   Earned its place: two independent copies, both .7s linear infinite, both
   rotating a single border edge — .gg-cn-spinner (inc/connections.php:1106)
   and .gg-gmg-spinner (assets/gmail-setup-guide.css:141). Donor for the
   dimensions and timing: .gg-cn-spinner, READ-ONLY.
   Deliberately NOT sharp-cornered: a spinner is a circle, which is the
   documented opt-in the corner rule already carries for .gg-avatar. */
.gg-spinner {
	display: inline-block;
	width: 14px;
	height: 14px;
	border: 2px solid color-mix(in srgb, currentColor 22%, transparent);
	border-top-color: currentColor;
	border-radius: 50%;
	animation: gg-spin .7s linear infinite;
	vertical-align: middle;
	/* Both donors carried this: a spinner usually sits inside a flex button
	   next to a label, and without it the circle squashes to an oval. */
	flex-shrink: 0;
}
.gg-spinner--large { width: 26px; height: 26px; border-width: 3px; }
/* Any component that sets its own display must restore [hidden], because the
   browser's own [hidden] rule is weaker than a class. Both donors defaulted to
   display:none and let script reveal them, so this keeps that working. */
.gg-spinner[hidden] { display: none; }
@keyframes gg-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
	.gg-spinner { animation-duration: 2.4s; }
}

/* ── Back link / breadcrumb (gg_ui_back_link() — components.php) ──
 * WHY IT EXISTS: five plugins had each written their own back link, all
 * slightly different sizes and colours, so "go back" looked like a different
 * control on every screen. One component, one look.
 *
 * The single shared "← Back to X" navigation link. Replaces the per-plugin
 * bespoke copies (.gg-skill-back, .cmpback, .gg-ads-back, …) that were each
 * ~13px muted grey with their own inline CSS. Body-size text (noticeably
 * bigger than the old 13px), comfortable tap target, muted ink that
 * brightens to the accent on hover. Works on both <a> (helper) and <button>
 * (JS-built call sites) — resets the button chrome so both render identically.
 * This one class is the single source of truth: tune the size/colour HERE and
 * every back-link on the platform follows. */
.gg-back {
	display: inline-flex;
	align-items: center;
	gap: calc(var(--gg-space-base) * 0.75);
	margin: 0 0 calc(var(--gg-space-base) * 3) 0;
	padding: calc(var(--gg-space-base) * 0.5) 0;
	background: none;
	border: 0;
	font-family: var(--gg-font-body);
	font-size: var(--gg-body-size);
	font-weight: var(--gg-weight-body);
	line-height: 1.2;
	color: var(--gg-on-surface-muted, rgba(255,255,255,0.66));
	text-decoration: none;
	cursor: pointer;
	transition: color var(--gg-transition-speed, 150ms) var(--gg-transition-easing, ease);
}
.gg-back:hover,
.gg-back:focus-visible { color: var(--gg-colour-accent, #74C299); }

/* The back link's row, when a caller hands it quiet facts for the right-hand
   end (gg_ui_back_link's `aside_html`). The link keeps its own bottom margin so
   a bare back link and a row-with-facts sit the page identically; the row only
   has to stop that margin collapsing the two apart. */
.gg-back-row {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: calc(var(--gg-space-base) * 2);
	flex-wrap: wrap;
}
.gg-back-row .gg-back { margin-bottom: 0; }
.gg-back-row__aside {
	display: inline-flex;
	align-items: baseline;
	gap: calc(var(--gg-space-base) * 2);
	margin-left: auto;
	font-size: var(--gg-micro-size, 12px);
	line-height: 1.2;
	color: var(--gg-on-surface-muted, rgba(255,255,255,0.66));
	text-align: right;
}
/* A quiet icon action at the right-hand end of that row — Copy this page, move
   it to the bin. Shared (2026-08-13) because it was declared inline on the
   project page, so the second one to want it would have copied the rule and the
   two would have drifted. It says what it does on hover through `title`; these
   are corner actions, not the page's main controls, so they stay unlabelled
   until asked. --danger warns on hover only: a destructive action should be
   findable without shouting from a masthead. */
.gg-back-row__icon {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	background: none;
	border: 0;
	padding: 2px 4px;
	cursor: pointer;
	color: inherit;
	opacity: .7;
}
.gg-back-row__icon:hover,
.gg-back-row__icon:focus-visible { opacity: 1; }
.gg-back-row__icon--danger:hover,
.gg-back-row__icon--danger:focus-visible { color: var(--gg-status-danger, #ff8c8c); }
.gg-back-row__icon-form { display: inline; }
/* A CORNER ACTION THAT HAS TO ASK FIRST (2026-08-19).
 *
 * Most corner icons act on the press. A client's bin cannot: the one thing a
 * coach needs to know before removing somebody is that it is NOT a delete, and
 * that sentence has to be read before there is anything to confirm.
 *
 * So the icon opens its question directly beneath itself, anchored to the icon
 * rather than added to the row - a full-width panel inside a baseline-aligned
 * flex row would push the whole masthead about. It is the row's own width, not
 * a tooltip: the words matter more than the icon does at that moment.
 *
 * [hidden] IS SPELLED OUT because .gg-btn and this panel both declare a display
 * of their own, which beats the browser's own rule. Same trap the draft chip
 * fell into this morning. */
.gg-cm-remove { position: relative; display: inline-flex; }
.gg-cm-remove__ask {
	position: absolute;
	top: calc(100% + 8px);
	right: 0;
	z-index: 20;
	width: max-content;
	max-width: min(460px, 86vw);
	/* Border-box, so max-width means the width a coach can SEE. Without it the
	   padding and border sit outside the cap and a panel told to fit the screen
	   still hangs 22px off the left edge of a phone. */
	box-sizing: border-box;
	display: flex;
	flex-direction: column;
	gap: 10px;
	padding: 14px;
	text-align: left;
	white-space: normal;
	background: var(--gg-surface, #0f2536);
	border: var(--gg-border-width, 1px) solid var(--gg-status-danger, #ff8c8c);
}
.gg-cm-remove__ask .gg-btn { align-self: flex-start; }
.gg-cm-remove__ask[hidden],
.gg-cm-remove [data-gg-cm-remove-step][hidden] { display: none; }
/* IT STAYS PINNED TO ITS RIGHT EDGE ON A PHONE. Flipping it to the left of the
   icon sent it straight off the screen, because the icon it hangs off is itself
   at the right-hand edge of the row. Hanging leftwards from the same corner is
   the only direction with any room in it. */
@media (max-width: 620px) {
	.gg-cm-remove__ask { max-width: calc(100vw - 44px); padding: 12px; }
}

/* ── THE BASICS LIST (2026-08-13) ──────────────────────────────────────────
 * The shared field box, dense, for the handful of rows at the top of a project
 * page that say what the thing IS — name, description, picture, what it is
 * linked to.
 *
 * Why it needs its own treatment: every one of those controls was designed to
 * be met ALONE, revealed inside a toggle, with room to explain itself. Opened
 * all at once and stacked they came to 1,800 pixels for six facts — a 370px
 * empty picture well, and a choice list 965px tall because it draws one row per
 * campaign the coach owns and never stops. The controls were not wrong; asking
 * for all of them open at once was.
 *
 * So this is the same component with the explaining turned down: hints fold
 * away (the ? beside each label still carries them), the picture well is a
 * thumbnail, and a long choice list becomes a short scrolling one in columns.
 * Nothing is hidden that cannot be reached, and no control is replaced. */
/* FULL WIDTH (2026-08-20, Sam: "fields should be full width (fields at the
   top)").

   These rows were inset on 19 August to line up with the fields inside a
   question toggle, which start 40px in from the reading column. That was
   answering a page that had both. The toggles have since gone from the pages
   these rows actually appear on, so the inset lined up with nothing and left
   the top fields narrower than the rules, the section headings and the grid
   underneath them - two right edges on one page instead of two left ones.

   So they run the reading column's own width, like everything else on the
   page. If a page ever draws basics ABOVE question rows again, that page gives
   its list the inset back rather than every page carrying it.

   The selector names the element because .gg-entry-list, which this list also
   wears, zeroes the padding further down the sheet and would otherwise win on
   source order alone - so the zero has to be stated here to be reliable. */
ol.gg-basics-list,
.gg-project-page section.gg-project-actions {
	box-sizing: border-box;
	/* padding-left/right only, never the shorthand: the page's own
	   .gg-project-page .gg-ofp-section rule sets the top padding and the rule
	   below it in the sheet, so writing `padding` here would take that away. */
	padding-left: 0;
	padding-right: 0;
}
/* ── TWO SHORT ROWS, SIDE BY SIDE (2026-08-24, Sam: "linked offer and linked
   asset, those dropdown fields ... could both go next to each other, in a
   row") ──────────────────────────────────────────────────────────────────────

   The list is a two-column grid and EVERY row spans both of them, which is the
   list exactly as it was. Only a row that asks to be paired takes one column,
   and it takes it by wearing --half. So this is opt-in per row, from the PHP
   that builds the list, rather than a layout guessed from position: two halves
   that end up next to each other did so because both were declared, not because
   they happened to be adjacent.

   WHAT EARNS A HALF: a row whose control is a fixed-width thing a coach picks
   from - a dropdown, a date - and whose label is short. A box someone types
   into keeps the full width, because the line length is the point.

   AN ODD ONE OUT IS FINE. A single --half row sits in the left column at half
   width, which is honest: it is a small control, and stretching it across the
   page to fill the space is what made these rows look wrong to begin with.

   ROW GAP IS ZERO ON PURPOSE. The rows already space themselves (6px of margin
   on .gg-project-qrow--setting), so a gap here would add a second, competing
   rhythm on the rows that pair and not on the ones that do not. */
ol.gg-basics-list {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	column-gap: 22px;
	row-gap: 0;
	align-items: start;
}
ol.gg-basics-list > li { grid-column: span 2; min-width: 0; }
ol.gg-basics-list > li.gg-project-qrow--half { grid-column: span 1; }
/* ONE COLUMN ON A NARROW SCREEN. Two dropdowns beside each other on a phone are
   two dropdowns nobody can read the labels of. 782px because that is where the
   masthead above this list already changes shape - the top of the page turns
   once, not twice on the way down. */
@media (max-width: 782px) {
	ol.gg-basics-list { grid-template-columns: minmax(0, 1fr); column-gap: 0; }
	ol.gg-basics-list > li,
	ol.gg-basics-list > li.gg-project-qrow--half { grid-column: span 1; }
}
.gg-basics-list .gg-bv2-field-note { display: none; }
/* The label column widened to 230px here so a label and its "?" stayed on one
   line. There is no label column any more - the label sits above its field, on
   the row's full width - so only the tighter padding is left. (2026-08-19) */
.gg-basics-list .gg-project-qrow--setting .gg-bv2-field {
	padding: 8px 0;
}
/* ONE LINE, THAT GROWS (2026-08-13, Sam: "description should be one line, and
   grow if the text gets longer").

   The floor is the whole story: the base autogrow rule is min-height four lines
   (1.5em * 4 + 22px), so the box was born four lines tall no matter what was in
   it, and gg-field-box.js's scrollHeight sizing could only ever make it bigger.
   One line plus the same padding, and the autogrow takes it from there. The
   row count itself comes from min_rows on the field (gg-brand components). */
.gg-basics-list textarea.gg-bv2-field-autogrow {
	min-height: calc(1.5em + 22px);
	max-height: 8em;
}
/* THE PICTURE: A THUMBNAIL WITH ITS BUTTON BESIDE IT (2026-08-13, Sam: "Choose
   an image could go to right of 'background picture'. background picture could
   visibly look smaller on this page to save room").

   Stacked, at full size, one optional picture was the tallest thing in a list
   of six one-line facts. Side by side and smaller it is a thumbnail again,
   which is all it is: a preview of something shown elsewhere. */
/* THE PICTURE IS THE BUTTON (2026-08-13, Sam: "text of choose an image could be
   on the background image picture, doesn't need a separate button").
   He is right — a 112px thumbnail sitting next to a button that acts on it is
   two controls for one job, and the picture is the obvious target. The words
   move onto it and the separate button stops existing here. Markup untouched:
   the real control is still the same file input, laid over its own preview, so
   every other surface using this component is unaffected. */
/* SHALLOWER, AND THE WORDS SIT DEAD CENTRE (2026-08-20, Sam: "choose a featured
   image is too deep? and text not vertically centered on the box?").

   Both complaints were one cause and one near-miss.

   DEPTH: the tile was held to aspect-ratio 16/9, so at 148px wide it was forced
   to 83px deep - the tallest row in a list whose other rows are one line each.
   A fixed 64px is the depth the words themselves already occupied, so the tile
   loses the empty band underneath them and nothing else changes. The ratio is
   dropped rather than adjusted: a preview of a 1200x800 picture is 3:2, so the
   16/9 was not telling the truth about the shape either, and a cropped
   thumbnail is what every card on the platform already shows.

   CENTRING: the words were 11px high in the box, which reads as a mistake
   rather than a choice. The button was centred correctly; it just was not the
   only thing in the column. The base component's .gg-bv2-field-upload-actions
   carries gap:10px for its normal side-by-side layout, and this override turned
   the row into a column without resetting it - so an empty status line 12px
   tall, plus that 10px gap, ate 22px off the bottom of an 85px tile and pushed
   the button up. The gap goes to zero here and the status line comes out of the
   flow (see below), so the button is the column. */
.gg-basics-list .gg-bv2-field-upload {
	display: block;
	position: relative;
	width: 148px;
	cursor: pointer;
}
.gg-basics-list .gg-bv2-field-upload--wide .gg-bv2-field-upload-preview {
	width: 148px;
	max-width: 148px;
	aspect-ratio: auto;
	height: 64px;
	min-height: 0;
}
/* The empty state's own words would say it twice. */
.gg-basics-list .gg-bv2-field-upload-empty { display: none; }
/* The whole tile is the target, and it says what pressing it does. */
.gg-basics-list .gg-bv2-field-upload-actions {
	position: absolute;
	inset: 0;
	margin: 0;
	display: flex;
	flex-direction: column;
	/* Not the base component's 10px: in a column that gap is dead space under
	   the words, and it is half of why they read high. */
	gap: 0;
}
/* Out of the flow, so an empty status line cannot take depth off the button and
   a real one ("SAVED") cannot make the tile jump mid-upload. It sits along the
   bottom of the tile, over its own scrim, so it stays readable on a photo. */
.gg-basics-list .gg-bv2-field-upload-status {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	min-height: 0;
	padding: 3px 0;
	text-align: center;
	pointer-events: none;
}
.gg-basics-list .gg-bv2-field-upload-status:not(:empty) {
	background: rgba(10,22,40,0.72);
}
/* :not(.is-hidden) is load-bearing. Remove is hidden by that class until there
   IS a picture, and a blanket display rule here un-hid it — so an empty tile
   offered to remove the image it does not have. */
.gg-basics-list .gg-bv2-field-upload-actions .gg-row-btn:not(.is-hidden) {
	flex: 1 1 auto;
	border: 0;
	background: rgba(10,22,40,0.42);
	color: var(--ggb-text, rgba(255,255,255,0.92));
	font-size: 11px;
	/* Centred both ways: the words are ON the tile, not sitting at the top of
	   it like a caption that lost its picture. */
	display: flex;
	align-items: center;
	justify-content: center;
	text-align: center;
	transition: background .15s;
}
.gg-basics-list .gg-bv2-field-upload-actions .gg-row-btn:not(.is-hidden):hover,
.gg-basics-list .gg-bv2-field-upload-actions .gg-row-btn:not(.is-hidden):focus-visible {
	background: rgba(10,22,40,0.66);
}
/* Remove is the quieter of the two when both are showing over a picture. */
.gg-basics-list .gg-bv2-field-upload-remove:not(.is-hidden) {
	flex: 0 0 auto;
	min-height: 26px;
	font-size: 10px;
	opacity: .8;
}

/* A CHOSEN PICTURE IS SHOWN, NOT COVERED (2026-08-20, Sam: "when you add an
   image, it gets cluttered with text ON the image").

   The words going onto the tile was his own call on 13 August, and it still
   holds for an EMPTY tile: there is nothing underneath to hide, and a bare
   square with no instruction on it says nothing. The moment there is a picture
   that reverses. Replace image, Remove and their scrim covered the whole photo,
   so the one thing the row exists to show was the one thing a coach could not
   see.

   So the words stay while the tile is empty and step aside once it is filled.
   Nothing is removed and nothing moves: the same controls fade back in on
   hover, and on keyboard focus, which is why this is focus-within rather than a
   hover rule on its own. Tabbing to Replace or Remove brings them up exactly as
   pointing at the tile does.

   Filled is asked as "the preview holds an img", which is the same fact the
   component itself works from - gg-field-box.js puts a real <img> in there on
   upload and swaps it back for the empty span on remove, so this follows a
   picture being added or cleared with no reload and no new class to keep in
   step. */
.gg-basics-list .gg-bv2-field-upload:has(.gg-bv2-field-upload-preview img) .gg-bv2-field-upload-actions {
	opacity: 0;
	transition: opacity .15s ease;
}
.gg-basics-list .gg-bv2-field-upload:has(.gg-bv2-field-upload-preview img):hover .gg-bv2-field-upload-actions,
.gg-basics-list .gg-bv2-field-upload:has(.gg-bv2-field-upload-preview img):focus-within .gg-bv2-field-upload-actions {
	opacity: 1;
}
/* A touch screen has no hover to give, and controls that only exist on hover
   are controls a phone cannot reach. There they stay on, which is the honest
   trade: a coach on a phone can still replace or remove the picture. */
@media (hover: none) {
	.gg-basics-list .gg-bv2-field-upload:has(.gg-bv2-field-upload-preview img) .gg-bv2-field-upload-actions {
		opacity: 1;
	}
}
.gg-basics-list .gg-bv2-field-upload-hint { display: none; }

/* ── Prose inside a project page's read row (2026-08-13) ───────────────────
 * An orientation the coach opened on purpose. Not a card blurb, so it does not
 * wear a card's two-line clamp — see gg_project_question_row(). */
/* FULL WIDTH INSIDE THE TOGGLE (2026-08-13, Sam: "start here copy is not full
 * width inside the toggle - fix that"). It had a 68ch measure, which is a good
 * rule for a page of prose and wrong here: the box is already a narrow column
 * inside a disclosure, so capping it again left a ragged empty third down the
 * right of every explanation. */
.gg-project-prose { max-width: none; }
.gg-project-prose__p {
	margin: 0 0 calc(var(--gg-space-base) * 2);
	font-size: 13px;
	line-height: 1.65;
	color: var(--gg-on-surface-muted, #9fb0c5);
	white-space: pre-wrap;
}
.gg-project-prose__p:last-child { margin-bottom: 0; }
/* A choice list is capped and scrolls, in as many columns as the row is wide.
   A coach with forty campaigns gets a short box to scroll, not forty rows of
   page. GRID rather than CSS columns: the base rule is a flex column, and
   `columns` does nothing to a flex container — which is exactly the silent
   no-op this replaced. The cap clears four rows, so the common case never
   scrolls at all. */
/* ONE COLUMN, NEVER THREE (2026-08-19, Sam: "Dropdown fields should only ever
   be 1 column, never 3").
   It filled the row with as many columns as fitted, which read as a block of
   options to scan rather than a list to go down. The cap and the scroll stay:
   they are what stops forty campaigns becoming forty rows of page. */
.gg-basics-list .gg-bv2-field-checks {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 7px;
	max-height: 152px;
	overflow-y: auto;
}

/* ── Multiselect: the tick list with a lid (2026-08-13) ────────────────────
 * Closed it reads like a select and names what is picked; open it is the
 * ordinary check list. Shaped to match .gg-bv2-field-select exactly, because a
 * coach should not have to learn that this dropdown is a different animal. */
.gg-bv2-field-multi { width: 100%; }
.gg-bv2-field-multi__summary {
	display: flex;
	align-items: center;
	gap: calc(var(--gg-space-base) * 1.5);
	padding: 10px 12px;
	border: 1px solid var(--ggb-border, rgba(255,255,255,0.12));
	background: rgba(255,255,255,0.04);
	cursor: pointer;
	list-style: none;
	font-size: 13.5px;
	color: var(--gg-on-surface, #e7eef7);
}
.gg-bv2-field-multi__summary::-webkit-details-marker { display: none; }
.gg-bv2-field-multi__summary:hover { border-color: rgba(255,255,255,0.22); }
/* The names, one line, clipped — a project linked to six campaigns must not
   push the row to three lines just sitting there closed. */
.gg-bv2-field-multi__said {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.gg-bv2-field-multi__count:not(:empty) {
	flex: 0 0 auto;
	padding: 1px 7px;
	font-size: 11px;
	font-weight: 600;
	border: 1px solid currentColor;
	color: var(--gg-colour-accent, #74C299);
}
.gg-bv2-field-multi__chev {
	flex: 0 0 auto;
	transform: rotate(90deg);
	transition: transform .15s ease;
	opacity: .7;
}
.gg-bv2-field-multi[open] .gg-bv2-field-multi__chev { transform: rotate(-90deg); }
/* Open: the list hangs directly off the summary as one control, no gap. */
.gg-bv2-field-multi[open] .gg-bv2-field-checks { border-top: 0; }

/* ── Status pills (donor: gg-connections.php) ──
 * Background + border derive from currentColor, so each state only sets
 * `color`. Sharp corners — these are chips, not pills-the-shape. */
.gg-pill {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	font-family: var(--gg-font-body);
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: .08em;
	text-transform: uppercase;
	line-height: 1.4;
	padding: 3px 9px;
	color: var(--gg-on-surface-muted);
	background: color-mix(in srgb, currentColor 12%, transparent);
	border: var(--gg-border-width) solid color-mix(in srgb, currentColor 32%, transparent);
	border-radius: 0;
}
.gg-pill--ok     { color: var(--gg-status-ok); }
.gg-pill--warn   { color: var(--gg-status-warn); }
.gg-pill--danger { color: var(--gg-status-danger); }

/* ── Count badge (donor: gg-roadmap-v2's .gg-rv2-card-num, 2026-08-09) ──
 * An x-of-y roll-up worn by a card. Built for the project board's goals
 * count, but nothing in it knows about goals, so any card with a
 * completed-out-of-total number reuses it. Sharp corners and the same
 * 800 weight as the position badge it sits opposite, so a card wearing
 * both reads as a pair rather than two unrelated marks. Tone follows the
 * card's own colour by default and flips to the ok status colour when the
 * count is complete, which is the one state worth spotting across a grid.
 * Positioning is deliberately NOT set here - where it sits is the host
 * card's business, the look is ours. */
.gg-count-badge {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	box-sizing: border-box;
	font-family: var(--gg-font-body);
	font-size: 10px;
	font-weight: 700;
	line-height: 1;
	font-variant-numeric: tabular-nums;
	letter-spacing: -.01em;
	color: #fff;
	background: rgba(10, 22, 40, 0.62);
	/* 2px, not 1px: a hairline ring on a circle aliases into a broken,
	   sketchy edge on a non-retina screen (Sam, 9 Aug). Higher contrast for
	   the same reason - a washed-out ring is what makes the stepping show. */
	border: 2px solid rgba(255, 255, 255, 0.62);
	border-radius: 50%;
	-webkit-backdrop-filter: blur(4px);
	backdrop-filter: blur(4px);
	text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
}
.gg-count-badge--complete {
	color: var(--gg-status-ok);
	border-color: color-mix(in srgb, var(--gg-status-ok) 85%, transparent);
	background: color-mix(in srgb, var(--gg-status-ok) 16%, rgba(10, 22, 40, 0.7));
}
.gg-pill--info   { color: var(--gg-status-info); }
.gg-pill--muted  { color: var(--gg-on-surface-muted); }
.gg-pill--dot::before {
	content: '';
	width: 6px;
	height: 6px;
	background: currentColor;
	display: inline-block;
	flex-shrink: 0;
}

/* ── Progress badge  .gg-progress-badge  ────────────────────────────────
 * WHY IT EXISTS: a setup page has to answer "how far through am I" at a
 * glance, and a plain sentence does not. It is not a .gg-pill because it
 * carries a meter as well as a count.
 *
 * A small "N of M completed" chip shown at the top of a setup page. It is
 * the visible face of the platform's "the AI chases whatever isn't done"
 * idea: a count + a thin meter that fills as fields get filled. Shares the
 * .gg-pill visual family (sharp corners, tokened status colour, uppercase
 * micro-label) so it sits naturally beside the existing chips. Render it
 * with gg_progress_badge($done, $total) in gg-brand/inc/components.php —
 * one call, every page inherits the look. Colour is muted while there's
 * work left and flips to --gg-status-ok on completion; both derive their
 * fill/border from currentColor exactly like .gg-pill. */
.gg-progress-badge {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	font-family: var(--gg-font-body);
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: .08em;
	text-transform: uppercase;
	line-height: 1.4;
	padding: 4px 10px;
	color: var(--gg-on-surface-muted);
	background: color-mix(in srgb, currentColor 12%, transparent);
	border: var(--gg-border-width) solid color-mix(in srgb, currentColor 32%, transparent);
	border-radius: 0;
}
.gg-progress-badge--complete { color: var(--gg-status-ok); }
/* Thin segmented meter — sharp-cornered track + fill, both from currentColor
 * so the whole chip recolours as one when it completes. */
.gg-progress-badge__track {
	position: relative;
	width: 46px;
	height: 5px;
	flex-shrink: 0;
	background: color-mix(in srgb, currentColor 22%, transparent);
	border-radius: 0;
	overflow: hidden;
}
.gg-progress-badge__fill {
	position: absolute;
	inset: 0 auto 0 0;
	height: 100%;
	background: currentColor;
	border-radius: 0;
}
.gg-progress-badge__label { white-space: nowrap; }

/* ── Stat row  .gg-stat-row / .gg-stat  ─────────────────────────────────
 * WHY IT EXISTS: "how many are there" is the second thing a coach reads on
 * a screen, after its name, and it had been drawn twice from scratch:
 * Page Review's waiting/on-hold/approved counts (gg-page-review
 * inc/queue-page.php), and the Finance co-pilot's three totals
 * (gg-finance/inc/copilot.php), which were not a class at all — the sizes
 * were typed into style="" attributes on the elements themselves. Both now
 * render from here.
 *
 * Added rather than stretched: nothing existing fits. .gg-progress-badge is
 * one meter for one number, .gg-pill is a status chip, and .gg-table's
 * counts live inside a table. This is the standalone "big number, small
 * label" tile, in a row.
 *
 * The number is a FIXED 28px, not a --gg-hN-size token, for the same reason
 * .gg-heading-2 below is: those tokens are the COACH's typography for the
 * pages they publish, and this is platform chrome. A coach picking a big
 * display face for their own site must not resize the platform's counters.
 * The label takes the pill's uppercase treatment so the two families look
 * related rather than merely adjacent. Sharp corners, and no colour of its
 * own beyond the surface tokens — it inherits whatever it is placed on, so it
 * works on a card and on a page backdrop without needing a variant. */
.gg-stat-row {
	display: flex;
	flex-wrap: wrap;
	gap: calc(var(--gg-space-base) * 3);
	align-items: center;
}
.gg-stat {
	text-align: center;
}
.gg-stat__n {
	display: block;
	font-family: var(--gg-font-heading);
	font-size: 28px;
	font-weight: 700;
	line-height: 1;
	color: var(--gg-on-surface);
}
.gg-stat__l {
	display: block;
	font-family: var(--gg-font-body);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: .08em;
	text-transform: uppercase;
	color: var(--gg-on-surface-muted);
}
/* The label reads either side of the number and the spacing follows the
   markup, so neither caller has to add a margin of its own: Page Review puts
   the count first and names it underneath, the Finance co-pilot names the
   measure first and gives the figure under it. */
.gg-stat__n + .gg-stat__l { margin-top: 4px; }
.gg-stat__l + .gg-stat__n { margin-top: 2px; }
/* An inline qualifier on the figure — "of £5,000", "active". Same line, quieter. */
.gg-stat__unit {
	font-size: 13px;
	font-weight: 400;
	color: var(--gg-on-surface-muted);
}
/* A footnote under the figure: the percentage, the breakdown, the caveat. */
.gg-stat__sub {
	display: block;
	margin-top: 4px;
	font-size: 12px;
	color: var(--gg-on-surface-muted);
}
/* Two row shapes, because the two callers genuinely differ: a centred huddle
   under a page header (Page Review), or an even spread across a card, which
   wants left-aligned figures so the labels line up down the left edge. */
.gg-stat-row--grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
	align-items: start;
}
.gg-stat-row--grid .gg-stat { text-align: left; }

/* ── Setup shell  .gg-setup-shell  ──────────────────────────────────────
 * WHY IT EXISTS: four setup pages (Business Setup, Train your AI, Offer,
 * Brand) ask a coach the same shape of question, so they should look and
 * behave identically and differ only in the questions themselves.
 *
 * The shared "fill in your answers" setup-page skin. Any page whose backdrop
 * wrapper carries `.gg-setup-shell` (via gg_setup_shell_open() in
 * gg-brand/inc/setup-shell.php) inherits the exact Business-Setup look: a
 * grouped dark section card, a vertical progress rail of status circles, and
 * dark-skinned editable field boxes. Everything here is scoped to
 * `.gg-setup-shell` so it can never touch a page that hasn't opted in.
 *
 * The row STRUCTURE (.gg-checkrow*) is the shared component gg-my-business's
 * admin.css always said "normally ships from gg-brand" — this is that source.
 * The skin below is the Business-Setup dark treatment, kept verbatim so a
 * shell page renders pixel-identically to it. Sharp corners throughout. */

/* Structural rail + rows (themed via the --gg-* custom props the card sets). */
/* The status circle's size, named once (2026-08-04). It was 26px typed into
   three rules — the rail column's width, the node's width and its height — and
   the track rail needs a fourth (how far down a row the node's centre sits).
   Four copies of one number is four chances for the line to miss the circle,
   which is exactly the bug this was. */
.gg-setup-shell { --gg-node-size: 26px; }
.gg-setup-shell .gg-checklist { display: flex; flex-direction: column; }
.gg-setup-shell .gg-checkrow {
	position: relative;
	display: flex;
	align-items: stretch;
	gap: 16px;
	padding: 14px 18px;
	border-bottom: 1px solid var(--gg-checkrow-divider, rgba(128,128,128,0.18));
}
.gg-setup-shell .gg-checkrow:last-child { border-bottom: 0; }
.gg-setup-shell .gg-checkrow-rail {
	position: relative;
	flex: 0 0 var(--gg-node-size);
	display: flex;
	flex-direction: column;
	align-items: center;
	margin: -14px 0;
}
.gg-setup-shell .gg-checkrow-rail::before {
	content: "";
	position: absolute;
	top: 0;
	bottom: 0;
	left: 50%;
	width: 2px;
	transform: translateX(-50%);
	background: var(--gg-rail, rgba(255,255,255,0.13));
}
.gg-setup-shell .gg-checkrow--rail-first .gg-checkrow-rail::before { top: 27px; }
.gg-setup-shell .gg-checkrow--rail-last .gg-checkrow-rail::before { bottom: calc(100% - 27px); }
.gg-setup-shell .gg-checkrow-node {
	position: relative;
	z-index: 1;
	flex: 0 0 auto;
	margin-top: 14px;
	width: var(--gg-node-size);
	height: var(--gg-node-size);
	border-radius: 50%;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	background: var(--gg-node-fill, #101f33);
	border: 2px solid var(--gg-node-todo, rgba(255,255,255,0.32));
	box-shadow: none;
	transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.gg-setup-shell .gg-checkrow-node-tick {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: #ffffff;
}
.gg-setup-shell .gg-checkrow-node-tick svg { width: 15px; height: 15px; display: block; }
.gg-setup-shell .gg-checkrow--done .gg-checkrow-node {
	background: var(--gg-node-done, #37a86a);
	border-color: var(--gg-node-done, #37a86a);
	box-shadow: 0 0 0 4px var(--gg-node-done-halo, rgba(55,168,106,0.16));
}
/* THE ONE YOU'RE ON (2026-08-04, Sam: "the one you're on should glow, as is
   brand styles"). Until now the rail had exactly one state — done — so a coach
   reading down it could see what they had finished and nothing about where to
   start.

   Same pattern as the done halo directly above, one accent along: a ring in
   --gg-status-info, the accent this stylesheet already uses everywhere for
   "interactive / active" (.gg-input:focus, .gg-disclosure[open], and the "Next"
   pill sitting on this very row), plus the 4px halo. It reads as lit rather
   than filled, which is the point: done is a completed thing, current is a live
   one.

   IT IS A MARKER, NOT A GATE. Nothing here disables, hides, collapses or dims
   any other row — see gg_project_mark_next() in gg-roadmap-v2. A coach may
   answer any question in any order and the glow simply moves. */
.gg-setup-shell .gg-checkrow--current .gg-checkrow-node {
	border-color: var(--gg-node-current, var(--gg-status-info, #93c5fd));
	box-shadow: 0 0 0 4px var(--gg-node-current-halo, color-mix(in srgb, var(--gg-status-info, #93c5fd) 22%, transparent));
}
/* ── THE TRACK RAIL ON A QUESTION LIST — .gg-disclosure-list--rail ───────
   (2026-08-04, Sam: "the circles should be outside of the toggles, to the left
   of the toggles. the one you're on should glow, as is brand styles. the line
   should be through the exact center of each circle.")

   WHAT THIS REPLACES, and why it could never have worked. The node was rendered
   INSIDE each <summary> as the disclosure's leading icon, so there was no
   element on the left of the row for the line to belong to. The line was
   therefore painted as a stripe on the <li> at `left: 0` plus a hardcoded
   `margin-left: 12px` — a guess at where the circle had ended up. Measured, it
   missed by 18px: circles at x=325, line at x=307, reading on screen as stray
   stubs at the far left with the circles floating away from them. Nudging that
   12px would have been a second guess.

   The fix is structural, and it is the shape .gg-checkrow-rail above has always
   had: the node lives in its OWN column beside the toggle, and the line is that
   column's ::before at left:50%. The line passes through the circle BY
   CONSTRUCTION — there is no offset to get wrong, at any width, in any column.

   THREE NUMBERS, each stated once. --gg-node-size is the circle (shared with
   the checkrow above). --gg-rail-row-h is the height of a collapsed row, and it
   is the summary's min-height. --gg-rail-node-centre is derived from both: how
   far below the row's top edge the circle's centre lands, counting the toggle's
   own border, because the rail column starts at the <li> edge and the summary
   starts one border inside it.

   That last one is used by all THREE things that have to agree — where the node
   sits, where the line starts at the top of the list, and where it stops at the
   bottom. Three copies of one offset is how the line came to miss the circle in
   the first place. */
.gg-setup-shell .gg-disclosure-list--rail {
	--gg-rail-row-h: 52px;
	--gg-rail-gap: calc(var(--gg-space-base) * 1);
	--gg-rail-node-centre: calc(var(--gg-rail-row-h) / 2 + var(--gg-border-width));
	gap: var(--gg-rail-gap);
}
.gg-setup-shell .gg-disclosure-list--rail > li {
	position: relative;
	display: flex;
	align-items: stretch;
	gap: calc(var(--gg-space-base) * 1.75);
	list-style: none;
}
/* The toggle takes what is left; min-width:0 so a long value can ellipsis
   rather than push the rail off the edge. */
.gg-setup-shell .gg-disclosure-list--rail > li > .gg-disclosure { flex: 1 1 auto; min-width: 0; }
.gg-setup-shell .gg-disclosure-list--rail > li > .gg-checkrow-rail {
	position: relative;
	flex: 0 0 var(--gg-node-size);
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: flex-start;
	margin: 0;
}
/* The connector. Centre of the column = centre of the node, and it runs on
   through the list's own gap so the rail reads as one continuous line rather
   than a dashed one. */
.gg-setup-shell .gg-disclosure-list--rail > li > .gg-checkrow-rail::before {
	content: "";
	position: absolute;
	left: 50%;
	top: 0;
	bottom: calc(var(--gg-rail-gap) * -1);
	width: 2px;
	transform: translateX(-50%);
	background: var(--gg-rail, rgba(255,255,255,0.13));
	pointer-events: none;
}
/* EVERY END OF THE LINE IS A CIRCLE. The track starts at the first circle of a
   run of rows and stops at the last one, so it can never dangle into space.

   WHY A "RUN" AND NOT JUST THE WHOLE LIST. A question list is not only rows: a
   group's heading ("OFFER CLARITY", "WRITE YOUR SCRIPT", and the bare row
   carrying "Expand all") sits between them as a sibling. Trimming only at the
   two ends of the whole list left a stub hanging below the last circle before
   each heading and another starting in mid-air after it — the same stray-stub
   look this change exists to remove, one axis along. So each run of rows is its
   own complete track, which is also what the headings are saying: these
   questions, then those.

   Stated generically — "a row with no row next to it" — so gg-brand needs to
   know nothing about which markup a feature puts between its rows. */
.gg-setup-shell .gg-disclosure-list--rail > li:not(li + li) > .gg-checkrow-rail::before {
	top: var(--gg-rail-node-centre);
}
.gg-setup-shell .gg-disclosure-list--rail > li:not(:has(+ li)) > .gg-checkrow-rail::before {
	bottom: calc(100% - var(--gg-rail-node-centre));
}
/* The node sits ON the line, so it paints over it — and it sits against the
   SUMMARY, not the middle of an expanded row, so opening a question does not
   drag its circle down the page away from its own title. */
.gg-setup-shell .gg-disclosure-list--rail > li > .gg-checkrow-rail > .gg-checkrow-node {
	position: relative;
	z-index: 1;
	/* Pushed down until its CENTRE is on --gg-rail-node-centre — the same offset
	   the line's two ends are trimmed to, so all three are one number. */
	margin-top: calc(var(--gg-rail-node-centre) - var(--gg-node-size) / 2);
}
/* The row height the rail is built on, stated where it is true — and stated in
   BORDER-box, so "52px" is the height of the row a coach sees rather than 52px
   plus whatever the padding happens to be. wp-admin does not agree with itself
   about box-sizing, so the block says which it means. */
.gg-setup-shell .gg-disclosure-list--rail .gg-disclosure__summary {
	box-sizing: border-box;
	min-height: var(--gg-rail-row-h);
}

.gg-setup-shell .gg-checkrow-body {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 3px;
}
.gg-setup-shell .gg-checkrow-label { font-size: 13px; font-weight: 600; line-height: 1.35; }
.gg-setup-shell .gg-checkrow-sub { font-size: 12px; line-height: 1.4; opacity: 0.72; }
.gg-setup-shell .gg-checkrow-action {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	gap: 8px;
}
.gg-setup-shell .gg-checkrow--done .gg-checkrow-label { opacity: 0.62; font-weight: 500; }
.gg-setup-shell .gg-checkrow--done .gg-checkrow-sub { opacity: 0.45; }
.gg-setup-shell .gg-checkrow-body--field .gg-bv2-field { width: 100%; }
@media (max-width: 600px) {
	.gg-setup-shell .gg-checkrow { flex-wrap: wrap; }
	.gg-setup-shell .gg-checkrow-action { margin-left: 42px; }
}
/* Field rows centre the status circle against the whole (taller) row. */
.gg-setup-shell .gg-checkrow--field .gg-checkrow-rail { justify-content: center; }
.gg-setup-shell .gg-checkrow--field .gg-checkrow-node { margin-top: 0; }
.gg-setup-shell .gg-checkrow--field .gg-checkrow-body { justify-content: center; }
.gg-setup-shell .gg-checkrow--field.gg-checkrow--rail-first .gg-checkrow-rail::before { top: 50%; }
.gg-setup-shell .gg-checkrow--field.gg-checkrow--rail-last .gg-checkrow-rail::before { bottom: 50%; }

/* Section card: one cohesive grouped card, opaque navy, continuous dividers. */
.gg-setup-shell .gg-mb-section-card {
	max-width: 820px;
	margin: 0 auto 22px;
	border: 1px solid rgba(255,255,255,0.12);
	background: #0a1628;
	color: #fff;
	--gg-checkrow-divider: rgba(255,255,255,0.08);
	--gg-checkrow-icon: rgba(255,255,255,0.55);
	--gg-checkrow-done: #a3eecb;
	--gg-rail: rgba(255,255,255,0.13);
	--gg-node-fill: #101f33;
	--gg-node-todo: rgba(255,255,255,0.32);
	--gg-node-done: #37a86a;
	--gg-node-done-halo: rgba(55,168,106,0.16);
	/* "The one you're on" — see .gg-checkrow--current above. Declared beside the
	   done pair so the two states of one component are themed from one block
	   rather than one being a token and the other a hardcoded colour. */
	--gg-node-current: var(--gg-status-info, #93c5fd);
	--gg-node-current-halo: color-mix(in srgb, var(--gg-status-info, #93c5fd) 22%, transparent);
}
.gg-setup-shell .gg-mb-section-card .gg-mb-checklist {
	max-width: none;
	margin: 0;
	border: 0;
	background: transparent;
}
.gg-setup-shell .gg-mb-section-card .gg-checkrow {
	border-top: 0;
	border-bottom: 1px solid var(--gg-checkrow-divider, rgba(255,255,255,0.08));
}
.gg-setup-shell .gg-mb-section-card > *:last-child .gg-checkrow:last-child { border-bottom: 0; }

/* Section headings. */
.gg-setup-shell .gg-mb-section-title {
	max-width: 820px;
	margin: 64px auto 4px;
	font-size: 15px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: #fff;
}
.gg-setup-shell .gg-mb-section-sub {
	max-width: 820px;
	margin: 0 auto 16px;
	font-size: 13px;
	color: rgba(255,255,255,0.6);
}

/* Dark checklist skin + editable field boxes. */
.gg-setup-shell .gg-mb-checklist {
	max-width: 820px;
	margin: 0 auto 22px;
	border: 1px solid rgba(255,255,255,0.12);
	background: #0a1628;
	color: #fff;
	--gg-checkrow-divider: rgba(255,255,255,0.08);
	--gg-checkrow-icon: rgba(255,255,255,0.55);
	--gg-checkrow-done: #a3eecb;
}
.gg-setup-shell .gg-mb-checklist .gg-checkrow-label { color: #fff; }
.gg-setup-shell .gg-mb-checklist .gg-checkrow-sub { color: #fff; }
.gg-setup-shell .gg-mb-checklist .gg-checkrow-body--field .gg-bv2-field-label-row { margin-bottom: 6px; }
.gg-setup-shell .gg-mb-checklist .gg-checkrow-body--field .gg-bv2-field-label { color: rgba(255,255,255,0.82); }
.gg-setup-shell .gg-checkrow--done .gg-bv2-field-label { opacity: 0.55; }
/* The field's middle line — why this row matters, shown the moment it opens.
   Sits between the label row and the input, quieter than the label, plainer
   than body copy. Added 2026-08-03 with stage_field_points. */
/* Takes only the width of its own sentence, so the "?" and Discuss sit
   immediately after the description rather than floating out at the row's
   right edge. The descriptions are authored to a consistent length, which is
   what keeps those glyphs roughly aligned down the page. */
.gg-bv2-field-note { flex: 0 1 auto; min-width: 0; margin: 0; font-size: 13px;
  line-height: 1.45; color: var(--ggb-text-soft, rgba(255,255,255,0.66)); }
/* A described field puts its sentence first and its glyphs right, so the "?"
   and Discuss sit beside the description rather than floating above it. */
.gg-bv2-field-label-row--noted { align-items: flex-start; }
.gg-bv2-field-label-row--noted > .gg-bv2-field-status { margin-left: auto; }
.gg-bv2-field-label-row--noted > .gg-bv2-field-label { order: 99; }
/* ON A PHONE THE ROW WRAPS (2026-08-23).
 *
 * The row above is one unwrapped flex line: the description shrinks (flex 0 1
 * auto, min-width 0) and the "?" and the two buttons do not (flex 0 0 auto).
 * On a 390px screen that squeezed the description to about forty pixels - one
 * word per line, a dozen lines tall - and still pushed the buttons 285px off
 * the right of the screen, where they could not be reached. The document
 * reported no overflow, because the overflow was inside the column.
 *
 * So below the phone breakpoint it wraps: the description takes the first line
 * on its own, and the "?" with the two buttons sit under it. Nothing changes
 * above the breakpoint. */
@media (max-width: 600px) {
	.gg-bv2-field-label-row { flex-wrap: wrap; }
	.gg-bv2-field-label-row--noted > .gg-bv2-field-note { flex: 1 0 100%; }
	.gg-bv2-field-label-row--noted > .gg-bv2-field-status { margin-left: 0; }
}
.gg-setup-shell .gg-mb-checklist textarea.gg-bv2-field-input,
.gg-setup-shell .gg-mb-checklist input.gg-bv2-field-input,
.gg-setup-shell .gg-mb-checklist .gg-bv2-field-input {
	width: 100%;
	box-sizing: border-box;
	background-color: rgba(255,255,255,0.05);
	color: #f4f4f4;
	border: 1px solid rgba(255,255,255,0.16);
	border-radius: 0;
}
.gg-setup-shell .gg-mb-checklist .gg-bv2-field-input::placeholder { color: rgba(255,255,255,0.35); }
.gg-setup-shell .gg-mb-checklist .gg-bv2-field-input:hover { border-color: rgba(255,255,255,0.26); }
.gg-setup-shell .gg-mb-checklist .gg-bv2-field-input:focus {
	outline: none;
	border-color: rgba(232,180,79,0.55);
	background-color: rgba(255,255,255,0.08);
	box-shadow: 0 0 0 2px rgba(232,180,79,0.18);
}

/* ── Content card (donor: gg-rv2-card incl. its sharp-corner enforcement) ── */
.gg-card {
	position: relative;
	display: flex;
	flex-direction: column;
	background: var(--gg-surface-card);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	color: var(--gg-on-surface);
	box-shadow: var(--gg-shadow-sm);
	overflow: hidden;
}
/* Sharp-corner enforcement: WP admin / theme CSS injects radii on children
 * (.postbox, img, .notice). Non-!important on descendants so an avatar can
 * still opt back into 50%. */
.gg-card,
.gg-card::before,
.gg-card::after { border-radius: 0 !important; }
.gg-card > *,
.gg-card img    { border-radius: 0; }
/* TRASH ON A CARD (16 Aug 2026, Sam: "every grid card gets a trash icon, top
 * right of the card"). Values lifted from .gg-rv2-card-trash in
 * gg-roadmap-v2/assets/admin-roadmap-v2.css rather than re-picked, so the card
 * that already had a bin and the shared card that just got one look identical.
 *
 * .gg-card__hit is the transparent full-bleed link a trashable card carries
 * instead of being an <a> itself. It sits UNDER the trash (z-index 1 vs 6) so
 * the bin is clickable, and above the card's own content so the whole tile
 * still opens the thing. */
.gg-card--trashable .gg-card__hit,
.gg-card--tagpickable .gg-card__hit {
	position: absolute;
	inset: 0;
	z-index: 1;
	display: block;
	text-indent: -9999px;
	overflow: hidden;
}
/* WHERE THE KEYBOARD IS (21 Aug 2026 UX audit: "Keyboard users cannot see where
 * they are").
 *
 * The card's own focus style sits on .gg-card--img:focus-visible, and a card
 * carrying any top-right control is a <div> - so the thing that actually takes
 * focus is this transparent link inside it, and the div's rule never fires. A
 * reviewer photographed the same card focused and unfocused and the two images
 * came back byte for byte identical. On the Client Manager that was forty
 * invisible stops, each of them two stops from the bin.
 *
 * Drawn INSET rather than offset: .gg-card clips to its own box, so a ring
 * outside the edge is cut away and the fix would look like no fix at all. The
 * colour is the one the bin and the tag control already focus in. */
.gg-card--trashable .gg-card__hit:focus-visible,
.gg-card--tagpickable .gg-card__hit:focus-visible {
	outline: 2px solid #E8B44F;
	outline-offset: -3px;
}
.gg-card__trash {
	position: absolute;
	right: 8px;
	top: 8px;
	z-index: 6;
	margin: 0;
	line-height: 0;
}
.gg-card__trash button {
	display: grid;
	place-items: center;
	width: 30px;
	height: 30px;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: rgba(0, 0, 0, 0.42);
	color: rgba(255, 255, 255, 0.62);
	cursor: pointer;
	transition: background 0.14s ease, color 0.14s ease;
}
.gg-card__trash button:hover { background: rgba(176, 42, 42, 0.92); color: #fff; }
.gg-card__trash button:focus-visible { outline: 2px solid #E8B44F; outline-offset: 1px; }
.gg-card__trash svg { display: block; }

/* THE ROADMAP CONTROL ON A CARD (21 Aug 2026, Sam: "I would also put a 'add to
 * my roadmap' option on each thing's own card"). Same 30px plate as the trash
 * and the tag control above, because it is the same control wearing a third
 * icon - a coach learns one shape in the corner of a card, not three.
 *
 * It sits LEFT of whatever else is up there, on the same rule the tag control
 * follows: controls line up along the top edge rather than stacking, because
 * stacking in one corner is how a mis-click bins something. Its gold-on-press
 * is the brand's own accent, and the ON state stops being a button at all -
 * a pressed-looking control that refuses is worse than one that is not there. */
.gg-card__roadmap {
	position: absolute;
	right: 8px;
	top: 8px;
	z-index: 7;
	margin: 0;
	line-height: 0;
}
/* THE GAP IS 14px, NOT 4px (21 Aug 2026 UX audit). Three of five testers set the
 * bin off by accident, and one of them was aiming at the tag button when it
 * fired - the two plates sat four pixels apart. 30px plate + 14px gap, so each
 * step along the row is 44px. */
.gg-card--trashable .gg-card__roadmap { right: 52px; }
.gg-card--tagpickable .gg-card__roadmap { right: 52px; }
.gg-card--trashable.gg-card--tagpickable .gg-card__roadmap { right: 96px; }
.gg-card__roadmap button {
	display: grid;
	place-items: center;
	width: 30px;
	height: 30px;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: rgba(0, 0, 0, 0.42);
	color: rgba(255, 255, 255, 0.62);
	cursor: pointer;
	transition: background 0.14s ease, color 0.14s ease;
}
.gg-card__roadmap button:hover { background: rgba(232, 180, 79, 0.92); color: #0a1628; }
.gg-card__roadmap button:focus-visible { outline: 2px solid #E8B44F; outline-offset: 1px; }
.gg-card__roadmap svg { display: block; }
/* Already on it: says so, and cannot be pressed. */
.gg-card__roadmap.is-on button {
	background: rgba(232, 180, 79, 0.92);
	color: #0a1628;
	cursor: default;
}

/* THE TAG CONTROL ON A CARD (17 Aug 2026, Sam - Client Manager's "Client tags"
 * list: "matching the Project Manager card"). The button deliberately shares
 * every value with .gg-card__trash above so the two controls are one control
 * wearing two icons, and the menu is lifted from .gg-rv2-tagpick-menu in
 * gg-roadmap-v2/assets/admin-roadmap-v2.css so the tag list on a client card and
 * the tag list on a project card are the same object.
 *
 * It sits LEFT of the trash rather than under it: two controls stacked in one
 * corner is how a mis-click bins something. */
.gg-card__tagpick {
	position: absolute;
	right: 8px;
	top: 8px;
	z-index: 7;
	margin: 0;
	line-height: 0;
}
/* 14px clear of the bin, not 4px - see the note on .gg-card__roadmap above. */
.gg-card--trashable .gg-card__tagpick { right: 52px; }
.gg-card__tagpick-btn {
	display: grid;
	place-items: center;
	width: 30px;
	height: 30px;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: rgba(0, 0, 0, 0.42);
	color: rgba(255, 255, 255, 0.62);
	cursor: pointer;
	transition: background 0.14s ease, color 0.14s ease;
}
.gg-card__tagpick-btn:hover { background: rgba(0, 0, 0, 0.72); color: #fff; }
.gg-card__tagpick-btn:focus-visible { outline: 2px solid #E8B44F; outline-offset: 1px; }
.gg-card__tagpick svg { display: block; }

/* THE CORNER CONTROLS ON A PHONE (21 Aug 2026 UX audit, built 23 Aug at Sam's
 * instruction).
 *
 * The board used to fit a phone by scaling itself down, which took these 30px
 * plates to 17px square - two tiny targets fourteen pixels apart, one of them
 * the bin. The scaling stops below 700px now (see .gg-rv2-body in
 * gg-roadmap-v2), so this is where they get a finger-sized target instead: 36px
 * each, with the gap growing to match so the bin does not creep back towards the
 * tag button.
 *
 * IT LIVES HERE, beside the values it overrides, and not in board-shared.css
 * where the rest of the phone layout went. brand-css is enqueued after that
 * file, so the same rule written there loses to these and looks like a media
 * query that does not fire - which is exactly how it was found.
 *
 * Not scoped to a board: these are the shared card's controls and they are the
 * same size wherever the card is drawn. */
@media (max-width: 700px) {
	.gg-card__trash button,
	.gg-card__tagpick-btn,
	.gg-card__roadmap button { width: 36px; height: 36px; }
	/* 36px plate + the same 14px clear the desktop rule keeps. */
	.gg-card--trashable .gg-card__tagpick { right: 58px; }
	.gg-card--trashable .gg-card__roadmap,
	.gg-card--tagpickable .gg-card__roadmap { right: 58px; }
	.gg-card--trashable.gg-card--tagpickable .gg-card__roadmap { right: 108px; }
}
/* FIXED, NOT ABSOLUTE, and that is not a preference (17 Aug 2026). .gg-card sets
 * overflow:hidden - it has to, so the image plate and its overlay stay inside the
 * tile - and an absolutely-positioned menu inside it is clipped to the card. The
 * menu was there and unclickable, which is worse than missing. Fixed takes it out
 * of every ancestor's clipping, and the host script places it against the
 * button's own rect and closes it on scroll. */
.gg-card__tagpick-menu {
	position: fixed;
	z-index: 100000;
	min-width: 190px;
	max-width: 280px;
	max-height: 260px;
	overflow-y: auto;
	padding: 6px 0;
	background: #111826;
	border: 1px solid var(--gg-control-border, rgba(255, 255, 255, 0.28));
	box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45);
	text-align: left;
	line-height: 1.4;
}
.gg-card__tagpick-menu[hidden] { display: none; }
.gg-card__tagpick-row {
	display: flex;
	align-items: center;
	gap: 9px;
	padding: 6px 12px;
	cursor: pointer;
	color: #e8edf3;
	font-family: var(--gg-font-body, "Catamaran", sans-serif);
	font-size: 13.5px;
}
.gg-card__tagpick-row:hover { background: rgba(255, 255, 255, 0.07); }
.gg-card__tagpick-cb {
	flex: 0 0 auto;
	width: 14px;
	height: 14px;
	margin: 0;
	accent-color: #E8B44F;
}
/* A tag title is free text and can be long. It wraps rather than widening the
   menu past max-width or pushing its own tick off the row. */
.gg-card__tagpick-label { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.gg-card__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--gg-space-base);
	padding: calc(var(--gg-space-base) * 1.75) calc(var(--gg-space-base) * 2.5);
	border-bottom: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-card__body {
	flex: 1;
	padding: calc(var(--gg-space-base) * 2.5);
}

/* ── Content card — image variant (donor: gg-rv2-card family, READ-ONLY,
 * gg-roadmap-v2/src/AdminRoadmapV2.php:4548-4769 + assets/admin-roadmap-v2.css
 * :785+ — backlog #3, audited 2026-07-15) ──
 * Built ON TOP of .gg-card above — same shell, same sharp-corner
 * enforcement, same tokens. Adds a cover photo + scrim, then reuses the
 * existing .gg-card__body for the text stack (title/desc/pills). That IS the
 * card — cover + title + desc + tag pills — matching a live PM board card.
 * The .gg-card__foot status banner is OPT-IN and OFF by default: the board
 * renders it only for Scheduled/Blocked projects; live and done cards have
 * none, so gg_ui_image_card() omits it unless a 'foot' arg is passed.
 *
 * Markup (render via gg_ui_image_card() — don't hand-roll this):
 *   <a class="gg-card gg-card--img" href="…">
 *     <div class="gg-card__img" style="background-image:url(…)">
 *       <div class="gg-card__img-overlay"></div>
 *     </div>
 *     <div class="gg-card__body">
 *       <h4 class="gg-card__title">Title</h4>
 *       <p class="gg-card__desc">One-line description, clamps to 2 lines.</p>
 *       <div class="gg-card__pills"><span class="gg-pill gg-pill--ok">Live</span></div>
 *     </div>
 *     <!-- optional, Scheduled/Blocked only — omitted on a normal card:
 *     <div class="gg-card__foot gg-card__foot--warn">
 *       <span class="gg-card__foot-status">Scheduled</span>
 *       <span class="gg-card__foot-meta">Discuss</span>
 *     </div> -->
 *   </a>
 * $tag is <a> when a href is given, else a plain <div> (e.g. a decorative
 * demo card with nothing to click through to). */
.gg-card--img {
	text-decoration: none;
	cursor: pointer;
	outline: none;
	transition: transform var(--gg-transition-speed) var(--gg-transition-easing),
	            box-shadow var(--gg-transition-speed) var(--gg-transition-easing),
	            background var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-card--img:hover,
.gg-card--img:focus-visible {
	transform: translateY(-3px);
	box-shadow: var(--gg-shadow-md), 0 0 0 1px var(--gg-colour-primary);
	background: color-mix(in srgb, var(--gg-surface-card) 88%, #ffffff 12%);
	color: var(--gg-on-surface);
}
/* Belt-and-braces sharp-corner enforcement, same recipe as .gg-card above —
 * covers the new descendants (.gg-card's own rule only forces direct
 * children + img, not grandchildren like .gg-card__img-overlay). */
.gg-card--img,
.gg-card--img *,
.gg-card--img::before,
.gg-card--img::after { border-radius: 0 !important; }

.gg-card__img {
	width: 100%;
	aspect-ratio: 16 / 9;
	position: relative;
	flex: none;
	background-size: cover;
	background-position: center;
	background-color: var(--gg-surface-panel);
}
.gg-card__img-overlay {
	position: absolute;
	inset: 0;
	background: linear-gradient(180deg, rgba(0,0,0,0) 40%, var(--gg-scrim) 100%);
	pointer-events: none;
}
/* No-photo person card: gg_ui_image_card() paints large initials in the
 * image slot instead of a blank tile (pass 'initials', gg_ui_initials()). */
.gg-card__img--initials {
	display: flex;
	align-items: center;
	justify-content: center;
	background: linear-gradient(135deg,
		color-mix(in srgb, var(--gg-colour-primary) 18%, var(--gg-surface-panel)) 0%,
		var(--gg-surface-panel) 100%);
}
.gg-card__initials {
	font-family: var(--gg-font-heading);
	font-size: 34px;
	font-weight: 600;
	letter-spacing: 0.08em;
	color: color-mix(in srgb, var(--gg-colour-primary) 75%, var(--gg-on-surface));
	user-select: none;
}
/* THE ICON PLATE (2026-08-04, Sam: "could we have images for them all").
 * For things that have no photograph and never will - a setup step, a
 * connection, anything that is an action rather than an artefact. The type's
 * own icon, large, on the type's own colour, so a grid of them reads as a set
 * of different things rather than one repeated rectangle. Deliberately not a
 * stock photo: the brand rule is no metaphor images.
 * The eight tints are the SAME eight the cover palette uses, restated here as
 * tokens because a card lives in the design system and must not reach into a
 * feature plugin to find out what "ocean" means. */
.gg-card__img--icon {
	display: flex;
	align-items: center;
	justify-content: center;
	background: linear-gradient(135deg, var(--gg-tint-from, #1c2733), var(--gg-tint-mid, #3a4b60) 55%, var(--gg-tint-to, #7d93ad));
}
.gg-card__icon {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 46px;
	height: 46px;
	color: rgba(255,255,255,0.92);
	filter: drop-shadow(0 2px 6px rgba(0,0,0,0.35));
}
.gg-card__icon svg { width: 100%; height: 100%; display: block; stroke-width: 1.4; }
.gg-card__img--tint-plum   { --gg-tint-from:#3a2a5e; --gg-tint-mid:#7a4a86; --gg-tint-to:#c9764f; }
.gg-card__img--tint-teal   { --gg-tint-from:#123a3f; --gg-tint-mid:#1f6f6b; --gg-tint-to:#3fa392; }
.gg-card__img--tint-amber  { --gg-tint-from:#4a2a1c; --gg-tint-mid:#8a5a2a; --gg-tint-to:#d8a24a; }
.gg-card__img--tint-ocean  { --gg-tint-from:#1b2a52; --gg-tint-mid:#2f4e8a; --gg-tint-to:#5a8fd8; }
.gg-card__img--tint-rose   { --gg-tint-from:#43182f; --gg-tint-mid:#7a2d4e; --gg-tint-to:#c85a6e; }
.gg-card__img--tint-forest { --gg-tint-from:#16321f; --gg-tint-mid:#2f6b45; --gg-tint-to:#6fa356; }
.gg-card__img--tint-gold   { --gg-tint-from:#4a3a12; --gg-tint-mid:#9a7a2a; --gg-tint-to:#e8b44f; }
.gg-card__img--tint-slate  { --gg-tint-from:#1c2733; --gg-tint-mid:#3a4b60; --gg-tint-to:#7d93ad; }

/* FIGURES — a project's row of counts or amounts (2026-08-04). Added with the
   Figures block so Client Manager's snapshot tiles and billing strip had a home
   on the shared page instead of being dropped. Deliberately plain: a number and
   a word, the same on every project that declares any. */
.gg-figures {
	display: flex;
	flex-wrap: wrap;
	gap: var(--gg-space-base, 12px);
	margin: 0 0 calc(var(--gg-space-base, 12px) * 1.5);
}
.gg-figures__item {
	flex: 1 1 128px;
	min-width: 128px;
	display: flex;
	flex-direction: column;
	gap: 3px;
	padding: 12px 14px;
	box-sizing: border-box;
	background: var(--gg-surface-panel);
	border: var(--gg-border-width, 1px) solid var(--gg-surface-border, rgba(255,255,255,.16));
	text-decoration: none;
	color: inherit;
}
a.gg-figures__item:hover { border-color: var(--gg-colour-primary); }
.gg-figures__value {
	font-family: var(--gg-font-heading);
	font-size: 22px;
	font-weight: 600;
	line-height: 1.1;
	color: var(--gg-on-surface, #fff);
}
/* FACTS - what is true about a thing, read at a glance (2026-08-06). The
   counterpart to Questions: a question is filled in, a fact is simply true and
   comes from somewhere else. Nothing here is clickable, because forcing facts
   into openable question rows is what made a client page read as a
   half-finished form. */
.gg-facts { margin: 0 0 20px; padding: 0; }
.gg-facts__row {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 16px;
	padding: 9px 0;
	border-bottom: 1px solid var(--gg-surface-border, rgba(255,255,255,.10));
}
.gg-facts__label {
	flex: 0 0 190px;
	margin: 0;
	font-size: 12.5px;
	color: var(--gg-on-surface-muted, #9fb0c5);
}
.gg-facts__value {
	flex: 1 1 220px;
	margin: 0;
	font-size: 13.5px;
	line-height: 1.5;
	color: var(--gg-on-surface, #e8edf3);
	overflow-wrap: anywhere;
}
.gg-facts__value a { color: var(--gg-colour-primary); }
.gg-facts__source {
	margin: 8px 0 0;
	font-size: 12px;
	color: var(--gg-on-surface-muted, #9fb0c5);
}
.gg-facts__source a { color: var(--gg-colour-primary); }

/* ROADMAP - the plan, and how far along it they are (2026-08-06). Same status
   node as the question rail, so a ring and a tick mean one thing everywhere. */
.gg-roadmap { margin: 0 0 22px; }
.gg-roadmap__head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 8px; }
.gg-roadmap__count { font-size: 12px; color: var(--gg-on-surface-muted, #9fb0c5); }
.gg-roadmap__empty { margin: 0 0 10px; font-size: 13px; color: var(--gg-on-surface-muted, #9fb0c5); }
.gg-roadmap__list { list-style: none; margin: 0; padding: 0; position: relative; }
.gg-roadmap__list::before {
	content: ''; position: absolute; left: 15px; top: 14px; bottom: 14px; width: 2px;
	background: var(--gg-rail, rgba(255,255,255,.13));
}
.gg-roadmap__stage { display: flex; gap: 12px; align-items: flex-start; padding: 7px 0; position: relative; }
.gg-roadmap__body { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; padding-top: 4px; }
.gg-roadmap__toggle {
	background: none; border: 0; padding: 0; cursor: pointer;
	font: inherit; font-size: 13.5px; text-align: left;
	color: var(--gg-on-surface, #e8edf3);
}
.gg-roadmap__toggle:hover { color: var(--gg-colour-primary); }
/* The circle IS the button on a step you can tick, so it has to look like one. */
.gg-roadmap__tick {
	background: none; border: 0; padding: 0; cursor: pointer; line-height: 0;
	border-radius: 50%;
}
.gg-roadmap__tick:hover { filter: brightness(1.35); }
.gg-roadmap__tick:focus-visible { outline: 2px solid var(--gg-colour-primary); outline-offset: 2px; }
/* An untickable step's circle must not invite a click it will not answer. */
.gg-roadmap__stage.is-auto .gg-setup-node { opacity: .8; }
.gg-roadmap__title { font-size: 13.5px; color: var(--gg-on-surface, #e8edf3); }
.gg-roadmap__stage.is-done .gg-roadmap__toggle,
.gg-roadmap__stage.is-done .gg-roadmap__title { color: var(--gg-on-surface-muted, #9fb0c5); }
/* A stage the platform ticked itself says so, quietly - so a coach knows why it
   is done and why they cannot untick it. */
.gg-roadmap__auto {
	font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em;
	color: var(--gg-on-surface-muted, #9fb0c5); opacity: .7;
}
.gg-roadmap__note { flex: 1 0 100%; font-size: 12px; color: var(--gg-on-surface-muted, #9fb0c5); }
.gg-roadmap__edit { margin-top: 10px; }
.gg-roadmap__edit summary { cursor: pointer; font-size: 12.5px; color: var(--gg-on-surface-muted, #9fb0c5); }
.gg-roadmap__edit summary:hover { color: var(--gg-colour-primary); }
.gg-roadmap__hint { margin: 8px 0 6px; font-size: 12px; color: var(--gg-on-surface-muted, #9fb0c5); }
.gg-roadmap__input {
	width: 100%; max-width: 520px; box-sizing: border-box; padding: 10px 12px;
	background: var(--gg-surface-panel); color: var(--gg-on-surface, #e8edf3);
	border: var(--gg-border-width, 1px) solid var(--gg-surface-border, rgba(255,255,255,.16));
	border-radius: 0; font-family: var(--gg-font-body); font-size: 13px; resize: vertical;
}
.gg-roadmap__input:focus { outline: none; border-color: var(--gg-colour-primary); }
.gg-roadmap__foot { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.gg-roadmap__said { font-size: 12px; color: var(--gg-on-surface-muted, #9fb0c5); }

.gg-figures__label {
	font-size: 12px;
	line-height: 1.35;
	color: var(--gg-on-surface-muted, #9fb0c5);
}

/* THE TIMELINE (ported 2026-08-06 from the old Client Manager page, which had
   it first and had it better). An event with a kind, a time, who did it and a
   body worth reading is a general shape, so it lives here rather than on one
   page. Colours come off the brand tokens; the old page hard-coded them. */
.gg-timeline-head { display: flex; align-items: baseline; gap: 12px; }
.gg-timeline-count { font-size: 12px; color: var(--gg-on-surface-muted, rgba(255,255,255,.4)); }
/* The head sits ON the box's top edge, so the two read as one thing. */
.gg-timeline-head { margin-bottom: 8px; }
/* AND AIR ABOVE IT (2026-08-19). It had no top margin, which was invisible
   while a page carried ONE timeline sitting at the very end of everything. A
   client's page now carries two - Communications up near the top and History at
   the foot - and both landed hard against whatever preceded them, the History
   heading practically touching the Research row above it. A section heading
   needs the same air whichever state it is in and wherever it sits. */
.gg-timeline-head { margin-top: 34px; }
/* The rail inside the box starts below the padding, not at the panel edge. */
.gg-timeline::before { left: 31px; }

/* A SETTING, DRAWN OPEN (2026-08-06), ON THE SAME GRID AS EVERYTHING ELSE.
   The first attempt just reordered a flex row and left three different left
   edges on one page - labels at 475 and 518, hints at 294, buttons at 770 and
   946, all drifting per row because of auto margins (measured, then fixed).
   Now a setting is the same two columns as a fact: label, then control. One
   left edge for labels, one for values, top to bottom.
   Scoped to .gg-project-qrow--setting, so collapsed interview rows are
   untouched. */
.gg-project-qrow--setting { list-style: none; margin: 0 0 6px; }
/* THE LABEL SITS ABOVE ITS FIELD (2026-08-19, Sam).
   It was two columns: label on the left, control on the right. That is not how
   a field looks anywhere else on the platform - open one inside a toggle and
   its label is above it, full width - so the rows at the top of a project page
   read as a different kind of thing from the rows below them.
   Sam: "I want the same to be done on the shared project page/interview system
   for all the fields at the top... Then the fields can be made full width -
   they currently look stupid."
   One column does both at once: the label row is still the first thing in it,
   and the control under it now has the whole width instead of whatever was
   left after a fixed 190px. The rules below still say `grid-column: 1`, which
   is now the only column there is. */
.gg-project-qrow--setting .gg-bv2-field {
	display: grid;
	/* WIDTH: 100% is doing real work. A grid sizes to its content by default, so
	   the divider under each setting ran only as far as that row's longest line
	   - 415px under one, 445px under the next, against 812px of facts above.
	   Measured, not eyeballed. */
	width: 100%;
	box-sizing: border-box;
	grid-template-columns: minmax(0, 1fr);
	gap: 3px;
	align-items: start;
	padding: 10px 0;
	border-bottom: 1px solid var(--gg-surface-border, rgba(255,255,255,.10));
}
/* Every control in one of these rows takes the row. A text box that stops
   two-thirds of the way across, beside nothing, is the thing that looked wrong.

   NOT THE PICTURE. It is deliberately a small tile with its own words on it
   (Sam, 13 Aug: "background picture could visibly look smaller on this page to
   save room", and "text of choose an image could be on the background image
   picture"). Stretched to the full row it became an empty well with its button
   stranded in the middle, which is the opposite of what he asked for. */
.gg-project-qrow--setting .gg-bv2-field > :not(.gg-bv2-field-label-row):not(.gg-bv2-field-upload) {
	width: 100%;
	max-width: 100%;
	box-sizing: border-box;
}
/* Everything except the input lives INSIDE .gg-bv2-field-label-row - the label,
   the tooltip, the hint and the Discuss button are all siblings in there, which
   is why column rules aimed at them never landed (they are not grid items).
   So the label-row IS column one, and stacks its own contents. */
/* CLOSE TO THE FIELD IT NAMES (2026-08-19, Sam: "field labels should not be so
   far away from them"). The row's own gap sets the distance; this stacks the
   label's own parts tightly so the two read as one thing. */
.gg-project-qrow--setting .gg-bv2-field-label-row {
	grid-column: 1;
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 2px 6px;
	margin: 0;
	line-height: 1.2;
}
.gg-project-qrow--setting .gg-bv2-field-label {
	order: 1;
	text-transform: none;
	letter-spacing: 0;
	font-size: 12.5px;
	/* THE TITLE IS BOLD (2026-08-19, Sam: "each field title should be bolded").
	 *
	 * It was set to 400 and the muted colour, which put a field's NAME at the
	 * same weight as the hint underneath it and lighter than the value in the
	 * box. Nothing on the row said which of the three lines was the title.
	 *
	 * 700 is the field box's own answer, not a number picked here - the shared
	 * .gg-bv2-field-label is 700 everywhere else and these rows had overridden
	 * it down. What they keep overriding is the CASE: "What is going on right
	 * now" in tracked uppercase is a heading shouting a sentence, which is why
	 * these rows went sentence case in the first place. */
	font-weight: 700;
	color: var(--gg-on-surface, var(--ggb-text, #e8eef6));
}
.gg-project-qrow--setting .gg-tip-icon { order: 2; }
/* ON THE FIELD'S OWN LINE (2026-08-19, Sam: "Discuss with assistant should
   always be in line with field title").
 *
 * It was ordered after the hint, and the hint takes a full line of its own, so
 * a door to the assistant that belongs to one field was pushed onto a third
 * line and read as a control belonging to the page. Name, "?" and the way in
 * are one line now, the way the field box has always drawn them everywhere
 * else - only the settings rows had reordered them apart. */
.gg-project-qrow--setting .gg-bv2-field-help-btn {
	order: 3;
	flex: 0 0 auto;
	margin: 0;
}
/* The hint under all three, on its own line, in the label column. */
.gg-project-qrow--setting .gg-bv2-field-note {
	order: 4;
	flex: 1 0 100%;
	margin: 2px 0 0;
	font-size: 11.5px;
	line-height: 1.4;
	color: var(--gg-on-surface-muted, #9fb0c5);
	opacity: .75;
}
.gg-project-qrow--setting .gg-bv2-field-help-tip,
.gg-project-qrow--setting .gg-bv2-field-status {
	order: 5;
	flex: 1 0 100%;
	margin: 2px 0 0;
}
/* EXCEPT THE ONE INSIDE THE BUTTON (2026-08-20, Sam: "Discuss with assistant
   button looks weird - big gap between icon and text. Also icon too squished on
   the left").

   .gg-bv2-field-help-tip is two different things wearing one class: the row's
   own full-width status line, which the rule above is for, and the label span
   INSIDE the Discuss button. The button is itself a flex container, so that
   descendant selector reached in and handed the span flex: 1 0 100% - 140px of
   a 160px button - which pushed the icon out to a 2px inset and opened the gap
   between them. Measured on the client page before and after.

   Put right by naming the button's own child rather than by narrowing the rule
   above: that rule is load-bearing for three settings rows and rewriting its
   selector risks them, where this only excludes the one element that was never
   meant to be caught. It is a word on a chip, so it sizes to itself.

   AND THE ICON LEADS, EVERYWHERE. The markup is span-then-svg, so left alone
   the chip reads "Discuss with assistant" then a speech bubble - which is what
   it has been doing on every NON-settings row all along, while settings rows
   drew it icon-first because the leaked order:5 happened to flip them. Two
   different buttons depending on which row you were looking at, by accident.
   Stating order here settles it as one: the glyph first, then the words, which
   is the shape Sam has been looking at and the same way round as every other
   labelled control on the platform. */
.gg-bv2-field-help-btn > .gg-bv2-field-help-tip {
	order: 1;
	flex: 0 0 auto;
	margin: 0;
}
/* AN EMPTY LINE IS NOT A LINE (2026-08-19, Sam: "less spacing between field
   title and field").
 *
 * The save status takes a full-width line of its own so "Saved" can never
 * shove the label sideways. It was taking that line while EMPTY too, which put
 * a blank strip under every field name on the page and read as the gap the
 * title had been given. Nothing was spacing them apart - something invisible
 * was sitting between them.
 *
 * They collapse until they have something to say, and take their line back the
 * moment they do. The binder writes textContent, never a space, so :empty is
 * exactly "nothing to report". */
.gg-project-qrow--setting .gg-bv2-field-help-tip:empty,
.gg-project-qrow--setting .gg-bv2-field-status:empty {
	display: none;
}
.gg-timeline {
	list-style: none;
	margin: 0 0 20px;
	padding: 4px 14px 4px 16px;
	position: relative;
	/* A LONG RELATIONSHIP SHOULD NOT MAKE A LONG PAGE (2026-08-06, Sam: "the
	   history should be inside a scrollable box, so it doesn't go down
	   forever"). A plain max-height needs no cutoff rule: a short timeline never
	   reaches it and is not boxed at all, a long one scrolls. Generous on
	   purpose - this is the block a coach actually reads. */
	max-height: 62vh;
	overflow-y: auto;
	overscroll-behavior: contain;   /* the page does not lurch when this ends */

	/* AND IT SHOULD LOOK LIKE A BOX (Sam, same day: "make the history box more
	   obviously different from the rest of the page... so you can more easily
	   see when you're in the box"). A recessed panel, a real border, and the
	   content fading at both edges - the fade is the honest signal that there is
	   more above and below, which a hard edge never gives you. */
	background: var(--gg-surface-sunken, rgba(0,0,0,.22));
	border: var(--gg-border-width, 1px) solid var(--gg-surface-border, rgba(255,255,255,.16));
	box-shadow: inset 0 8px 10px -10px rgba(0,0,0,.7), inset 0 -8px 10px -10px rgba(0,0,0,.7);
	scrollbar-width: thin;
	scrollbar-color: var(--gg-surface-border, rgba(255,255,255,.25)) transparent;
}
.gg-timeline::-webkit-scrollbar { width: 8px; }
.gg-timeline::-webkit-scrollbar-track { background: transparent; }
.gg-timeline::-webkit-scrollbar-thumb {
	background: var(--gg-surface-border, rgba(255,255,255,.22));
	border-radius: 0;
}
.gg-timeline::-webkit-scrollbar-thumb:hover { background: var(--gg-on-surface-muted, rgba(255,255,255,.35)); }
.gg-timeline::before {
	content: ''; position: absolute; left: 15px; top: 6px; bottom: 6px; width: 1px;
	background: var(--gg-rail, rgba(255,255,255,.08));
}
.gg-timeline__event { display: flex; gap: 14px; padding: 10px 0; position: relative; }
.gg-timeline__event[role="button"] { cursor: pointer; }
.gg-timeline__event:focus-visible { outline: 1px solid var(--gg-colour-primary); outline-offset: 2px; }
.gg-timeline__icon {
	flex: 0 0 auto; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;
	border-radius: 50%; background: var(--gg-surface-panel, rgba(10,22,40,.9));
	border: 1px solid var(--gg-surface-border, rgba(255,255,255,.12));
	color: var(--gg-on-surface-muted, rgba(255,255,255,.6)); position: relative; z-index: 1;
}
/* Who did it, at a glance - the coach in one colour, their assistant in another. */
.gg-timeline__event.actor-coach .gg-timeline__icon { color: #7fc7ff; border-color: rgba(127,199,255,.4); }
.gg-timeline__event.actor-shepherd .gg-timeline__icon { color: var(--gg-colour-primary, #f5d28a); border-color: rgba(232,180,79,.4); }
.gg-timeline__body { min-width: 0; flex: 1; }
.gg-timeline__line { font-size: 13.5px; color: var(--gg-on-surface, rgba(255,255,255,.85)); overflow-wrap: anywhere; }
.gg-timeline__meta {
	display: flex; flex-wrap: wrap; gap: 10px; margin-top: 3px;
	font-size: 11px; color: var(--gg-on-surface-muted, rgba(255,255,255,.4));
}
.gg-timeline__type { color: var(--gg-on-surface-muted, rgba(255,255,255,.3)); text-transform: uppercase; letter-spacing: .04em; }
.gg-timeline__expanded {
	margin-top: 10px; padding: 12px 14px;
	background: var(--gg-surface-panel); border: 1px solid var(--gg-surface-border, rgba(255,255,255,.14));
	font-size: 13px; line-height: 1.5;
}
.gg-timeline__expanded h4 { margin: 0 0 6px; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--gg-on-surface-muted); }
.gg-timeline__text { margin-top: 8px; white-space: pre-wrap; overflow-wrap: anywhere; }
.gg-timeline__logged { color: var(--gg-on-surface-muted); font-size: 11px; margin-top: 6px; }

/* AN EMAIL, DRAWN AS AN EMAIL (2026-08-20, Sam: "it should look like a normal
   email that's waiting to go out at the top - from name, froma address,
   subject, body").

   The rules for gg_ui_email(). One drawing for every email the platform shows,
   so the draft waiting on a client's page and the message opened in their
   timeline read the same way.

   SHARP CORNERS and the shared surface tokens, like everything else here - the
   card carries no radius of its own and no colour of its own. The only thing
   it adds is the arrangement: who it is from, then the subject at heading
   weight, then the words with room to breathe. */
.gg-email {
	display: block;
	padding: 16px 18px;
	background: var(--gg-surface-panel);
	border: var(--gg-border-width, 1px) solid var(--gg-surface-border, rgba(255,255,255,.14));
	border-radius: 0;
	color: var(--gg-on-surface, #e8edf3);
}
.gg-email__eyebrow {
	margin: 0 0 10px;
	font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
	color: var(--gg-on-surface-muted, rgba(255,255,255,.45));
}
/* The head is the envelope and the body is the letter, so a hairline sits
   between them rather than a gap alone - a gap reads as spacing, a line reads
   as "this part is about the message, that part is the message". */
.gg-email__head {
	padding-bottom: 12px;
	border-bottom: 1px solid var(--gg-surface-border, rgba(255,255,255,.12));
}
.gg-email__line {
	display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
	margin: 0 0 4px;
	font-size: 13px; line-height: 1.4;
}
.gg-email__label {
	flex: 0 0 auto;
	min-width: 44px;
	font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
	color: var(--gg-on-surface-muted, rgba(255,255,255,.45));
}
.gg-email__who { font-weight: 600; overflow-wrap: anywhere; }
/* The address is quieter than the name because it is the half you check, not
   the half you read. It stays selectable text rather than a mailto link: this
   component draws an email, it never offers a way to start another one. */
.gg-email__address {
	color: var(--gg-on-surface-muted, rgba(255,255,255,.55));
	overflow-wrap: anywhere;
}
/* TWO CLASSES DELIBERATELY. The subject is an <h4>, and a panel this component
   gets dropped into may already style its own headings - the timeline's
   expanded panel shouts every h4 in uppercase at 12px, which turned a real
   subject line into "RE: AFTER OUR CALL". A subject is somebody's own words and
   is never shouted, so this rule has to outrank an ancestor's element rule. */
.gg-email .gg-email__subject {
	margin: 10px 0 0;
	font-family: var(--gg-font-heading);
	font-size: var(--gg-h4-size, 18px);
	font-weight: var(--gg-weight-heading, 600);
	line-height: var(--gg-line-height-heading, 1.25);
	letter-spacing: normal;
	text-transform: none;
	/* INHERITED, NOT --gg-colour-heading. That token is the heading colour for
	   a page on the light background and is a dark navy; the surfaces this
	   component sits on are dark, so naming it here paints the subject almost
	   invisibly onto the panel. Inheriting takes the card's own --gg-on-surface
	   and keeps the component right whichever surface it is dropped onto. */
	color: inherit;
	overflow-wrap: anywhere;
}
.gg-email__meta {
	margin: 6px 0 0;
	font-size: 11px;
	color: var(--gg-on-surface-muted, rgba(255,255,255,.45));
}
/* DRAFT OR SCHEDULED (2026-08-21, Sam: "id rather have a schedule/draft
   switch"). One control saying which of two states a draft is in, in place of a
   button that only ever undid one of them.

   THE WORD IS THE LABEL. A switch with a fixed caption makes a coach work out
   which way is on; this one says what it currently IS, and changes as it moves.
   Scheduled takes the platform's accent so the booked state is the one the eye
   finds down a page. */
/* ROOM BETWEEN THEM (2026-08-21, Sam: "icons are too squished. maybe 2px extra
   in between each"). The date, the toggle, the word and the "?" were sitting on
   8 to 12px of each other and read as one clump. Two more everywhere they meet,
   including inside the switch itself - the toggle and its word are two of the
   things that were touching. */
.gg-outbox__mode { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.gg-outbox__mode .gg-switch { gap: 10px; }
/* IT SITS BESIDE THE DAY IT GOVERNS, so the row is the date and its state on
   one line rather than a toggle stranded under the panel. */
.gg-email__rowvalue .gg-outbox__mode { margin-left: 12px; }
.gg-outbox__mode-word {
	font-size: 11px; font-weight: 700;
	letter-spacing: .08em; text-transform: uppercase;
	color: var(--gg-on-surface-muted, rgba(255,255,255,.5));
}
/* THE SAME "ON" COLOUR THE SWITCH ITSELF USES. The shared .gg-switch paints its
   track with --gg-status-info when checked, so a gold word beside a blue toggle
   was one control showing two different on-states. */
.gg-outbox__mode.is-on .gg-outbox__mode-word { color: var(--gg-status-info, #7fc7ff); }
.gg-outbox__mode-said {
	font-size: 11px;
	color: var(--gg-on-surface-muted, rgba(255,255,255,.5));
}
/* A DISABLED DATE BOX STILL HAS TO LOOK DISABLED. In the Draft state the day is
   not settable, and a box that reads exactly like a live one is the same fault
   this whole panel keeps being corrected for. */
.gg-email__date:disabled,
.gg-email__rowvalue .gg-email__date:disabled {
	opacity: .45;
	cursor: default;
}

/* WHEN A QUEUED EMAIL GOES OUT, stated on its row in the list (2026-08-21, Sam:
   "this list on this page of future emails should show the scheduled date
   too"). It sits in the row's meta lane ahead of the chip and the written-ago
   time, because it is the fact a coach is scanning that list FOR - the ago time
   says when the assistant wrote it, which is not the same question.

   Quieter than the chip and louder than the time: it is the answer, but the
   chip is what the row is. A row with nothing booked says so rather than
   leaving a gap, because a blank there reads as "not loaded" and not as
   "nothing scheduled". */
.gg-entry__due {
	font-size: 11.5px;
	white-space: nowrap;
	color: var(--gg-on-surface, rgba(255,255,255,.75));
}
.gg-entry__due--none { color: var(--gg-on-surface-muted, rgba(255,255,255,.4)); }
/* A DAY ALREADY GONE IS NOT A QUIET FACT. It is the one row on the list a coach
   should act on, so it carries the platform's warning colour rather than
   sitting at the same weight as a booking still to come. */
.gg-entry__due--past { color: var(--gg-status-warn, #f5d28a); }
.gg-entry__due--past .gg-entry__due-label { color: inherit; }
.gg-entry__due-label {
	margin-right: 4px;
	font-size: 10px; font-weight: 700;
	letter-spacing: .08em; text-transform: uppercase;
	color: var(--gg-on-surface-muted, rgba(255,255,255,.45));
}
/* ON A PHONE THE LANE WRAPS RATHER THAN EATING THE NAME. .gg-entry__meta is
   flex:none and every child of it refuses to wrap, so adding a third fact took
   its width out of the only thing that can give - the client's name and the
   subject beside it, which shrank to a few characters on the very rows this was
   added for. The lane stacks under 600px instead, and the row grows a line. */
@media (max-width: 600px) {
	.gg-entry__meta { flex-wrap: wrap; justify-content: flex-end; row-gap: 2px; }
	.gg-entry__due { flex-basis: 100%; text-align: right; }
}

/* THE COMPOSER HEAD - the shape a mail client draws, and the one the check-in
   composer had grown on its own before this was shared (2026-08-20, Sam: "see
   how from and subject are designed in boxes. we want to make a component of
   this. and reuse it on pages like this").

   A short muted label in a fixed left gutter, the value beside it, a hairline
   between rows. Lifted from gg-shepherd's .gg-ca-ci-hrow, which is now drawn by
   this instead - the whole point being that the two screens cannot drift. */
.gg-email__rows {
	border: var(--gg-border-width, 1px) solid var(--gg-surface-border, rgba(255,255,255,.09));
	background: var(--gg-surface-card, rgba(255,255,255,.03));
}
/* THE HEAD STOPS DRAWING ITS OWN LINE. The rows box carries a full border, so
   the head's usual hairline underneath it was a second rule about a centimetre
   below the first. */
.gg-email__head--rows { padding-bottom: 0; border-bottom: 0; }
.gg-email__row {
	display: flex; align-items: center; gap: 12px; min-width: 0;
	padding: 8px 14px;
	border-bottom: 1px solid var(--gg-surface-border, rgba(255,255,255,.07));
}
.gg-email__row:last-child { border-bottom: 0; }
.gg-email__rowlabel {
	flex: 0 0 58px;
	font-size: 10px; font-weight: 700;
	letter-spacing: .1em; text-transform: uppercase;
	color: var(--gg-on-surface-muted, rgba(255,255,255,.45));
}
.gg-email__rowvalue { flex: 1 1 auto; min-width: 0; }
.gg-email__rowtext {
	font-size: 13px;
	color: var(--gg-on-surface, rgba(255,255,255,.85));
	overflow-wrap: anywhere;
}
/* SIDE BY SIDE, AND NOT HALF THE ROW EACH. Two controls in one row are one
   fact in two halves - a sender's name and the address it goes out by - so the
   first takes only what it needs and the second takes the rest, up to a width
   an address actually needs. Given equal flex they sat at opposite ends of the
   row with a gap between them, which reads as two unrelated fields. */
.gg-email__rowgroup { display: flex; align-items: center; gap: 8px; min-width: 0; }
.gg-email__rowgroup > * { min-width: 0; }
.gg-email__rowgroup > :first-child { flex: 0 1 auto; max-width: 40%; }
.gg-email__rowgroup > :first-child ~ * { flex: 1 1 auto; max-width: 320px; }
/* THE BOXES STAY BOXES. This is the look Sam pointed at and asked to be made a
   component - a visible box per value, so the head reads as something you fill
   in. The donor rule tried to make them chromeless and never actually did,
   because .gg-input carries !important on its background and border; the screen
   Sam approved is the one where that attempt lost. Keeping the boxes is
   therefore both his call and what was really on screen. */
.gg-email__rowvalue .gg-input,
.gg-email__rowvalue .gg-select {
	width: 100%; box-sizing: border-box;
	font-size: 13px !important;
}
.gg-email__rowvalue .gg-input:focus,
.gg-email__rowvalue .gg-select:focus {
	border-color: var(--gg-accent-gold, #f5d28a) !important;
	box-shadow: none;
	outline: none;
}
/* On a phone the gutter stops holding a column of its own - 58px beside a
   320px screen costs the address the room it needs to stay readable. */
@media (max-width: 600px) {
	.gg-email__row { flex-wrap: wrap; gap: 2px 12px; }
	.gg-email__rowlabel { flex-basis: 100%; }
	.gg-email__rowgroup { flex-wrap: wrap; }
	/* THE SAME TWO SELECTORS, not a looser one. A media query adds no
	   specificity, so `.gg-email__rowgroup > *` at (0,1,0) never beat the
	   :first-child pair at (0,2,0) above and the caps stayed on - the row did
	   not stack on a phone at all, which is the one thing this block is for. */
	.gg-email__rowgroup > :first-child,
	.gg-email__rowgroup > :first-child ~ * { flex: 1 1 100%; max-width: none; }
}

/* THE HEAD AS A FORM. A message that has not gone anywhere yet has four things
   a coach still sets - who it is from, the address it leaves by, what it says on
   the tin, and the day it goes - and they are stated as one list of labelled
   fields at one size, not a heading with rows around it. The rows are the
   platform's own .gg-form-row, so they match every other form on the estate and
   nothing here restates how a field looks. */
.gg-email__head--fields { padding-bottom: 14px; }
.gg-email__fields { display: flex; flex-direction: column; gap: 10px; }
.gg-email__fields .gg-form-row { margin: 0; }
/* A stated line above the fields is the envelope; the gap is what stops the
   first label reading as part of it. */
.gg-email__line + .gg-email__fields { margin-top: 12px; }
/* ONE SIZE ACROSS ALL FOUR, and the dropdown is why this line exists: .gg-select
   sets 14px where .gg-input sets 13, so the address sat a notch bigger than the
   name beside it. Sam's complaint about this panel was that the type was
   "randomaly huge" in one place and not another, and a row of fields that do not
   agree with each other is the same fault at a smaller scale.

   !important for the same reason the .gg-input/.gg-select block further down
   already uses it: wp-admin ships `.wp-core-ui select { font-size: 14px }` and
   a plain class does not out-specify it. */
.gg-email__fields .gg-input,
.gg-email__fields .gg-select { font-size: 13px !important; }
/* The two short ones sit side by side where there is room - an address and a
   day are both narrow, and a column of four full-width boxes reads as a long
   form rather than the top of an email. */
@media (min-width: 700px) {
	.gg-email__fields { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px; }
}

/* THE LAST ROW OF THE HEAD, for a control that belongs with the envelope - when
   it is going out, and the calendar that sets it. On the same line rather than
   stacked, because a heading with a form bolted underneath is what made the
   first version of this panel a list of five things before you reached the
   email. */
.gg-email__head-extra {
	display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
	margin-top: 12px;
	font-size: 13px;
}
.gg-email__head-extra .gg-email__label { min-width: 0; }
/* THE DATE ITSELF. A native date input, so the browser draws the calendar and
   there is no date library to keep in step with this stylesheet - the same call
   gg_ui_field( 'date' ) already made on 10 Aug. It is dressed to match the
   panel rather than left in the operating system's own chrome, and color-scheme
   is what stops the little calendar button being drawn black on a dark panel. */
/* TWO CLASSES, because WordPress admin styles `input[type=date]` by element and
   would otherwise hand a coach a white box with a black calendar button in the
   middle of a dark panel. The control always sits in this row, in both the
   places it is drawn, so naming the row is honest rather than a specificity
   trick for its own sake. */
.gg-email__head-extra .gg-email__date,
.gg-email__fields .gg-email__date,
.gg-email__rowvalue .gg-email__date {
	/* WIDE ENOUGH FOR A DATE AND NOTHING MORE (2026-08-21, Sam: "calendar icon
	   should be next to the date numbers"). The row's controls fill their lane,
	   which is right for a subject and wrong for a date: it stretched the box
	   across the panel and left the little calendar button stranded at the far
	   end, a hand's width from the numbers it opens. */
	width: auto !important;
	padding: 6px 10px;
	/* WordPress admin sets min-height: 40px on every date input, which is what
	   the padding and type size here are fighting - without this the control
	   renders 40px tall beside an 11px label and reads as a form dropped into
	   the panel rather than a fact on the head's own line. */
	min-height: 0;
	background: var(--gg-surface-card, rgba(255,255,255,.04));
	border: var(--gg-border-width, 1px) solid var(--gg-surface-border, rgba(255,255,255,.16));
	border-radius: 0;
	color: var(--gg-on-surface, #e8edf3);
	font-family: var(--gg-font-body);
	font-size: 13px;
	line-height: 1.3;
	color-scheme: dark;
}
/* FOCUS TAKES THREE DECLARATIONS, not one. WordPress core's own focus rule sets
   a blue border AND a blue ring as well as the outline, so an outline alone
   leaves the platform's gold sitting inside WordPress blue. Both states are
   named here so the control looks like ours whether or not it is focused. */
.gg-email__head-extra .gg-email__date:focus,
.gg-email__head-extra .gg-email__date:focus-visible,
.gg-email__fields .gg-email__date:focus,
.gg-email__fields .gg-email__date:focus-visible,
.gg-email__rowvalue .gg-email__date:focus,
.gg-email__rowvalue .gg-email__date:focus-visible {
	border-color: var(--gg-accent-gold, #f5d28a);
	box-shadow: none;
	outline: 2px solid var(--gg-accent-gold, #f5d28a);
	outline-offset: 1px;
}
.gg-email__head-extra .gg-email__date::-webkit-calendar-picker-indicator,
.gg-email__rowvalue .gg-email__date::-webkit-calendar-picker-indicator,
.gg-email__fields .gg-email__date::-webkit-calendar-picker-indicator { cursor: pointer; opacity: .75; }
.gg-email__head-extra .gg-email__date::-webkit-calendar-picker-indicator:hover,
.gg-email__rowvalue .gg-email__date::-webkit-calendar-picker-indicator:hover,
.gg-email__fields .gg-email__date::-webkit-calendar-picker-indicator:hover { opacity: 1; }
.gg-email__date-said,
.gg-outbox__field-said {
	display: inline-block;
	margin-top: 4px;
	font-size: 11px;
	color: var(--gg-on-surface-muted, rgba(255,255,255,.5));
}
.gg-email__body {
	margin-top: 14px;
	font-size: 14px; line-height: 1.6;
	overflow-wrap: anywhere;
}
/* THE WORDS AS TYPED, and only when they ARE typed words. pre-wrap and not
   nl2br, so nothing is inserted into somebody's message on its way to the
   screen. It is a modifier rather than a rule on the body, because a message
   that genuinely arrived as HTML brings its own line breaks - pre-wrap over
   that turns its source newlines and indenting into blank lines and gaps on
   top of its own layout. gg_ui_email() adds this in the plain-text branch. */
.gg-email__body--text { white-space: pre-wrap; }
/* THE SAME WORDS, IN A BOX. A draft still waiting for a coach's yes is theirs
   to change, so on those screens the body IS the control - same type, same
   spacing, same place, so the message does not jump when a screen lets it be
   edited. It opens deep enough to read a short check-in whole rather than in a
   two-line slot, and it can be dragged taller; it does not grow on its own, so
   a long draft is still scrolled inside the box. */
.gg-email__body--edit {
	display: block;
	width: 100%;
	box-sizing: border-box;
	min-height: 140px;
	padding: 10px 12px;
	background: var(--gg-surface-card, rgba(255,255,255,.04));
	border: var(--gg-border-width, 1px) solid var(--gg-surface-border, rgba(255,255,255,.16));
	border-radius: 0;
	color: var(--gg-on-surface, #e8edf3);
	font-family: var(--gg-font-body);
	resize: vertical;
}
.gg-email__body--edit:focus,
.gg-email__body--edit:focus-visible {
	border-color: var(--gg-accent-gold, #f5d28a);
	box-shadow: none;
	outline: 2px solid var(--gg-accent-gold, #f5d28a);
	outline-offset: 1px;
}
.gg-email__foot {
	display: block;
	margin-top: 14px; padding-top: 12px;
	border-top: 1px solid var(--gg-surface-border, rgba(255,255,255,.12));
	font-size: 12.5px; line-height: 1.5;
	color: var(--gg-on-surface-muted, rgba(255,255,255,.6));
}
.gg-email__foot > :first-child { margin-top: 0; }
.gg-email__foot > :last-child { margin-bottom: 0; }
/* WHY IT WROTE THAT. One class for it, because the assistant's reasoning shows
   under a draft in three places now - the waiting email on a client's page, the
   backlog rows under it, and the check-in queue - and each had grown its own.
   The label carries the accent so the eye can skip the whole line when it is
   the words being checked.

   IT CARRIES ITS OWN GAP, because it is not always in a footer. The backlog
   rows put it straight under the message with nothing between the two, and a
   reasoning that butts against the words reads as another paragraph of the
   email. Inside the foot the border and padding already separate it, so the gap
   comes off there rather than doubling. */
.gg-email__why { margin: 12px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--gg-on-surface-muted, rgba(255,255,255,.6)); }
.gg-email__foot .gg-email__why { margin-top: 0; font-size: inherit; line-height: inherit; color: inherit; }
/* --gg-accent-gold AND NOT --gg-colour-primary. The primary colour is a brand
   setting and on this install it is a dark navy, #022F42 - named on these dark
   panels it drew "Why:" at 1.2:1 against the card, which is invisible rather
   than quiet. --gg-accent-gold is the platform's own on-dark accent, and it is
   what the hand-rolled rules this component replaced were reaching for when
   they wrote #f5d28a by hand. Same reasoning for the focus outlines above. */
.gg-email__why strong { color: var(--gg-accent-gold, #f5d28a); font-weight: 600; }

/* BARE - the same email inside something that is already a panel. The opened
   row in a client's timeline has its own border and its own background, so the
   card drawn again inside it is a box in a box. The arrangement is what matters
   and the arrangement is what stays. */
.gg-email--bare {
	padding: 0;
	background: none;
	border: 0;
}

/* ON A PHONE the label stops holding a column of its own. A 44px gutter beside
   a 320px screen costs the address the room it needs, and an address that
   wraps mid-word is the one fact on here that has to stay readable. */
@media (max-width: 600px) {
	/* NOT :not() BY ACCIDENT. `.gg-email--bare` sits above this at the same
	   specificity, so a plain `.gg-email { padding }` here would hand the bare
	   variant its inset back on every phone - the box inside a box it exists to
	   remove, visible only on mobile. */
	.gg-email:not(.gg-email--bare) { padding: 14px; }
	.gg-email__line { gap: 4px 8px; }
	.gg-email__line .gg-email__label { min-width: 0; flex-basis: 100%; }
	/* THE HEAD ROW STAYS ON ONE LINE. "Sending" is two words next to a date, not
	   a heading over a form - giving it a row of its own is the stacked shape
	   this row exists to avoid, and it only ever happened on a phone. */
	.gg-email__head-extra .gg-email__label { min-width: 0; flex-basis: auto; }
}

/* THE QUICK NOTE (2026-08-04) - type a line, save it, it is recorded. Carried
   by the Notes block for any type that declares a writer. Built for Client
   Manager's note box, which had no home here and is the same shape as the
   free-text notes lost in the Your Roadmap reversal. */
.gg-quicknote { display: flex; flex-direction: column; gap: 6px; margin: 0 0 20px; }
.gg-quicknote__label { font-size: 12px; color: var(--gg-on-surface-muted, #9fb0c5); }
.gg-quicknote__input {
	width: 100%;
	box-sizing: border-box;
	padding: 10px 12px;
	background: var(--gg-surface-panel);
	border: var(--gg-border-width, 1px) solid var(--gg-surface-border, rgba(255,255,255,.16));
	border-radius: 0;
	color: var(--gg-on-surface, #e8edf3);
	font-family: var(--gg-font-body);
	font-size: 13px;
	resize: vertical;
}
.gg-quicknote__input:focus { outline: none; border-color: var(--gg-colour-primary); }
.gg-quicknote__foot { display: flex; align-items: center; gap: 10px; }
.gg-quicknote__said { font-size: 12px; color: var(--gg-on-surface-muted, #9fb0c5); }
.gg-card--img .gg-card__body {
	display: flex;
	flex-direction: column;
	gap: calc(var(--gg-space-base) * 0.75);
}
.gg-card__title {
	margin: 0;
	font-family: var(--gg-font-heading);
	font-size: 15px;
	font-weight: 700;
	line-height: 1.2;
	color: var(--gg-on-surface);
}
.gg-card__desc {
	margin: 0;
	font-size: 12.5px;
	line-height: 1.4;
	color: var(--gg-on-surface-muted);
	flex: 1;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.gg-card__pills {
	display: flex;
	flex-wrap: wrap;
	gap: 5px;
}
.gg-card__foot {
	position: relative;
	display: flex;
	align-items: stretch;
	justify-content: space-between;
	min-height: 36px;
	border-top: var(--gg-border-width) solid var(--gg-surface-border);
	background: var(--gg-surface-panel);
	color: var(--gg-on-surface);
	font-family: var(--gg-font-body);
	font-size: 10.5px;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}
.gg-card__foot-status,
.gg-card__foot-meta {
	display: flex;
	align-items: center;
	padding: 0 calc(var(--gg-space-base) * 1.5);
	min-width: 0;
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}
.gg-card__foot-meta {
	flex: 1 1 auto;
	justify-content: flex-end;
	font-weight: 500;
	letter-spacing: 0.04em;
	text-transform: none;
	opacity: 0.85;
	border-left: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-card__foot--ok     { background: color-mix(in srgb, var(--gg-status-ok) 24%, var(--gg-surface-panel)); }
.gg-card__foot--warn   { background: color-mix(in srgb, var(--gg-status-warn) 24%, var(--gg-surface-panel)); }
.gg-card__foot--danger { background: color-mix(in srgb, var(--gg-status-danger) 24%, var(--gg-surface-panel)); }
.gg-card__foot--info   { background: color-mix(in srgb, var(--gg-status-info) 24%, var(--gg-surface-panel)); }
.gg-card__foot--muted  { background: var(--gg-surface-panel); }

/* .gg-card--tile — THE CHOOSER CARD (2026-08-16, Sam: "a visually strong
   square card with a relevant featured image as the background… the option
   name prominently in bright white text over it").
   Not a second card. Same .gg-card--img shell, same photo box, same scrim
   element, same sharp corners — three things change and nothing else:
     · the photo box goes square instead of 16:9;
     · the scrim covers the whole photo instead of weighting the bottom, and
       goes heavier, because white text has to hold over ANY photograph rather
       than over the dark foot of one;
     · the body stops being a strip under the picture and becomes the label,
       centred on it.
   Earned its place: a modal that asks "which kind of thing do you want to
   add?" wants four equal doors. The standard card answers a different
   question — it describes something you already own, which is why its title
   sits in a text strip below the art. Asked to be a door, it reads as a list.
   The Roadmap's "Add to your roadmap" is the first caller; any screen that
   asks the same question inherits this instead of drawing its own.
   Opt in with 'class' => 'gg-card--tile' on gg_ui_simple_card() — the class
   passthrough that is already there, so no caller and no signature changes.
   Spacing is token-only on purpose (--gg-space-base): the 16 Aug gutter sweep
   existed because a dozen screens had each typed their own number. */
.gg-card--tile { position: relative; }
.gg-card--tile .gg-card__img { aspect-ratio: 1 / 1; }
/* Edge to edge rather than bottom-weighted — the label sits in the middle of
   the photo here, not under it. --gg-scrim is the same token the standard
   card's overlay reads, so a coach restyling the scrim moves both. */
.gg-card--tile .gg-card__img-overlay {
	background: linear-gradient(180deg,
		var(--gg-scrim) 0%,
		color-mix(in srgb, #000000 30%, var(--gg-scrim)) 100%);
}
/* The body leaves the flow and lands on the picture. Absolute against the
   card, so it covers the square whatever the photo's intrinsic size. */
.gg-card--tile .gg-card__body {
	position: absolute;
	inset: 0;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: calc(var(--gg-space-base) * 0.5);
	padding: calc(var(--gg-space-base) * 2);
	text-align: center;
	background: none;
}
/* Bright white, not --gg-on-surface: this text is over a photograph, and the
   surface ink is chosen to sit on a panel. The shadow is what keeps it legible
   over a pale patch, so the label never depends on the picture behaving. */
.gg-card--tile .gg-card__title {
	color: #ffffff;
	font-size: 18px;
	text-shadow: 0 1px 3px #000000, 0 2px 14px #000000;
}
.gg-card--tile .gg-card__desc {
	color: rgba(255,255,255,0.85);
	flex: none;
	text-shadow: 0 1px 3px #000000;
}
/* THE SLOT A TILE DOES NOT HAVE. The body is absolute over the photo and the
   foot is a SIBLING after it, so a caller passing 'foot' would get a 36px strip
   below the square with the transparent body sitting on top of it: the card
   stops being square and the label drifts off its centre. A tile is a door, and
   a door has no status line - hidden here so the mistake shows as nothing
   rather than as a broken card. */
.gg-card--tile .gg-card__foot { display: none; }

/* ── Card grid — responsive layout for .gg-card--img tiles (backlog #4
 * shares this track once gg-course-grid re-points at it) ──
 * Column track matches the PM board's own grid (donor: .gg-rv2-grid,
 * admin-roadmap-v2.css:786 — auto-fill, not auto-fit, so a single result
 * stays one column wide instead of stretching full width). Pairs with the
 * standard .gg-view-toggle: no new JS — the gg-list-controls plugin
 * (already enqueued sitewide) toggles is-view-list/is-view-grid on the
 * wrapper it targets; the CSS below reacts to is-view-list only, since the
 * unmodified base state already IS the grid. */
.gg-card-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(var(--gg-card-track), 1fr));
	gap: calc(var(--gg-space-base) * 2.5);
}
.gg-card-grid.is-view-list {
	display: flex;
	flex-direction: column;
	gap: calc(var(--gg-space-base) * 1.25);
}
.gg-card-grid.is-view-list .gg-card--img {
	flex-direction: row;
	align-items: stretch;
}
.gg-card-grid.is-view-list .gg-card__img {
	width: 120px;
	aspect-ratio: auto;
}
.gg-card-grid.is-view-list .gg-card--img .gg-card__body {
	flex: 1 1 auto;
	min-width: 0;
}
.gg-card-grid.is-view-list .gg-card__desc { -webkit-line-clamp: 1; }
.gg-card-grid.is-view-list .gg-card__foot {
	flex: none;
	flex-direction: column;
	width: 150px;
	border-top: none;
	border-left: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-card-grid.is-view-list .gg-card__foot-meta {
	border-left: none;
	border-top: var(--gg-border-width) solid var(--gg-surface-border);
	justify-content: center;
}
.gg-card-grid__empty {
	grid-column: 1 / -1;
	padding: calc(var(--gg-space-base) * 2.5);
	text-align: center;
	color: var(--gg-on-surface-muted);
	font-style: italic;
}

/* ── Persona / advisor grid (donor: gg-my-team's .gg-team-grid/.gg-team-card,
 * plugins/gg-my-team/inc/page.php:337-410 + :608-638 — READ-ONLY, backlog
 * #5, audited 2026-07-15/16) ──
 * Deliberately its OWN visual language, not built on .gg-card above: round
 * avatar, blurred/translucent glass surface, generous centred padding —
 * mirrors the donor's exact look onto the --gg-persona-* and --gg-convo-*
 * tokens instead of new hardcoded hex (see the :root block above for why
 * those two families split). Sharp corners still apply everywhere except the avatar
 * circle, matching the donor (its own only rounded element).
 *
 * Markup (render via gg_ui_persona_grid()/gg_ui_persona_card() — don't
 * hand-roll this):
 *   <div class="gg-persona-grid">
 *     <div class="gg-persona-card" data-slug="cfo" style="border-top-color:#1f8a70">
 *       <div class="gg-persona-avatar"><svg>…</svg></div>
 *       <h4 class="gg-persona-name">CFO</h4>
 *       <p class="gg-persona-role">Chief Financial Officer</p>
 *       <p class="gg-persona-blurb">…</p>
 *       <button type="button" class="gg-persona-cta">
 *         <span class="gg-persona-cta-label">Chat with CFO</span>
 *         <svg class="gg-persona-cta-icon">…</svg>
 *       </button>
 *     </div>
 *     …
 *   </div>
 * $tag on the CTA is <a> when a href is given, else <button type="button">
 * for a page's own click handler (donor: My Team's data-slug wiring into
 * the LCP). */
.gg-persona-grid {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 22px;
	text-align: center;
}
@media (max-width: 1000px) { .gg-persona-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px)  { .gg-persona-grid { grid-template-columns: 1fr; } }

.gg-persona-card {
	background: var(--gg-persona-glass);
	backdrop-filter: blur(var(--gg-persona-blur));
	-webkit-backdrop-filter: blur(var(--gg-persona-blur));
	border: 1px solid var(--gg-persona-border);
	border-top: 3px solid var(--gg-persona-border); /* colour overridden per-card via style="border-top-color:…" — the persona's own tint */
	padding: 30px 24px 24px;
	display: flex;
	flex-direction: column;
	align-items: center;
	min-height: 330px;
	position: relative;
	color: var(--gg-convo-text);
	transition: border-color var(--gg-transition-speed) var(--gg-transition-easing),
	            background var(--gg-transition-speed) var(--gg-transition-easing),
	            transform var(--gg-transition-speed) var(--gg-transition-easing);
}
/* Sharp-corner enforcement, same recipe as .gg-card — the avatar below
 * opts back into 50% since that's a deliberate rounded exception, matching
 * the donor. */
.gg-persona-card,
.gg-persona-card::before,
.gg-persona-card::after { border-radius: 0 !important; }
.gg-persona-card:hover {
	border-color: var(--gg-persona-border-hover);
	background: var(--gg-persona-glass-hover);
	transform: translateY(-2px);
}
.gg-persona-avatar {
	width: 104px;
	height: 104px;
	border-radius: 50%;
	overflow: hidden;
	margin-bottom: 18px;
	background: var(--gg-convo-bg);
	box-shadow: 0 8px 22px rgba(0,0,0,0.4), inset 0 -2px 6px rgba(0,0,0,0.25), inset 0 2px 4px rgba(255,255,255,0.15);
}
.gg-persona-avatar svg,
.gg-persona-avatar img {
	width: 100%;
	height: 100%;
	display: block;
	object-fit: cover;
}
.gg-persona-name {
	font-family: var(--gg-font-heading);
	font-size: 20px;
	font-weight: 500;
	letter-spacing: -0.01em;
	color: #fff;
	margin: 0 0 4px;
}
.gg-persona-role {
	font-size: 11px;
	letter-spacing: 0.16em;
	text-transform: uppercase;
	font-weight: 600;
	margin: 0 0 14px;
	color: var(--gg-convo-muted);
}
.gg-persona-blurb {
	font-size: 14px;
	font-weight: 400;
	line-height: 1.55;
	color: var(--gg-convo-text-soft);
	margin: 0 0 20px;
	flex-grow: 1;
}
.gg-persona-cta {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	color: var(--gg-convo-text);
	font-family: var(--gg-font-body);
	font-size: 13px;
	font-weight: 500;
	text-decoration: none;
	background: rgba(255,255,255,0.06);
	border: 0;
	border-radius: 0;
	padding: 10px 16px;
	cursor: pointer;
	transition: background var(--gg-transition-speed) var(--gg-transition-easing), color var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-persona-cta:hover { background: rgba(255,255,255,0.12); color: #fff; }
.gg-persona-cta-icon { width: 14px; height: 14px; flex-shrink: 0; }
.gg-persona-grid__empty {
	grid-column: 1 / -1;
	padding: calc(var(--gg-space-base) * 2.5);
	text-align: center;
	color: var(--gg-convo-muted);
	font-style: italic;
}

/* ── Modal (donor: gg-my-team.php structure + platform conventions) ──
 * Markup:  <div class="gg-modal" hidden>
 *            <div class="gg-modal__backdrop"></div>
 *            <div class="gg-modal__panel" role="dialog" aria-modal="true">
 *              <div class="gg-modal__head"><h3>…</h3>
 *                <button class="gg-modal__close">&times;</button></div>
 *              <div class="gg-modal__body">…</div>
 *              <div class="gg-modal__foot">…buttons…</div>
 *            </div>
 *          </div>
 * JS toggles the [hidden] attribute. */
.gg-modal {
	position: fixed;
	inset: 0;
	z-index: 99990;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: calc(var(--gg-space-base) * 3);
}
.gg-modal[hidden] { display: none !important; }
.gg-modal__backdrop {
	position: absolute;
	inset: 0;
	background: var(--gg-scrim);
	backdrop-filter: blur(2px);
	-webkit-backdrop-filter: blur(2px);
}
.gg-modal__panel {
	position: relative;
	display: flex;
	flex-direction: column;
	width: min(560px, 92vw);
	max-height: 86vh;
	background: var(--gg-surface-panel);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	box-shadow: var(--gg-shadow-lg);
	color: var(--gg-on-surface);
	font-family: var(--gg-font-body);
	border-radius: 0 !important;
}
.gg-modal__panel--wide { width: min(720px, 92vw); }
.gg-modal__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--gg-space-base);
	padding: calc(var(--gg-space-base) * 2) calc(var(--gg-space-base) * 3);
	border-bottom: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-modal__head h2,
.gg-modal__head h3 {
	margin: 0;
	font-family: var(--gg-font-heading);
	font-size: 18px;
	font-weight: 500;
	color: var(--gg-on-surface);
	line-height: 1.3;
}
.gg-modal__body {
	flex: 1;
	overflow: auto;
	padding: calc(var(--gg-space-base) * 3);
}
.gg-modal__foot {
	display: flex;
	gap: calc(var(--gg-space-base) * 1.25);
	justify-content: flex-end;
	padding: calc(var(--gg-space-base) * 2) calc(var(--gg-space-base) * 3);
	border-top: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-modal__close {
	width: 30px;
	height: 30px;
	flex: none;
	display: flex;
	align-items: center;
	justify-content: center;
	background: transparent;
	border: var(--gg-border-width) solid var(--gg-surface-border);
	border-radius: 0;
	color: var(--gg-on-surface-muted);
	font-size: 18px;
	line-height: 1;
	cursor: pointer;
	transition: color var(--gg-transition-speed) var(--gg-transition-easing),
	            background var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-modal__close:hover {
	color: var(--gg-on-surface);
	background: color-mix(in srgb, currentColor 10%, transparent);
}

/* ── Page header (donors: gg-mb-page-title / gg-rv2-detail-title) ──
 * h1.gg-page-title ties with WP core's `.wrap h1` on specificity and wins
 * on cascade order (admin_head prints after core styles) — no !important. */
.gg-page-header {
	text-align: center;
	margin: 0 auto calc(var(--gg-space-base) * 3.5);
	padding-top: calc(var(--gg-space-base) * 3.5);
	max-width: 860px;
}
/* GG + Business Accelerator logo lockup (donor: .gg-rv2-brandstack,
 * gg-roadmap-v2/src/AdminRoadmapV2.php's Offer Manager hero — same markup,
 * same rules, now the shared page-header logo everywhere). */
.gg-page-brandstack {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 9px;
	margin: 0 0 20px;
}
.gg-page-brandstack img {
	width: auto;
	display: block;
}
/* SMALLER, 16 Aug 2026 (Sam): the lockup was taking the top of every grid page
 * before the page's own title got a look in. Dropped from 56/26 to 40/18 —
 * about three quarters — with the gap brought in to match so the two marks
 * still read as one stack. Changed here, in the shared lockup, so every page
 * that calls gg_page_logo() inherits it rather than one page being patched. */
.gg-page-brandstack-gg {
	height: 40px;
	filter: drop-shadow(0 2px 6px rgba(0,0,0,.45));
}
.gg-page-brandstack-ba {
	height: 18px;
	filter: brightness(0) invert(1) drop-shadow(0 2px 5px rgba(0,0,0,.5));
}
.gg-page-eyebrow {
	font-size: 12px;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	font-weight: 600;
	color: var(--gg-on-surface-muted);
	margin: 0 0 14px;
}
.gg-page-title,
h1.gg-page-title,
.gg-heading-1,
h1.gg-heading-1 {
	font-family: var(--gg-font-heading);
	font-size: clamp(32px, 4.5vw, 42px);
	font-weight: 500;
	line-height: 1.15;
	letter-spacing: -0.01em;
	color: var(--gg-on-surface);
	margin: 0 0 10px;
	padding: 0;
}
.gg-page-subtitle {
	font-size: 13.5px;
	line-height: 1.55;
	color: var(--gg-on-surface-muted);
	max-width: 640px;
	margin: 0 auto calc(var(--gg-space-base) * 3);
}

/* Primary action row — THE place a screen's main action sits, so it is in the
 * same position on every screen of a type (Goal 2). Centred under the
 * subtitle, matching the signed-off Project Manager header (donor:
 * .gg-rv2-offers-northstar, gg-roadmap-v2 — same rules, now shared so a
 * screen can no longer put its main button somewhere of its own choosing).
 * Markup: render via gg_page_header()'s `actions` arg — don't hand-roll. */
.gg-page-actions {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 14px;
	width: 100%;
	margin: 0 0 calc(var(--gg-space-base) * 3);
}
@media (max-width: 640px) {
	.gg-page-actions {
		flex-direction: column;
		gap: 8px;
	}
	.gg-page-actions > * {
		width: 100%;
	}
}

/* ── Heading levels (added 2026-08-02) ──
 * The scale below the page title. Until now the platform only had a shared
 * class for the h1 (.gg-page-title) and for the heading on a card
 * (.gg-card__title) — so every heading BETWEEN those two was written where it
 * was used. The Project page carried five of them, three with the size typed
 * into a style="" attribute (15px, 15px, 17px) and two with no class at all
 * leaning on the browser default; the Email Outreach page had a third set
 * again. Three sizes for two jobs, and none of them movable from one place.
 *
 * Three named levels, and nothing else may set a heading's size:
 *   .gg-heading-1  the page's one h1 — the same rule as .gg-page-title above,
 *                  grouped rather than copied so the two can never drift.
 *   .gg-heading-2  a section of the page: the grid of Assets, the panel that
 *                  says what the build produced.
 *   .gg-heading-3  a group inside a section: "On this page", "Linked".
 *
 * Sizes are px, not the --gg-hN-size tokens. Those tokens are the COACH's
 * typography for the pages they publish (2rem/1.5rem/1.25rem defaults, set in
 * My Brand); platform chrome is a fixed reading size, the same reason
 * .gg-page-eyebrow and .gg-page-subtitle are px. .gg-heading-3 is 15px/700 to
 * match .gg-card__title — a heading on a card and a heading in a panel are the
 * same level of thing.
 *
 * Rhythm belongs here too, so a page needs no margin of its own: air above a
 * heading that follows content, none on one that opens its container. */
.gg-heading-2,
h1.gg-heading-2, h2.gg-heading-2, h3.gg-heading-2, h4.gg-heading-2 {
	font-family: var(--gg-font-heading);
	font-size: 17px;
	font-weight: 700;
	line-height: 1.25;
	letter-spacing: -0.005em;
	color: var(--gg-on-surface);
	margin: 26px 0 10px;
	padding: 0;
}
.gg-heading-3,
h1.gg-heading-3, h2.gg-heading-3, h3.gg-heading-3, h4.gg-heading-3 {
	font-family: var(--gg-font-heading);
	font-size: 15px;
	font-weight: 700;
	line-height: 1.25;
	color: var(--gg-on-surface);
	margin: 22px 0 8px;
	padding: 0;
}
.gg-heading-2:first-child,
.gg-heading-3:first-child { margin-top: 0; }

/* ── Edge handle (donor: gg-rcp's .gg-chat-tab / gg-rv2-lib-handle) ──
 * THE uniform open/close handle for every side panel on the platform.
 * Defined 2026-06-11 so the LCP "Assistant" handle (right edge) and the
 * Project Manager "Projects" library handle (left edge) share one rule.
 *
 * Standard:  fixed, vertically centred on the screen edge, navy #0a1628,
 *            1px soft border (edge side open), sharp corners, 18px icon
 *            above an 11px uppercase vertical label, 0.18em tracking,
 *            padding 14px 10px (≈38px wide), hover #122340 + 2px grow
 *            toward the content.
 *
 * Markup:    <button class="gg-edge-handle gg-edge-handle--right">
 *              <svg …18×18…></svg>
 *              <span class="gg-edge-handle__label">Assistant</span>
 *            </button>
 *
 * The owning panel still positions/hides it (z-index bumps, display:none
 * while its panel is open) via its own page class — visual identity only
 * lives here. */
.gg-edge-handle {
	position: fixed;
	top: 50%;
	transform: translateY(-50%);
	z-index: 99998;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 8px;
	appearance: none;
	-webkit-appearance: none;
	background: #0a1628;
	color: #ffffff;
	border: var(--gg-border-width) solid rgba(255,255,255,0.18);
	border-radius: 0;
	cursor: pointer;
	font-family: inherit;
	font-size: 12px;
	font-weight: 500;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	transition: background 0.15s ease, padding 0.15s ease;
}
.gg-edge-handle svg {
	width: 18px;
	height: 18px;
	color: #E8B44F;          /* amber icon — matches platform accent */
	flex-shrink: 0;
}
.gg-edge-handle__label {
	writing-mode: vertical-rl;
	font-size: 11px;
	letter-spacing: 0.18em;
	white-space: nowrap;
}
.gg-edge-handle:hover,
.gg-edge-handle:focus-visible {
	background: #122340;
	outline: none;
}
/* Right-edge variant (e.g. the Assistant chat handle) */
.gg-edge-handle--right {
	right: 0;
	border-right: none;
	padding: 14px 10px 14px 12px;
	box-shadow: -2px 0 0 rgba(0,0,0,0.45);
}
.gg-edge-handle--right .gg-edge-handle__label { transform: rotate(180deg); }
.gg-edge-handle--right:hover,
.gg-edge-handle--right:focus-visible { padding-right: 14px; }
/* Left-edge variant (e.g. the Projects library handle) */
.gg-edge-handle--left {
	left: 0;
	border-left: none;
	padding: 14px 12px 14px 10px;
	box-shadow: 2px 0 0 rgba(0,0,0,0.45);
}
.gg-edge-handle--left:hover,
.gg-edge-handle--left:focus-visible { padding-left: 14px; }

/* ON A PHONE IT STOPS SITTING ON THE READING COLUMN (2026-08-09).
   The handle is fixed to the middle of a screen edge, which is free space on a
   wide screen and is the text on a narrow one: at 390px it is a 41x134px navy
   strip parked over the content, and it was clipping real words on every
   screen at once — "Co-pilot summary" read "-pilot summary", "Clients" read
   "ients", "OWNER" read "WNER". One fixed element, every screen, which is
   exactly the kind of fault that belongs in the shared class and nowhere else.

   Below the phone breakpoint it becomes a compact corner button: the icon
   only, no vertical label, docked to the bottom of its own edge so it sits
   beside the assistant bubble in the opposite corner rather than across the
   middle of the page. 44px is the tap target, sharp corners are kept.

   `display` is deliberately NOT set here — the owning panel hides this button
   while its panel is open (see the note above), and re-declaring display in a
   media query would override that and strand an open panel with its own
   handle still showing. */
@media (max-width: 782px) {
	.gg-edge-handle {
		top: auto;
		bottom: 12px;
		transform: none;
		min-width: 44px;
		min-height: 44px;
		justify-content: center;
		gap: 0;
		padding: 12px;
	}
	.gg-edge-handle__label { display: none; }
	.gg-edge-handle--right {
		padding: 12px;
		box-shadow: -2px 0 0 rgba(0,0,0,0.45);
	}
	.gg-edge-handle--right:hover,
	.gg-edge-handle--right:focus-visible { padding: 12px; }
	.gg-edge-handle--left {
		padding: 12px;
		box-shadow: 2px 0 0 rgba(0,0,0,0.45);
	}
	.gg-edge-handle--left:hover,
	.gg-edge-handle--left:focus-visible { padding: 12px; }
}

/* ── Progress bar (donor: gg-rv2-card-progress on the Project Manager) ──
 * Thin glowing bar. Absolute-pin it inside a card foot yourself, or use
 * it inline as a standalone block. */
.gg-progress {
	height: 4px;
	background: color-mix(in srgb, currentColor 8%, transparent);
	overflow: hidden;
	pointer-events: none;
	border-radius: 0;
}
.gg-progress__fill {
	height: 100%;
	background: linear-gradient(90deg, #2EA862 0%, #41C97A 100%);
	box-shadow: 0 0 6px rgba(65, 201, 122, 0.35);
	transition: width 0.4s ease;
	border-radius: 0;
}
.gg-progress--done .gg-progress__fill {
	background: linear-gradient(90deg, #41C97A 0%, #6EE39C 100%);
	box-shadow: 0 0 8px rgba(110, 227, 156, 0.5);
}

/* ── Tabs / segmented switch (donor: gg-rv2-feed-switch on the Project
 *    Manager — the "Project Board | Notifications" toggle Sam signed off).
 *    A segmented control that flips between named panes. Styles here;
 *    behaviour lives in the gg-tabs plugin (opt in with data-gg-tabs).
 *    Sharp corners; active-segment colour is brand-adaptive via
 *    --gg-tabs-active (falls back to the donor navy).
 *
 *    Sam, 2026-07-18: the donor's zero-gap/zero-border segments read fine
 *    with one live button (the Inbox's own "Notifications" toggle), but on
 *    a real two-segment bar (Client Assistant's Clients / Check-in Emails)
 *    the inactive segment is fully transparent and butts straight against
 *    its neighbour — no visible seam, ambiguous where one ends and the
 *    next begins. Fix: a real gap between segments (track colour shows
 *    through) + a static per-button border so EVERY segment — active or
 *    not — reads as its own bounded box, plus more internal breathing
 *    room. Still the same family: sharp corners, same tokens, same track. */
.gg-tabs {
	display: inline-flex;
	gap: 6px;
	padding: 4px;
	background: rgba(0,0,0,0.40);
	border: 1px solid var(--gg-surface-border, rgba(255,255,255,0.14));
	box-shadow: 0 2px 6px rgba(0,0,0,0.20);
	border-radius: 0;
}
/* Full-width variant — bar stretches, buttons share the row evenly. */
.gg-tabs--block { display: flex; width: 100%; }
/* ON A PHONE THE STRIP SCROLLS INSTEAD OF BEING CUT OFF (2026-08-09).
   The bar is inline-flex/nowrap with overflow visible, which is right on a wide
   screen and silently loses tabs on a narrow one: GG Email's strips measured
   770px and 964px inside a 378px column, so an ancestor's overflow:hidden ate
   the last two of four and the last three of five. They were not merely hard to
   reach, they were gone, with no scrollbar and no wrap to hint at it.
   Scrolling the strip is chosen over wrapping it deliberately — wrapping makes
   the bar two or three rows tall and pushes the page content down on the screen
   that can least afford it, and it breaks the one-row segmented-control shape
   the component exists to be. The buttons must not shrink or they would squash
   to fit instead of overflowing, which is the same bug with narrower tabs. */
@media (max-width: 782px) {
	.gg-tabs {
		display: flex;
		max-width: 100%;
		overflow-x: auto;
		overscroll-behavior-x: contain;
		-webkit-overflow-scrolling: touch;
	}
	.gg-tabs__btn { flex-shrink: 0; }
}
.gg-tabs__btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	flex: 1 1 0;
	min-width: 148px;
	padding: 10px 18px;
	background: transparent;
	border: 1px solid rgba(255,255,255,0.10);
	color: var(--gg-on-surface-muted, rgba(255,255,255,0.70));
	font-family: inherit;
	font-size: 14px;
	font-weight: 600;
	letter-spacing: 0.02em;
	line-height: 1;
	/* A TAB IS ONE LINE (2026-08-04). line-height:1 is set for a single line,
	   so a two-word label that wrapped ("Check-in Emails") made its whole row
	   taller than every other tab row on the platform and stacked the words on
	   top of each other. The button grows sideways instead — flex:1 1 0 already
	   shares the space, and the row is inline-flex so it only ever takes the
	   width it needs. */
	white-space: nowrap;
	cursor: pointer;
	border-radius: 0;
	transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.gg-tabs__btn:hover {
	color: var(--gg-on-surface, #fff);
	background: rgba(255,255,255,0.08);
	border-color: rgba(255,255,255,0.18);
}
.gg-tabs__btn:focus-visible {
	outline: 2px solid var(--gg-colour-accent, #E8B44F);
	outline-offset: 1px;
}
.gg-tabs__btn.is-active {
	background: var(--gg-tabs-active, #1f3a5f);
	color: #fff;
	border-color: rgba(255,255,255,0.28);
	box-shadow:
		inset 0 0 0 1px rgba(255,255,255,0.14),
		inset 0 1px 0 rgba(255,255,255,0.10),
		inset 0 -2px 6px rgba(0,0,0,0.30);
}
/* Count badge (unread / queued) sitting on a tab button. Fixed gold/near-
 * black pairing — deliberately NOT --gg-colour-accent. That variable is the
 * COACH's brand colour (white-label, can be anything, including a dark
 * navy/purple) and this badge previously inherited it as the FILL while
 * keeping a fixed dark TEXT colour — on a dark-branded coach the two sat
 * within a few percent of each other in luminance and the number vanished
 * (Sam, 2026-07-18, Check-in Emails "6" unreadable). Matches the donor's own
 * badge (gg-rv2-feed-badge on the Inbox), which is fixed for the same
 * reason: a count badge must read on every brand, not just the neutral one
 * it was designed against. */
.gg-tabs__badge {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: 20px;
	height: 20px;
	padding: 0 6px;
	background: #E8B44F;
	color: #1a1205;
	font-size: 12px;
	font-weight: 800;
	line-height: 1;
	border-radius: 0;
}
.gg-tabs__badge[hidden] { display: none; }

/* ── Segmented chooser (.gg-seg) — a choice between peers, drawn as ONE
 *    control (2026-08-23).
 *
 *    NOT .gg-tabs above it. Tabs switch between views of the same thing and
 *    are drawn as separated pills; this is a FIELD — the answer is stored,
 *    and the segments butt together so the row reads as a single choice
 *    rather than a row of buttons.
 *
 *    Lifted in from gg-social-v2's post page, where it was written for the
 *    Post type row and lived in that plugin's own inline stylesheet — so the
 *    second page that wanted the same control had to copy it. This is that
 *    control, once. (gg-social-v2 still carries its own copy and should be
 *    moved onto this.)
 *
 *    Markup: gg_ui_segmented() in inc/form-controls.php.
 *    Behaviour: window.GGSeg in assets/gg-components.js.
 *
 *    THE BORDERS ARE DRAWN BY THE CELLS, not by the box. Six segments wrap to
 *    two or three rows on a narrow screen, and a box that draws its own right
 *    and bottom edge leaves a double line wherever a row ends. The container
 *    draws top and left, every cell draws right and bottom, so the control
 *    closes itself correctly at any number of rows. */
.gg-seg-row { display: flex; flex-direction: column; gap: 5px; margin: 0 0 6px; }
.gg-seg-row__lbl {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
	font-family: var(--gg-font-h, inherit);
	font-size: 13px;
	font-weight: 700;
	line-height: 1.2;
	color: var(--gg-on-surface, #fff);
	/* A field name, not the shared component's small-caps gold label. Said
	   explicitly so neither inherits from the other. */
	letter-spacing: 0;
	text-transform: none;
}
/* PLAIN TEXT, NOT A GLYPH (Sam, 19 Aug 2026). The heading face draws an
   asterisk as a filled six-pointed star, which reads as a decoration beside a
   field name rather than the thing every form has always used to mean
   required. */
.gg-seg-row__req,
.gg-ff__req {
	font-family: var(--gg-font-b, system-ui, sans-serif);
	color: var(--gg-accent-gold, #E8B44F);
	font-size: 17px;
	line-height: 1;
	font-weight: 700;
	transform: translateY(3px);
}
.gg-seg-row .gg-bv2-field-status { margin-left: auto; font-size: 12px; font-weight: 400; }
.gg-seg {
	display: grid;
	grid-template-columns: repeat(var(--gg-seg-cols, 5), minmax(0, 1fr));
	border-top: 1px solid var(--gg-surface-border, rgba(255,255,255,0.13));
	border-left: 1px solid var(--gg-surface-border, rgba(255,255,255,0.13));
}
.gg-seg > button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	min-width: 0;
	cursor: pointer;
	background: transparent;
	border: 0;
	border-right: 1px solid var(--gg-surface-border, rgba(255,255,255,0.13));
	border-bottom: 1px solid var(--gg-surface-border, rgba(255,255,255,0.13));
	color: var(--gg-on-surface-muted, #9fb0c5);
	font-family: var(--gg-font-h, inherit);
	font-size: 13.5px;
	font-weight: 700;
	/* A LABEL MAY RUN TO TWO LINES. Six segments across a reading column give
	   each about 130px, and "Recurring Campaign" does not fit on one — so it
	   wraps rather than being clipped or shrinking the type. */
	line-height: 1.25;
	text-align: center;
	padding: 12px 8px;
	border-radius: 0; /* design-guard-ok: sharp corners are the brand */
	transition: background 0.12s, color 0.12s;
}
.gg-seg > button:hover,
.gg-seg > button:focus-visible {
	color: var(--gg-on-surface, #fff);
	background: rgba(255,255,255,0.04);
	outline: none;
}
.gg-seg > button[aria-pressed="true"] {
	background: var(--gg-secondary);
	color: var(--gg-primary);
}
/* The mark carries the explanation; the segment itself stays quiet (Sam,
   10 Aug 2026). It is drawn small and low-contrast so it reads as an aside on
   the label rather than a second thing to press. */
.gg-seg__tip {
	flex: 0 0 auto;
	display: inline-grid;
	place-items: center;
	width: 15px;
	height: 15px;
	margin-left: 0;   /* the segment lays this out itself */
	opacity: 0.6;
	cursor: help;
}
.gg-seg__tip svg { width: 13px; height: 13px; }
.gg-seg > button:hover .gg-seg__tip,
.gg-seg__tip:hover { opacity: 1; }
@media (max-width: 782px) {
	/* TWO ACROSS ON A PHONE. Six segments at a readable size do not fit one
	   row on a narrow screen, and letting them reflow leaves an orphan on the
	   last line sitting under the floating assistant buttons — a touch target
	   you cannot reliably hit, on a control that looks broken. Even columns
	   instead, so nothing is orphaned because nothing wraps. */
	.gg-seg { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.gg-seg > button { padding: 12px 6px; font-size: 12.5px; }
}

/* ── Repeat list (.gg-rl) — as many of a thing as the coach needs (2026-08-24).
 *
 *    Every form here asked a fixed set of questions until now, so a sequence
 *    was three emails because somebody wrote three into a recipe. This is the
 *    control for "however many of these you need".
 *
 *    An entry is a bounded card INSIDE the creation panel, one step further in
 *    than the panel itself, so the nesting reads: the page, the thing you are
 *    making, the parts of it. Markup: gg_ui_repeat_list() in inc/form-controls.php.
 *    Behaviour: window.GGRepeat in assets/gg-seg.js. */
.gg-rl { display: flex; flex-direction: column; gap: 10px; margin: 0; }
.gg-rl__lbl {
	display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
	font-family: var(--ggb-font-head, inherit);
	font-size: 13px; font-weight: 700; line-height: 1.2;
	color: var(--ggb-text, #fff); letter-spacing: 0; text-transform: none;
}
.gg-rl__lbl .gg-bv2-field-status { margin-left: auto; font-size: 12px; font-weight: 400; }
.gg-rl__note {
	font: 400 13px/1.55 var(--ggb-font-body, inherit);
	color: var(--ggb-text-soft, rgba(255,255,255,0.62));
	margin: -4px 0 2px; max-width: 66ch;
}
.gg-rl__rows { display: flex; flex-direction: column; gap: 10px; }
/* A LITTLE MORE ROOM INSIDE AN ENTRY (2026-08-24, Sam: "inside the email 1 -
   the spacing is a bit tight ... make it a little more relaxed, just a tiny
   bit"). An email's subject and its message are two separate things to write,
   and at ten pixels apart they read as one block. Nudged, not redesigned. */
.gg-rl__row {
	border: 1px solid var(--ggb-line, rgba(255,255,255,0.14));
	background: rgba(255,255,255,0.025);
	padding: 16px 18px 18px;
	display: flex; flex-direction: column; gap: 14px;
}
.gg-rl__head { display: flex; align-items: center; gap: 10px; }
.gg-rl__n {
	font: 700 11px/1 var(--ggb-font-head, inherit);
	letter-spacing: 0.1em; text-transform: uppercase;
	color: var(--ggb-text-soft, rgba(255,255,255,0.6));
}
/* Quiet until wanted. Remove is not the thing a coach came here to do, and a
   button as loud as the fields invites the accident it describes. */
.gg-rl__rm {
	margin-left: auto; cursor: pointer;
	background: transparent; border: 0; padding: 2px 4px;
	font: 600 11.5px/1 var(--ggb-font-body, inherit);
	color: var(--ggb-text-soft, rgba(255,255,255,0.45));
	border-radius: 0;
}
.gg-rl__rm:hover, .gg-rl__rm:focus-visible { color: #f87171; outline: none; }
.gg-rl__rm[hidden] { display: none; }
.gg-rl__first {
	font: 400 12.5px/1.5 var(--ggb-font-body, inherit);
	color: var(--ggb-text-soft, rgba(255,255,255,0.55));
	margin: -2px 0 0;
}
.gg-rl__first[hidden] { display: none; }
.gg-rl__f { display: flex; flex-direction: column; gap: 6px; }
.gg-rl__f[hidden] { display: none; }
.gg-rl__flbl {
	font: 700 11.5px/1.2 var(--ggb-font-head, inherit);
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
	letter-spacing: 0; text-transform: none;
}
/* ELEMENT AND CLASS, NOT CLASS ALONE. WP admin styles input[type=text] by
   element, which out-specifies a bare class - so the subject boxes came out
   white on a dark panel while the textareas beside them were right. */
.gg-rl__in,
input.gg-rl__in,
input[type="text"].gg-rl__in,
input[type="number"].gg-rl__in,
textarea.gg-rl__in {
	width: 100%; box-sizing: border-box;
	background: var(--ggb-surface, #0F2536);
	border: 1px solid var(--ggb-line, rgba(255,255,255,0.13));
	color: var(--ggb-text, #DDDFE2);
	font-family: var(--ggb-font-body, inherit);
	font-size: 14px; line-height: 1.5;
	padding: 9px 11px;
	border-radius: 0; /* design-guard-ok: sharp corners are the brand */
}
.gg-rl__in:focus { border-color: var(--ggb-accent-2, #4CAF7D); outline: none; }
.gg-rl__in::placeholder { color: var(--ggb-text-soft, rgba(255,255,255,0.35)); }
textarea.gg-rl__in { resize: vertical; min-height: 84px; }
/* A number with its unit beside it, so "2" is never a number on its own. */
.gg-rl__withsuffix { display: inline-flex; align-items: center; gap: 8px; }
.gg-rl__withsuffix .gg-rl__in { width: 6.5em; }
.gg-rl__suffix {
	font: 400 13px/1 var(--ggb-font-body, inherit);
	color: var(--ggb-text-soft, rgba(255,255,255,0.62));
}
.gg-rl__add {
	align-self: flex-start; cursor: pointer;
	background: transparent;
	border: 1px dashed var(--ggb-line, rgba(255,255,255,0.22));
	color: var(--ggb-text-soft, rgba(255,255,255,0.72));
	font: 700 12.5px/1 var(--ggb-font-head, inherit);
	padding: 10px 14px;
	border-radius: 0; /* design-guard-ok: sharp corners are the brand */
	display: inline-flex; align-items: center; gap: 7px;
}
.gg-rl__add:hover, .gg-rl__add:focus-visible {
	border-style: solid;
	border-color: var(--ggb-accent-2, #4CAF7D);
	color: var(--ggb-text, #fff);
	outline: none;
}

/* ── Form field (.gg-ff) — the flat row: a name, then the box (2026-08-23).
 *
 *    The shared field box is built for an interview: a question, prose under
 *    it, a disclosure, an approve control. A form page wants none of that.
 *    Same field, interview taken off it. Lifted in from gg-social-v2's post
 *    page for the same reason as .gg-seg above.
 *
 *    Markup: gg_ui_form_field() in inc/form-controls.php. Saving is the
 *    shared binder's — the row wears .gg-bv2-field and its control wears
 *    .gg-bv2-field-input, which is the whole of that contract. */
.gg-ff { display: flex; flex-direction: column; gap: 5px; margin: 0; }
.gg-ff[hidden] { display: none; }
.gg-ff + .gg-ff { margin-top: 26px; }
.gg-ff__lbl {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
	font-family: var(--gg-font-h, inherit);
	font-size: 13px;
	font-weight: 700;
	line-height: 1.2;
	color: var(--gg-on-surface, #fff);
	letter-spacing: 0;
	text-transform: none;
}
.gg-ff .gg-bv2-field-status { margin-left: auto; font-size: 12px; font-weight: 400; }
/* THE ASSISTANT CHIP SITS BESIDE THE NAME. The shared button is absolutely
   positioned for a field box, which on a wide form floats it to the far right
   and makes a coach travel to find the help for the thing under their cursor. */
.gg-ff .gg-bv2-field-help-btn {
	position: static;
	margin: 0;
	flex: 0 0 auto;
	height: auto;
	width: auto;
	padding: 3px 9px;
	background: transparent;
	border: 1px solid var(--gg-surface-border, rgba(255,255,255,0.13));
	color: var(--gg-on-surface-muted, #9FA4AB);
	font-size: 11px;
	font-weight: 700;
	line-height: 1.3;
	border-radius: 0; /* design-guard-ok: sharp corners are the brand */
}
.gg-ff .gg-bv2-field-help-btn:hover,
.gg-ff .gg-bv2-field-help-btn:focus-visible {
	border-color: var(--gg-secondary);
	color: var(--gg-secondary);
	outline: none;
}
/* The shared button hides its words and shows an icon. Here there is room
   beside the name, and words say what a speech bubble only implies. */
.gg-ff .gg-bv2-field-help-btn svg { display: none; }
.gg-ff .gg-bv2-field-help-tip {
	position: static;
	display: inline;
	opacity: 1;
	visibility: visible;
	transform: none;
	background: none;
	border: 0;
	padding: 0;
	white-space: nowrap;
	font-size: 11px;
	color: inherit;
	box-shadow: none;
}
.gg-ff__in,
textarea.gg-ff__in,
select.gg-ff__in,
input.gg-ff__in {
	width: 100%;
	max-width: none;
	box-sizing: border-box;
	background: var(--gg-surface, #0F2536);
	border: 1px solid var(--gg-surface-border, rgba(255,255,255,0.13));
	color: var(--gg-on-surface, #DDDFE2);
	font-family: var(--gg-font-b, inherit);
	font-size: 14.5px;
	line-height: 1.5;
	padding: 10px 12px;
	border-radius: 0; /* design-guard-ok: sharp corners are the brand */
}
.gg-ff__in:focus { border-color: var(--gg-secondary); outline: none; }
.gg-ff__in::placeholder { color: var(--gg-on-surface-muted, #5E6C7A); }
/* A BOX GROWS TO WHAT IS IN IT (Sam, 19 Aug 2026). Overflow hidden rather than
   scroll, because the height is set from the content on every keystroke, so a
   scrollbar would only ever show for the instant before it was resized away. */
textarea.gg-ff__in { resize: none; overflow: hidden; min-height: 44px; }
/* A CHOOSER HAS TO LOOK LIKE ONE. Left native, a dark select draws its arrow in
   the operating system's own colour, which on this ground is somewhere between
   faint and invisible — so the field reads as a text box a coach can type into,
   and they try to. */
select.gg-ff__in {
	appearance: none;
	-webkit-appearance: none;
	padding-right: 34px;
	cursor: pointer;
	background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
	background-position: calc(100% - 19px) 50%, calc(100% - 13px) 50%;
	background-size: 6px 6px, 6px 6px;
	background-repeat: no-repeat;
}

/* ── Search box (donor: gg-rv2-gc-search on the Project Manager). A framed
 *    input with a leading magnifier. Filter-as-you-type behaviour lives in
 *    the gg-list-controls plugin (data-gg-search); the box works as a plain
 *    input without it. The !important flags beat WP admin's forms.css, which
 *    body-scopes input[type=search] and out-specifies a plain class. */
.gg-search {
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	background: rgba(0,0,0,0.28);
	border: 1px solid var(--gg-surface-border, rgba(255,255,255,0.16));
	padding: 0 10px;
	border-radius: 0;
}
.gg-search--block { display: flex; width: 100%; }
.gg-search__icon {
	flex: 0 0 14px;
	color: var(--gg-on-surface-muted, rgba(255,255,255,0.55));
	display: block;
}
.gg-search__input {
	flex: 1 1 auto;
	width: 100%;
	min-height: 30px;
	padding: 8px 2px;
	background: transparent !important;
	border: 0 !important;
	outline: none;
	color: var(--gg-on-surface, #e8e8e8) !important;
	font-family: inherit;
	font-size: 13px;
	line-height: 1.4;
	border-radius: 0 !important;
	box-shadow: none !important;
}
.gg-search__input::placeholder { color: color-mix(in srgb, currentColor 45%, transparent); font-style: italic; opacity: 1; }
.gg-search__input:focus { border: 0 !important; outline: none !important; box-shadow: none !important; }
.gg-search:focus-within { border-color: var(--gg-colour-accent, #E8B44F); }

/* ── View toggle (donor: gg-rv2-view-toggle). Segmented grid/list switch.
 *    Sharp corners to sit in the same family as .gg-tabs (the PM's rounded
 *    version is legacy). Behaviour + localStorage persistence live in the
 *    gg-list-controls plugin (data-gg-view). Shares --gg-tabs-active with the
 *    tabs so the whole segmented-control family recolours from one token. */
.gg-view-toggle {
	display: inline-flex;
	align-items: stretch;
	gap: 0;
	padding: 3px;
	width: fit-content;
	background: rgba(0,0,0,0.34);
	border: 1px solid var(--gg-surface-border, rgba(255,255,255,0.14));
	box-shadow: 0 2px 6px rgba(0,0,0,0.18);
	border-radius: 0;
}
.gg-view-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	min-width: 92px;
	padding: 9px 18px;
	background: transparent;
	border: 0;
	color: var(--gg-on-surface-muted, rgba(255,255,255,0.68));
	font-family: inherit;
	font-size: 14px;
	font-weight: 600;
	letter-spacing: 0.02em;
	line-height: 1;
	cursor: pointer;
	border-radius: 0;
	transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}
.gg-view-btn:hover { color: var(--gg-on-surface, #fff); background: rgba(255,255,255,0.08); }
.gg-view-btn:focus-visible { outline: 2px solid var(--gg-colour-accent, #E8B44F); outline-offset: 1px; }
.gg-view-btn svg { display: block; flex-shrink: 0; }
.gg-view-btn.is-active {
	background: var(--gg-tabs-active, #1f3a5f);
	color: #fff;
	box-shadow:
		inset 0 0 0 1px rgba(255,255,255,0.14),
		inset 0 1px 0 rgba(255,255,255,0.10),
		inset 0 -2px 6px rgba(0,0,0,0.30);
}

/* ── Tag chip (donor: gg-rv2-card-pill). A small uppercase colour chip; set
 *    its fill with --gg-tag-color (defaults to a neutral translucent white).
 *    .gg-tags is the wrapping row. Pure presentation — the interactive tag
 *    picker / filter / manager stays with each feature's own data layer. */
.gg-tags { display: flex; flex-wrap: wrap; gap: 4px; }
.gg-tag {
	display: inline-block;
	padding: 2px 6px;
	font-family: inherit;
	font-size: 10px;
	font-weight: 700;
	line-height: 1.5;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: #fff;
	background: var(--gg-tag-color, rgba(255,255,255,0.20));
	border-radius: 0;
}

/* ── Entry row (donor: .gg-hv2-* in gg-roadmap-v2/inc/gg-briefs-history-v2.php,
 *    the Project History panel — Sam-approved; READ ONLY donor, never
 *    touched). The canonical "list of things" row: one chip, a relative
 *    time, a clipped one-line headline, and a chevron that opens a
 *    collapsible body. Lifted onto brand tokens as a clean parallel family —
 *    .gg-hv2-* is untouched; migrating the History panel itself onto this
 *    family is a later step, and is out of scope here.
 *
 *    Render via gg_ui_entry_row() (gg-brand/inc/components.php) — don't
 *    hand-roll this markup. Full markup contract + args are documented on
 *    that function. Single-chip rule carried over from the donor: one chip
 *    per row, never two.
 *
 *    CONTRAST NOTE (same caveat as .gg-page-title/.gg-page-header above):
 *    .gg-entry-list renders near-white text (--gg-on-surface) — built for
 *    the platform's dark surfaces, not the plain white wp-admin content
 *    area. Always wrap it in a .gg-card (the Outbox does).
 *
 *    Default markup:
 *      <ol class="gg-entry-list">
 *        <li class="gg-entry-daygroup"><span class="gg-entry-daygroup__label">Today</span></li>
 *        <li class="gg-entry">
 *          <details class="gg-entry__details">
 *            <summary class="gg-entry__summary">
 *              <span class="gg-entry__chip gg-pill gg-pill--info">Sent</span>
 *              <span class="gg-entry__time" title="2026-07-15T10:00:00">2h ago</span>
 *              <span class="gg-entry__headline">Clipped one-line summary…</span>
 *              <span class="gg-entry__chev" aria-hidden="true">›</span>
 *            </summary>
 *            <div class="gg-entry__body">…full content…</div>
 *          </details>
 *        </li>
 *      </ol>
 *    A non-collapsible ("flat") row swaps the <details>/<summary> pair for a
 *    plain <div class="gg-entry__summary"> + always-visible .gg-entry__body
 *    right after — used where the content should never be hidden behind a
 *    click (e.g. the Outbox). .gg-entry__actions is an optional trailing
 *    row of buttons under the body.
 *
 *    Mail / notification-feed variant — add .gg-entry--mail to .gg-entry and
 *    swap the summary's contents for a leading .gg-entry__avatar block plus
 *    a .gg-entry__text column (bold .gg-entry__headline "subject" + a muted
 *    one-line .gg-entry__preview under it), with the chip/time trailing. */
.gg-entry-list {
	list-style: none;
	margin: 0;
	padding: 0;
}
.gg-entry-daygroup {
	list-style: none;
	padding: calc(var(--gg-space-base) * 2) 4px calc(var(--gg-space-base) * 1);
	border-bottom: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-entry-daygroup:first-child { padding-top: 4px; }
.gg-entry-daygroup__label {
	display: inline-block;
	font-size: 10.5px;
	font-weight: 700;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--gg-status-warn);
}
.gg-entry {
	border-bottom: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-entry:last-child { border-bottom: none; }
.gg-entry__details { background: transparent; }
.gg-entry__summary {
	list-style: none;
	cursor: pointer;
	display: flex;
	align-items: center;
	gap: var(--gg-space-base);
	padding: calc(var(--gg-space-base) * 1.25) 4px;
	transition: background var(--gg-transition-speed) var(--gg-transition-easing);
}
div.gg-entry__summary { cursor: default; }
.gg-entry__summary::-webkit-details-marker { display: none; }
.gg-entry__summary::marker { content: ''; }
details > .gg-entry__summary:hover { background: color-mix(in srgb, currentColor 5%, transparent); }
.gg-entry__chip {
	flex: none;
	white-space: nowrap;
	max-width: 160px;
	overflow: hidden;
	text-overflow: ellipsis;
}
.gg-entry__time {
	flex: none;
	font-size: var(--gg-small-size);
	color: var(--gg-on-surface-muted);
	white-space: nowrap;
	cursor: help;
}
.gg-entry__headline {
	flex: 1 1 auto;
	min-width: 0;
	font-size: 13.5px;
	color: var(--gg-on-surface);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	line-height: 1.4;
}
.gg-entry__chev {
	flex: none;
	color: var(--gg-on-surface-muted);
	font-size: 16px;
	line-height: 1;
	transition: transform var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-entry__details[open] .gg-entry__chev { transform: rotate(90deg); }
.gg-entry__body {
	padding: 4px 4px calc(var(--gg-space-base) * 2);
	font-size: 13.5px;
	line-height: 1.6;
	color: var(--gg-on-surface-muted);
}
.gg-entry__body > *:first-child { margin-top: 0; }
.gg-entry__body > *:last-child { margin-bottom: 0; }
.gg-entry__actions {
	display: flex;
	gap: var(--gg-space-base);
	padding: 0 4px calc(var(--gg-space-base) * 2);
}

/* Mail / notification-feed variant. */
.gg-entry--mail .gg-entry__summary {
	display: flex;
	align-items: flex-start;
	gap: calc(var(--gg-space-base) * 1.5);
	padding: calc(var(--gg-space-base) * 1.5) 4px;
}
.gg-entry__avatar {
	width: 32px;
	height: 32px;
	flex: none;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	background: var(--gg-surface-panel);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	color: var(--gg-on-surface-muted);
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	overflow: hidden;
}
.gg-entry__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gg-entry__text {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 3px;
}
.gg-entry--mail .gg-entry__headline {
	white-space: normal;
	display: flex;
	align-items: baseline;
	gap: 8px;
	font-weight: 600;
}
.gg-entry__preview {
	font-size: var(--gg-small-size);
	color: var(--gg-on-surface-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.gg-entry__meta {
	flex: none;
	display: flex;
	align-items: center;
	gap: calc(var(--gg-space-base) * 1.25);
	padding-top: 2px;
}

/* ── Table (donor STRUCTURE only: gg-users-table's sortable headers, hover
 *    rows, pill-in-cell — gg-users/inc/users-page.php. Its CSS was off-brand
 *    (Raleway, rounded corners, indigo) and is NOT reused; this is restyled
 *    entirely onto tokens with sharp corners.
 *
 *    Render via gg_ui_table() (gg-brand/inc/components.php). Sort behaviour
 *    opts in with data-gg-table-sort on the <table> plus data-sort-key on
 *    each sortable <th> — see gg-brand/assets/gg-components.js (thin,
 *    self-contained, follows the gg-tabs behaviour-plugin pattern). Every
 *    <td> the helper renders carries data-sort-value so the JS can sort a
 *    formatted cell (e.g. "£1,240.00") by its real underlying value.
 *
 *    CONTRAST NOTE (same caveat as .gg-page-title/.gg-entry-list above):
 *    .gg-table renders near-white text — wrap it in a .gg-card (every
 *    gg_ui_table() call on the Finance page does). */
.gg-table-wrap { overflow-x: auto; }
.gg-table {
	width: 100%;
	border-collapse: collapse;
	font-family: var(--gg-font-body);
	font-size: 13px;
	color: var(--gg-on-surface);
}
.gg-table thead th {
	text-align: left;
	padding: calc(var(--gg-space-base) * 1) calc(var(--gg-space-base) * 1.25);
	border-bottom: var(--gg-border-width) solid var(--gg-surface-border);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--gg-on-surface-muted);
	white-space: nowrap;
}
.gg-table tbody td {
	padding: calc(var(--gg-space-base) * 1) calc(var(--gg-space-base) * 1.25);
	border-bottom: var(--gg-border-width) solid var(--gg-surface-border);
	vertical-align: middle;
}
.gg-table tbody tr:last-child td { border-bottom: none; }
.gg-table tbody tr:hover td { background: color-mix(in srgb, currentColor 5%, transparent); }
.gg-table td.gg-table__num,
.gg-table th.gg-table__num { text-align: right; font-variant-numeric: tabular-nums; }
.gg-table th[data-sort-key] { cursor: pointer; user-select: none; }
.gg-table th[data-sort-key]:hover { color: var(--gg-on-surface); }
.gg-table__sort-icon {
	display: inline-block;
	margin-left: 4px;
	font-size: 9px;
	line-height: 1;
	opacity: 0.35;
}
.gg-table th[data-sort-key].is-sorted-asc .gg-table__sort-icon,
.gg-table th[data-sort-key].is-sorted-desc .gg-table__sort-icon {
	opacity: 1;
	color: var(--gg-status-info);
}
.gg-table th[data-sort-key].is-sorted-desc .gg-table__sort-icon { transform: scaleY(-1); }
.gg-table-empty {
	padding: calc(var(--gg-space-base) * 2) 4px;
	color: var(--gg-on-surface-muted);
	font-size: 13px;
	font-style: italic;
}

/* ── Empty state (donor: .gg-assets-empty, gg-assets/inc/admin-grid.php — same
 *    centred frosted card, with its hardcoded rgba() values swapped for tokens
 *    so it follows the brand instead of freezing one palette).
 *
 *    THE empty state for a whole screen. Distinct from .gg-table-empty above,
 *    which is the thin italic line INSIDE a table's own body — this one is the
 *    block a grid, list or detail screen shows when it has nothing yet.
 *
 *    Why it exists: 21 plugins had invented their own `*-empty` class, and most
 *    of them render a bare "No items" — which tells a coach nothing about what
 *    the screen is for or how anything gets into it. The shared version makes
 *    the explanation structural: the body copy is a required argument, so an
 *    empty screen that explains nothing can't be built by accident.
 *
 *    Icons only, never a metaphor illustration (house rule). Sharp corners.
 *
 *    Markup (render via gg_ui_empty_state() — don't hand-roll this):
 *      <div class="gg-empty-state">
 *        <div class="gg-empty-state__icon">…svg…</div>
 *        <p class="gg-empty-state__title">Nothing here yet</p>
 *        <p class="gg-empty-state__body">What will appear, and how it arrives.</p>
 *        <div class="gg-empty-state__action">…one .gg-btn…</div>
 *      </div>                                                                */
.gg-empty-state {
	max-width: 560px;
	margin: calc(var(--gg-space-base) * 7) auto 0;
	padding: calc(var(--gg-space-base) * 6.5) calc(var(--gg-space-base) * 5.5);
	text-align: center;
	background: var(--gg-surface-card);
	border: 1px solid var(--gg-border);
	border-radius: 0;
	box-shadow: 0 14px 40px rgba(0, 0, 0, 0.4);
}
/* Muted on-surface, NOT a brand colour. The donor used the coach's secondary,
 * which is a near-black navy by default and vanished against the dark card —
 * the exact dark-on-dark failure. The icon must stay legible whatever brand a
 * coach picks, so it rides the surface scale like the body copy does. */
.gg-empty-state__icon {
	color: var(--gg-on-surface-muted);
	line-height: 0;
}
.gg-empty-state__icon svg,
.gg-empty-state__icon .gg-icon {
	width: 42px;
	height: 42px;
	display: inline-block;
}
.gg-empty-state__title {
	font-family: var(--gg-font-heading);
	color: var(--gg-on-surface);
	font-size: 22px;
	font-weight: 600;
	letter-spacing: -0.01em;
	margin: 16px 0 10px;
	padding: 0;
}
.gg-empty-state__body {
	color: var(--gg-on-surface-muted);
	font-size: 14px;
	line-height: 1.6;
	margin: 0 auto;
	max-width: 42em;
}
.gg-empty-state__body strong {
	color: var(--gg-on-surface);
}
.gg-empty-state__action {
	margin-top: 20px;
}
@media (max-width: 640px) {
	.gg-empty-state {
		margin-top: calc(var(--gg-space-base) * 4);
		padding: calc(var(--gg-space-base) * 4) calc(var(--gg-space-base) * 2.5);
	}
	.gg-empty-state__action .gg-btn {
		width: 100%;
	}
}

/* ── Grid/list view for a .gg-table (donor: gg-rv2-view-toggle switching the
 *    Project Manager board between its card grid and its list). A <table>
 *    has no separate card markup to switch to, so "Grid" restyles the SAME
 *    rows into a compact card layout with pure CSS: each <tr> becomes a
 *    bordered card, each <td> becomes a label/value line using the column
 *    label gg_ui_table() stamps onto data-label. Toggled by adding
 *    .is-view-grid to .gg-table-wrap (gg-list-controls plugin, data-gg-view —
 *    see the control bar below); "List" (default / .is-view-list) is the
 *    plain table. */
.gg-table-wrap.is-view-grid table.gg-table,
.gg-table-wrap.is-view-grid table.gg-table thead,
.gg-table-wrap.is-view-grid table.gg-table tbody,
.gg-table-wrap.is-view-grid table.gg-table tr,
.gg-table-wrap.is-view-grid table.gg-table th,
.gg-table-wrap.is-view-grid table.gg-table td {
	display: block;
}
.gg-table-wrap.is-view-grid table.gg-table thead { display: none; }
.gg-table-wrap.is-view-grid table.gg-table tbody {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
	gap: calc(var(--gg-space-base) * 1.5);
}
.gg-table-wrap.is-view-grid table.gg-table tr {
	border: var(--gg-border-width) solid var(--gg-surface-border);
	background: color-mix(in srgb, currentColor 3%, transparent);
	padding: 0 calc(var(--gg-space-base) * 1.25);
}
.gg-table-wrap.is-view-grid table.gg-table tr:hover td { background: transparent; }
.gg-table-wrap.is-view-grid table.gg-table td {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: calc(var(--gg-space-base) * 1);
	padding: calc(var(--gg-space-base) * 0.85) 0;
	border-bottom: var(--gg-border-width) solid var(--gg-surface-border);
	text-align: right;
}
.gg-table-wrap.is-view-grid table.gg-table tr td:last-child { border-bottom: 0; }
.gg-table-wrap.is-view-grid table.gg-table td::before {
	content: attr(data-label);
	flex: 0 0 auto;
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--gg-on-surface-muted);
	text-align: left;
}

/* ── Table controls — the one-line toolbar above a .gg-table (donor: the
 *    Project Manager's render_grid_controls "search / filter by tag / sort /
 *    view toggle" row — gg-roadmap-v2/src/AdminRoadmapV2.php, READ-ONLY
 *    donor, never modified). Search reuses .gg-search verbatim; the view
 *    toggle reuses .gg-view-toggle verbatim (both via the gg-list-controls
 *    plugin, data-gg-search / data-gg-view — zero new JS for either). Filter
 *    and sort are new dropdown families below; render via gg_ui_table()'s
 *    $opts['controls'], behaviour in gg-brand/assets/gg-components.js. */
.gg-table-controls {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: calc(var(--gg-space-base) * 1);
	margin: 0 0 calc(var(--gg-space-base) * 1.25);
}
.gg-table-controls .gg-search { flex: 1 1 200px; min-width: 160px; }
@media (max-width: 640px) {
	.gg-table-controls { align-items: stretch; }
	.gg-table-controls .gg-search { flex: 1 1 auto; }
}

/* Shared popover panel for both dropdowns below — same recipe as
 * .gg-modal__panel (dark surface, sharp corners) at popover scale. */
.gg-table-menu {
	position: absolute;
	top: calc(100% + 6px);
	left: 0;
	z-index: 60;
	min-width: 220px;
	max-width: 300px;
	max-height: 320px;
	overflow-y: auto;
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: 6px;
	background: var(--gg-surface-panel);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	box-shadow: var(--gg-shadow-lg);
	border-radius: 0;
}
.gg-table-menu[hidden] { display: none; }
.gg-table-menu__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: calc(var(--gg-space-base) * 1);
	padding: 4px 8px 6px;
	border-bottom: var(--gg-border-width) solid var(--gg-surface-border);
	margin-bottom: 4px;
}
.gg-table-menu__title {
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--gg-on-surface-muted);
}
.gg-table-menu__clear {
	appearance: none;
	-webkit-appearance: none;
	border: var(--gg-border-width) solid var(--gg-surface-border);
	border-radius: 0;
	background: rgba(0,0,0,0.28);
	color: var(--gg-on-surface-muted);
	font-family: inherit;
	font-size: 11px;
	font-weight: 600;
	padding: 4px 8px;
	cursor: pointer;
	transition: background 0.12s ease, color 0.12s ease;
}
.gg-table-menu__clear:hover { background: rgba(255,255,255,0.08); color: var(--gg-on-surface); }

/* Trigger button chrome shared by the filter + sort dropdowns, and the
 * Manage-tags modal trigger (donor: gg-rv2-tagfilter-btn / gg-rv2-sortfilter-
 * btn / gg-rv2-manage-tags-btn — bordered pill, 34px). */
.gg-table-filter,
.gg-table-sort,
.gg-table-managetags { position: relative; display: inline-flex; align-items: center; }
.gg-table-filter__btn,
.gg-table-sort__btn,
.gg-table-managetags__btn {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	height: 34px;
	box-sizing: border-box;
	padding: 0 12px;
	background: rgba(0,0,0,0.28);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	border-radius: 0;
	color: var(--gg-on-surface-muted);
	font-family: inherit;
	font-size: 13px;
	font-weight: 600;
	cursor: pointer;
	transition: background 0.12s ease, color 0.12s ease;
}
.gg-table-filter__btn:hover,
.gg-table-sort__btn:hover,
.gg-table-managetags__btn:hover,
.gg-table-filter__btn[aria-expanded="true"],
.gg-table-sort__btn[aria-expanded="true"] {
	background: rgba(255,255,255,0.08);
	color: var(--gg-on-surface);
}
.gg-table-filter__icon,
.gg-table-sort__icon { flex: 0 0 auto; opacity: 0.85; }
.gg-table-caret { flex: 0 0 auto; transition: transform 0.12s ease; }
.gg-table-filter__btn[aria-expanded="true"] .gg-table-caret,
.gg-table-sort__btn[aria-expanded="true"] .gg-table-caret { transform: rotate(180deg); }
.gg-table-filter__count,
.gg-table-sort__current {
	color: var(--gg-colour-accent, #E8B44F);
	font-weight: 700;
	white-space: nowrap;
}
.gg-table-filter__count[hidden] { display: none; }

/* "Filter tags" search (2026-07-16 — donor: the Project Manager's
 * .gg-rv2-tag-picker-search, READ-ONLY donor, never modified). Sits above
 * the checklist inside the same scrolling .gg-table-menu, matching how the
 * donor's own search scrolls with its list. Pure view filter — behaviour in
 * gg-brand/assets/gg-components.js, initTagFilter(). Same recipe as
 * .gg-search/.gg-search__input above: the visible frame lives on the
 * wrapping label, the <input> itself goes transparent+borderless (!important
 * — beats wp-admin forms.css, which out-specifies a plain class on
 * input[type=search]) so wp-admin's white input chrome never shows through. */
.gg-table-filter__search {
	position: relative;
	display: block;
	margin: 2px 4px 6px;
	background: rgba(0,0,0,0.28);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	border-radius: 0;
}
.gg-table-filter__search:focus-within { border-color: var(--gg-colour-accent, #E8B44F); }
.gg-table-filter__search-input {
	display: block;
	width: 100%;
	box-sizing: border-box;
	min-height: 0 !important;
	padding: 7px 9px !important;
	background: transparent !important;
	border: 0 !important;
	outline: none;
	color: var(--gg-on-surface, #e8e8e8) !important;
	font-family: inherit;
	font-size: 12.5px;
	line-height: 1.3;
	border-radius: 0 !important;
	box-shadow: none !important;
}
.gg-table-filter__search-input::placeholder { color: var(--gg-on-surface-muted); font-style: italic; opacity: 1; }
.gg-table-filter__search-input:focus { border: 0 !important; outline: none; box-shadow: none !important; }

/* Filter-by-tag checklist rows (donor: gg-rv2-tagpick-row). Each row pairs a
 * checkbox with the existing .gg-tag chip — reuses the tag-chip component
 * instead of inventing a separate colour-dot. */
.gg-table-filter__list { display: flex; flex-direction: column; gap: 1px; }
.gg-table-filter__row {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 8px 10px;
	cursor: default; /* not a <label> — two checkboxes, see gg_ui_table_controls() */
	color: var(--gg-on-surface-muted);
	font-family: inherit;
	font-size: 12.5px;
	transition: background 0.12s ease, color 0.12s ease;
}
.gg-table-filter__row[hidden] { display: none; }
.gg-table-filter__row:hover { background: rgba(255,255,255,0.08); color: var(--gg-on-surface); }
.gg-table-filter__label {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.gg-table-filter__row-count {
	flex: 0 0 auto;
	margin-left: auto;
	padding-left: 6px;
	font-size: 11px;
	font-weight: 500;
	color: var(--gg-on-surface-muted);
	white-space: nowrap;
}

/* Per-tag include/exclude toggle pair (2026-07-16 — donor: the Project
 * Manager's dual .gg-rv2-filtcb--inc/--exc checkboxes, READ-ONLY donor,
 * never modified). Native checkbox appearance suppressed so a background-
 * image glyph decides the mark instead of the browser's own checkmark — a
 * check for include (brand accent), a MINUS for exclude (brand danger — the
 * "red minus" toggle). The glyph itself is a small monochrome SVG data-URI
 * (background-image can't consume currentColor/var() from the page), but
 * every colour that actually carries brand meaning — the checked fill/
 * border — reads from --gg-colour-accent / --gg-status-danger, same tokens
 * .gg-tag / .gg-pill already use. Mutual exclusivity (checking one clears
 * the other) + "excludes always win" live in initTagFilter().
 */
.gg-table-filter__toggles { display: inline-flex; align-items: center; gap: 5px; flex: 0 0 auto; }
/* !important throughout this checkbox family — beats wp-admin's forms.css,
 * which sets its own input[type=checkbox] border/background/border-radius
 * PLUS a :checked::before content-injected checkmark, all at equal-or-higher
 * specificity than a plain class (donor: the PM's .gg-rv2-filtcb, which
 * fights the exact same forms.css rules the exact same way). */
.gg-table-filter__cb {
	appearance: none !important;
	-webkit-appearance: none !important;
	-moz-appearance: none !important;
	width: 15px !important;
	height: 15px !important;
	min-width: 0 !important;
	margin: 0 !important;
	flex: 0 0 auto;
	box-sizing: border-box;
	border: var(--gg-border-width) solid var(--gg-surface-border) !important;
	border-radius: 0 !important;
	background-color: rgba(0,0,0,0.28) !important;
	/* position/repeat/size also !important: wp-admin's colour-scheme CSS sets
	 * the `background` SHORTHAND on :checked (see note below), which resets
	 * these longhands to their initial values too when it out-specifies a
	 * non-important plain-class declaration. */
	background-position: center !important;
	background-repeat: no-repeat !important;
	background-size: 11px 11px !important;
	box-shadow: none !important;
	cursor: pointer;
	transition: background-color 0.12s ease, border-color 0.12s ease;
}
.gg-table-filter__cb:hover { border-color: var(--gg-on-surface-muted) !important; }
.gg-table-filter__cb:focus,
.gg-table-filter__cb:focus-visible {
	border-color: var(--gg-colour-accent, #E8B44F) !important;
	box-shadow: none !important;
	outline: 2px solid var(--gg-colour-accent, #E8B44F);
	outline-offset: 1px;
}
/* Kill wp-admin's injected checkmark pseudo-element (input[type=checkbox]:
 * checked::before { content: url(...check.svg) }) so OUR background-image
 * glyph decides the mark — a check for include, a MINUS for exclude.
 * Without this, wp-admin stamps its own white check on the red exclude box
 * too, reading as "included" instead of "hidden". */
.gg-table-filter__cb::before,
.gg-table-filter__cb::after,
.gg-table-filter__cb:checked::before,
.gg-table-filter__cb:checked::after { content: none !important; display: none !important; }
.gg-table-filter__cb--inc {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7a90' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") !important;
}
.gg-table-filter__cb--inc:checked {
	background-color: var(--gg-colour-accent, #E8B44F) !important;
	border-color: var(--gg-colour-accent, #E8B44F) !important;
	/* !important on background-image specifically: wp-admin's colour-scheme
	 * CSS (wp-admin/css/colors/*.../colors.min.css) has
	 * input[type=checkbox]:checked { background: var(--wp-admin-theme-color); }
	 * — the shorthand resets background-image to none, and its extra `input`
	 * type-selector ties our class-only selector on specificity's b-tier then
	 * edges us out on the c-tier — !important is the only clean way to win a
	 * property that shorthand-resets, short of donor-style ancestor+type
	 * selector scoping.
	 *
	 * Glyph itself is a dark-outline + light-fill "halo" checkmark (two
	 * stacked polylines, not one) rather than a single fixed dark stroke —
	 * --gg-colour-accent is a coach's own brand pick (unlike --gg-status-
	 * danger, which is a fixed brand-independent semantic red) and can
	 * legitimately resolve near-black, where a plain dark checkmark
	 * disappears. The halo reads on any fill lightness. */
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12' stroke='%230A1628' stroke-width='6'/%3E%3Cpolyline points='20 6 9 17 4 12' stroke='%23ffffff' stroke-width='2.6'/%3E%3C/svg%3E") !important;
}
.gg-table-filter__cb--exc {
	border-color: rgba(255,140,140,0.45) !important;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23b56b6b' stroke-width='3.2' stroke-linecap='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E") !important;
}
.gg-table-filter__cb--exc:hover { border-color: rgba(255,140,140,0.85) !important; }
.gg-table-filter__cb--exc:checked {
	background-color: var(--gg-status-danger, #ff8c8c) !important;
	border-color: var(--gg-status-danger, #ff8c8c) !important;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230A1628' stroke-width='3.6' stroke-linecap='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E");
}

/* Sort dropdown options (donor: gg-rv2-sortopt / gg-rv2-sortopt-check — a
 * checkmark that's invisible until its row is the active sort). */
.gg-table-sort__list { display: flex; flex-direction: column; gap: 1px; }
.gg-table-sort__opt {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	padding: 8px 10px;
	border: 0;
	border-radius: 0;
	background: transparent;
	color: var(--gg-on-surface-muted);
	font-family: inherit;
	font-size: 12.5px;
	text-align: left;
	cursor: pointer;
	transition: background 0.12s ease, color 0.12s ease;
}
.gg-table-sort__opt:hover { background: rgba(255,255,255,0.08); color: var(--gg-on-surface); }
.gg-table-sort__opt.is-active { color: var(--gg-on-surface); }
.gg-table-sort__opt-check {
	flex: 0 0 15px;
	width: 15px;
	height: 15px;
	opacity: 0;
	color: var(--gg-colour-accent, #E8B44F);
	transition: opacity 0.12s ease;
}
.gg-table-sort__opt.is-active .gg-table-sort__opt-check { opacity: 1; }

/* ── Manage-tags modal (donor: the Project Manager board's manage-tags
 *    modal, gg-roadmap-v2's .gg-rv2-tag-m* — READ-ONLY donor, never modified).
 *    Reuses the .gg-modal shell verbatim; these classes style the row list +
 *    create-row inside it. Render via gg_ui_table_manage_tags_panel() /
 *    gg_ui_table_tags_row() (gg-brand/inc/components.php); behaviour (open/
 *    close + create/rename/recolour/delete, client-side only) in
 *    gg-brand/assets/gg-components.js, initManageTags(). */
.gg-table-tags-list { display: flex; flex-direction: column; }
.gg-table-tags-empty { font-size: 13.5px; color: var(--gg-on-surface-muted); margin: 10px 2px; }
.gg-table-tags-row {
	display: flex;
	align-items: center;
	gap: 12px;
	min-height: 44px;
	padding: 6px 4px;
	border-top: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-table-tags-row:first-child { border-top: 0; }
.gg-table-tags-info { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1 1 auto; }
.gg-table-tags-colorpick { position: relative; display: inline-flex; flex: 0 0 auto; }
.gg-table-tags-swatch {
	flex: 0 0 auto;
	width: 14px;
	height: 14px;
	padding: 0;
	border: var(--gg-border-width) solid var(--gg-surface-border);
	border-radius: 0;
	background: rgba(255,255,255,0.05);
	cursor: pointer;
	transition: transform 0.1s ease, border-color 0.1s ease;
}
.gg-table-tags-swatch:hover { transform: scale(1.1); }
.gg-table-tags-label {
	font-weight: 600;
	font-size: 14px;
	color: var(--gg-on-surface);
	min-width: 0;
	max-width: 100%;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.gg-table-tags-label-input {
	font: inherit;
	font-size: 13.5px;
	color: var(--gg-on-surface);
	background: var(--gg-surface-panel);
	border: var(--gg-border-width) solid var(--gg-status-info);
	border-radius: 0;
	padding: 3px 6px;
	min-width: 120px;
	outline: none;
}
.gg-table-tags-count {
	flex: 0 0 auto;
	font-weight: 500;
	font-size: 11.5px;
	color: var(--gg-on-surface-muted);
	white-space: nowrap;
}
.gg-table-tags-actions { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.gg-table-tags-action {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 28px;
	height: 28px;
	padding: 0;
	border: var(--gg-border-width) solid transparent;
	border-radius: 0;
	background: transparent;
	color: var(--gg-on-surface-muted);
	cursor: pointer;
	transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.gg-table-tags-action:hover { background: rgba(255,255,255,0.08); color: var(--gg-on-surface); border-color: var(--gg-surface-border); }
.gg-table-tags-action--delete:hover { color: var(--gg-status-danger); }

/* Colour popover — a preset-token grid (donor: gg-rv2-tag-mcolorgrid). Tokens,
 * not new hardcoded hex, so a brand recolour carries through automatically. */
.gg-table-tags-colorpop {
	position: absolute;
	top: calc(100% + 5px);
	left: 0;
	z-index: 70;
	display: grid;
	grid-template-columns: repeat(4, 22px);
	gap: 6px;
	padding: 8px;
	background: var(--gg-surface-panel);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	box-shadow: var(--gg-shadow-lg);
	border-radius: 0;
}
.gg-table-tags-colorpop[hidden] { display: none; }
.gg-table-tags-color-opt {
	width: 22px;
	height: 22px;
	padding: 0;
	border: var(--gg-border-width) solid var(--gg-surface-border);
	border-radius: 0;
	background: transparent;
	cursor: pointer;
	transition: transform 0.1s ease, border-color 0.1s ease;
}
.gg-table-tags-color-opt:hover { transform: scale(1.1); border-color: var(--gg-on-surface-muted); }
.gg-table-tags-color-opt.is-on { outline: 2px solid var(--gg-colour-accent, #E8B44F); outline-offset: 1px; }
.gg-table-tags-color-opt--none { position: relative; background: rgba(255,255,255,0.05); }
.gg-table-tags-color-opt--none::after {
	content: "";
	position: absolute;
	left: 50%;
	top: 3px;
	bottom: 3px;
	width: 1px;
	background: var(--gg-status-danger);
	transform: rotate(45deg);
	transform-origin: center;
}

/* Create-tag row — lives in .gg-modal__foot alongside the Close button.
 * .gg-input / .gg-btn are the existing shared form/button components; these
 * two rules are layout-only. */
.gg-table-tags-addrow { display: flex; gap: 8px; flex: 1 1 auto; align-items: center; }
.gg-table-tags-addinput { flex: 1 1 auto; min-width: 0; }
@media (max-width: 540px) {
	.gg-table-tags-row { flex-wrap: wrap; }
	.gg-modal__foot { flex-wrap: wrap; }
	.gg-table-tags-addrow { flex: 1 1 100%; }
}

/* ── Form controls (new — none existed before). Distinct names on purpose:
 *    .gg-field already exists, incompatibly, in gg-my-offer/inc/page.php AND
 *    gg-directory/src/MetaBox.php — redefining it globally would break both.
 *    Also skips .gg-toggle: gg-social/assets/app.css already owns that name
 *    on its own admin screen, and — because admin_print_styles (its enqueued
 *    <link>) fires before admin_head (where these component styles print) —
 *    a same-specificity .gg-toggle here would silently win and break gg-
 *    social's switches. Named .gg-switch instead; verified free by grep.
 *
 *    Render via gg_ui_field() (gg-brand/inc/components.php). Dark panel
 *    inputs, sharp corners, focus ring on --gg-status-info. */
.gg-form-row {
	display: flex;
	flex-direction: column;
	gap: calc(var(--gg-space-base) * 0.75);
	margin: 0 0 calc(var(--gg-space-base) * 2);
}
.gg-form-row--inline {
	flex-direction: row;
	align-items: center;
	flex-wrap: wrap;
	gap: calc(var(--gg-space-base) * 1.25);
}
.gg-form-row__help {
	font-size: var(--gg-small-size);
	color: var(--gg-on-surface-muted);
	line-height: 1.5;
}
.gg-label {
	font-size: 12px;
	font-weight: 600;
	color: var(--gg-on-surface-muted);
	letter-spacing: 0.02em;
}
/* !important on background/border/color/radius: wp-admin/css/forms.css ships
 * `input[type=text],…,select,textarea{background-color:#fff;border:1px solid
 * #949494;…}` PLUS `.wp-core-ui select{background:#fff url(…arrow…);…}` —
 * both out-specify a plain `.gg-select`/`.gg-input` class (same problem
 * `.gg-search__input` above already documents + fixes the same way). */
.gg-input,
.gg-select,
.gg-textarea {
	width: 100%;
	box-sizing: border-box;
	padding: calc(var(--gg-space-base) * 1) calc(var(--gg-space-base) * 1.25);
	background: var(--gg-surface-panel) !important;
	border: var(--gg-border-width) solid var(--gg-surface-border) !important;
	border-radius: 0 !important;
	color: var(--gg-on-surface) !important;
	font-family: var(--gg-font-body);
	font-size: 13px;
	line-height: 1.5;
	box-shadow: none !important;
	transition: border-color var(--gg-transition-speed) var(--gg-transition-easing),
	            box-shadow var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-textarea { resize: vertical; min-height: 90px; }
.gg-input::placeholder,
.gg-textarea::placeholder { color: var(--gg-on-surface-muted); opacity: 0.75; }
.gg-select {
	cursor: pointer;
	/* Drop wp-core-ui's baked-in dropdown-arrow background image — the
	   native arrow renders fine over our dark background. */
	background-image: none !important;
}
.gg-input:focus,
.gg-select:focus,
.gg-textarea:focus {
	outline: none;
	border-color: var(--gg-status-info) !important;
	box-shadow: 0 0 0 2px color-mix(in srgb, var(--gg-status-info) 30%, transparent) !important;
}
.gg-input:disabled,
.gg-select:disabled,
.gg-textarea:disabled { opacity: 0.55; cursor: not-allowed; }

/* Checkbox — native input, styled square, sharp corners. */
.gg-check {
	display: inline-flex;
	align-items: center;
	gap: calc(var(--gg-space-base) * 1);
	cursor: pointer;
	font-size: 13px;
	color: var(--gg-on-surface);
}
.gg-check input[type="checkbox"] {
	appearance: none;
	-webkit-appearance: none;
	width: 16px;
	height: 16px;
	flex: none;
	margin: 0;
	background: var(--gg-surface-panel);
	/* An empty box is nothing but its outline, so it takes the control token,
	   not the panel hairline — see --gg-control-border. */
	border: var(--gg-border-width) solid var(--gg-control-border);
	border-radius: 0;
	display: inline-grid;
	place-content: center;
	cursor: pointer;
	transition: border-color var(--gg-transition-speed) var(--gg-transition-easing);
}
/* A TICKED BOX HAS A TICK IN IT (2026-08-20, Sam: "Tickboxes on each roadmap
 * step should have ticks in them when you've clicked in one, not...whatver you
 * have now").
 *
 * It was an 8px filled square, which reads as "partly selected" rather than as
 * "done". Every checkbox on the platform draws from here, so this is one change
 * in one place and every screen inherits it - the Roadmap, the step editor,
 * bulk select, the setup pages.
 *
 * Two borders rotated 45deg rather than a glyph or an SVG, so it takes the box's
 * own colour, stays crisp at any zoom and needs no font. The negative margin and
 * the uneven sides are optical centring: a tick centred on its bounding box sits
 * visibly low. */
.gg-check input[type="checkbox"]::before {
	content: '';
	width: 4.5px;
	height: 8.5px;
	margin-top: -2px;
	border: solid var(--gg-status-info);
	border-width: 0 2px 2px 0;
	transform: rotate(45deg) scale(0);
	transition: transform var(--gg-transition-speed) var(--gg-transition-easing);
	background: none;
}
.gg-check input[type="checkbox"]:checked { border-color: var(--gg-status-info); }
.gg-check input[type="checkbox"]:checked::before { transform: rotate(45deg) scale(1); }
/* PART-TICKED IS ITS OWN ANSWER, AND IT IS NOT A TICK. A select-all over a
 * part-ticked list sets `indeterminate`, which had no mark of its own - it was
 * getting the same filled square as checked, and now would get a tick and claim
 * the whole list. So it gets the bar it should always have had. */
.gg-check input[type="checkbox"]:indeterminate { border-color: var(--gg-status-info); }
.gg-check input[type="checkbox"]:indeterminate::before {
	width: 8px;
	height: 0;
	margin-top: 0;
	border-width: 0 0 2px 0;
	transform: rotate(0deg) scale(1);
}
.gg-check input[type="checkbox"]:focus-visible {
	outline: 2px solid var(--gg-status-info);
	outline-offset: 2px;
}

/* Toggle switch — native checkbox input reskinned as a track + thumb. */
.gg-switch {
	display: inline-flex;
	align-items: center;
	gap: calc(var(--gg-space-base) * 1.25);
	cursor: pointer;
	font-size: 13px;
	color: var(--gg-on-surface);
}
.gg-switch input[type="checkbox"] {
	appearance: none;
	-webkit-appearance: none;
	position: relative;
	width: 38px;
	height: 20px;
	flex: none;
	margin: 0;
	background: var(--gg-surface-panel);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	border-radius: 0;
	cursor: pointer;
	transition: background var(--gg-transition-speed) var(--gg-transition-easing),
	            border-color var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-switch input[type="checkbox"]::before {
	content: '';
	position: absolute;
	top: 1px;
	left: 1px;
	width: 16px;
	height: 16px;
	background: var(--gg-on-surface-muted);
	transition: transform var(--gg-transition-speed) var(--gg-transition-easing),
	            background var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-switch input[type="checkbox"]:checked {
	background: color-mix(in srgb, var(--gg-status-info) 25%, var(--gg-surface-panel));
	border-color: var(--gg-status-info);
}
.gg-switch input[type="checkbox"]:checked::before {
	transform: translateX(18px);
	background: var(--gg-status-info);
}
.gg-switch input[type="checkbox"]:focus-visible {
	outline: 2px solid var(--gg-status-info);
	outline-offset: 2px;
}

/* ── Disclosure (donors: gg-my-offer's .gg-mo-item, non-PM, and
 *    gg-roadmap-v2's .brf-tg-item in gg-briefs-toggle.php — PM, READ ONLY,
 *    never touched). Multi-open toggle: native <details>/<summary>, several
 *    can be open at once. A boxed item (not a flat divided list row like
 *    .gg-entry) — border glows on hover/open using --gg-status-info, the
 *    same accent already used everywhere else in this file for
 *    "interactive/active" (.gg-input:focus, .gg-switch:checked, the table
 *    sort icon). Chevron matches .gg-entry__chev's rotate-on-open language
 *    for a consistent expand feel across every toggle family.
 *
 *    Render via gg_ui_disclosure() (gg-brand/inc/components.php) — one
 *    <details> per call; wrap several in <div class="gg-disclosure-list">.
 *    NOT named .gg-toggle — see the .gg-switch note above; verified free by
 *    grep across wp-content.
 *
 *    CONTRAST NOTE (same caveat as .gg-entry-list/.gg-table above): renders
 *    near-white text — always sits on a dark surface. */
/* ── Divider — a faint line between two halves of a page (Sam, 17 Aug 2026).
 *
 *    Donor: the Project Manager's project page, where it separates what a
 *    person writes and owns (Summary, Goals, Rules, Open questions, Notes)
 *    from the working sequence an AI runs (Research, Plan, Check).
 *
 *    FAINT IS THE SPEC, and the number is what makes it faint: the surface
 *    border at roughly half strength, so it reads as a change of register
 *    rather than as another box edge. It carries no words — a labelled line is
 *    a heading, and a heading announces a section; this only says the kind of
 *    thing changes from here down. Breathing room above and below is most of
 *    the effect, so the space is part of the component, not the caller's job.
 *
 *    Render via gg_ui_divider() (gg-brand/inc/components.php). Never draw a
 *    hairline locally — that is how one line ends up four different weights. */
.gg-divider {
	border: 0;
	border-top: var(--gg-border-width) solid
		color-mix(in srgb, var(--gg-surface-border) 55%, transparent);
	margin: calc(var(--gg-space-base) * 4) 0;
}
.gg-disclosure-list {
	display: flex;
	flex-direction: column;
	gap: calc(var(--gg-space-base) * 1);
}
.gg-disclosure {
	background: var(--gg-surface-card);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	transition: border-color var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-disclosure:hover,
.gg-disclosure[open] {
	border-color: color-mix(in srgb, var(--gg-status-info) 55%, transparent);
}
.gg-disclosure__summary {
	display: flex;
	align-items: center;
	gap: calc(var(--gg-space-base) * 1.5);
	padding: calc(var(--gg-space-base) * 1.75) calc(var(--gg-space-base) * 2.25);
	cursor: pointer;
	list-style: none;
	-webkit-tap-highlight-color: transparent;
}
.gg-disclosure__summary::-webkit-details-marker { display: none; }
.gg-disclosure__summary::marker { content: ''; }
.gg-disclosure__icon {
	flex: none;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	color: var(--gg-on-surface-muted);
}
.gg-disclosure__icon svg { width: 20px; height: 20px; display: block; }
/* The row's own glyph (2026-08-03). Sits between the status rail and the title:
   small, muted, and optically aligned to the words rather than the box, so a
   column of them reads as a quiet margin note and never competes with the rail
   on the left or with the title it belongs to. */
.gg-disclosure__glyph {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 18px;
	height: 18px;
	margin-right: 9px;
	color: var(--gg-on-surface-muted);
	opacity: 0.75;
}
.gg-disclosure__glyph svg { width: 15px; height: 15px; display: block; }
.gg-disclosure[open] .gg-disclosure__glyph,
.gg-disclosure__summary:hover .gg-disclosure__glyph { opacity: 1; }

.gg-disclosure__title {
	flex: 1 1 auto;
	min-width: 0;
	font-size: 15px;
	font-weight: 600;
	color: var(--gg-on-surface);
	letter-spacing: -0.005em;
}
.gg-disclosure__meta { flex: none; }
.gg-disclosure__chev {
	flex: none;
	color: var(--gg-on-surface-muted);
	font-size: 16px;
	line-height: 1;
	transition: transform var(--gg-transition-speed) var(--gg-transition-easing),
	            color var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-disclosure[open] .gg-disclosure__chev { transform: rotate(90deg); color: var(--gg-on-surface); }
.gg-disclosure__body {
	padding: calc(var(--gg-space-base) * 1.5) calc(var(--gg-space-base) * 2.25) calc(var(--gg-space-base) * 2.25);
	border-top: var(--gg-border-width) solid var(--gg-surface-border);
	font-size: 13.5px;
	line-height: 1.6;
	color: var(--gg-on-surface-muted);
}
.gg-disclosure__body > *:first-child { margin-top: 0; }
.gg-disclosure__body > *:last-child { margin-bottom: 0; }
/* --scroll — a disclosure holding a long READ rather than a short section: a
   transcript, a log, a pasted document. Caps the body and scrolls it in place,
   so opening one cannot push everything below it a screen and a half down the
   page. Added 2026-08-09 for the Academy's transcript, which had its own
   <details> styling carrying exactly this cap; a modifier on the shared toggle
   is the difference between a component with a long-content mode and a second
   toggle family. */
.gg-disclosure--scroll > .gg-disclosure__body {
	max-height: 26rem;
	overflow-y: auto;
}

/* ── Accordion (donor: gg-roadmap-v2's .gg-bv2-stage-* stage list in
 *    AdminProjectDetailV2.php + the feed rows in AdminFeed.php — PM, READ
 *    ONLY, never touched). Single-open toggle: <button aria-expanded> +
 *    sibling body; opening one closes every other section in the same
 *    .gg-accordion-list wrapper (donor's exact close-siblings behaviour).
 *    Chevron rotate-on-open matches .gg-entry__chev / .gg-disclosure__chev
 *    for one consistent expand language across all three toggle families.
 *
 *    Render via gg_ui_accordion() (gg-brand/inc/components.php) — one
 *    <section class="gg-accordion"> per call; wrap several in
 *    <div class="gg-accordion-list" data-gg-accordion> so the close-
 *    siblings behaviour (gg-brand/assets/gg-components.js, window.GGAccordion)
 *    can find and scope to them. More than one independent group can exist
 *    on the same page. NOT named .gg-toggle — verified free by grep.
 *
 *    CONTRAST NOTE: renders near-white text — always sits on a dark
 *    surface. */
.gg-accordion-list {
	border-top: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-accordion { border-bottom: var(--gg-border-width) solid var(--gg-surface-border); }
.gg-accordion__head {
	display: flex;
	align-items: center;
	width: 100%;
	gap: calc(var(--gg-space-base) * 1.5);
	padding: calc(var(--gg-space-base) * 1.75) 4px;
	background: transparent;
	border: 0;
	color: inherit;
	font-family: inherit;
	text-align: left;
	cursor: pointer;
	transition: background var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-accordion__head:hover { background: color-mix(in srgb, currentColor 5%, transparent); }
.gg-accordion__head:focus-visible { outline: 2px solid var(--gg-status-info); outline-offset: -2px; }
.gg-accordion__icon {
	flex: none;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 24px;
	height: 24px;
	color: var(--gg-on-surface-muted);
}
.gg-accordion__icon svg { width: 20px; height: 20px; display: block; }
.gg-accordion__title {
	flex: 1 1 auto;
	min-width: 0;
	font-size: 15px;
	font-weight: 600;
	color: var(--gg-on-surface);
	letter-spacing: -0.005em;
}
.gg-accordion__meta { flex: none; }
.gg-accordion__chev {
	flex: none;
	color: var(--gg-on-surface-muted);
	font-size: 16px;
	line-height: 1;
	transition: transform var(--gg-transition-speed) var(--gg-transition-easing),
	            color var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-accordion[data-open="true"] .gg-accordion__chev { transform: rotate(90deg); color: var(--gg-on-surface); }
.gg-accordion__body {
	overflow: hidden;
	max-height: 0;
	transition: max-height var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-accordion[data-open="true"] .gg-accordion__body { max-height: 4000px; }
.gg-accordion__body-inner {
	padding: 4px 4px calc(var(--gg-space-base) * 2.25) 4px;
	font-size: 13.5px;
	line-height: 1.6;
	color: var(--gg-on-surface-muted);
}
.gg-accordion__body-inner > *:first-child { margin-top: 0; }
.gg-accordion__body-inner > *:last-child { margin-bottom: 0; }

/* ── Row action button (donor: gg-inbox's .gg-inbox-pending-btn add/ignore
 *    pair — READ, never lifted its file). A small row-level action button
 *    for edit / delete / approve / ignore, three moods: neutral (default —
 *    muted, currentColor-derived border like .gg-pill), primary (the row's
 *    affirmative action — --gg-status-info, the same blue this file already
 *    uses for "interactive/confirm" everywhere else), danger (destructive —
 *    --gg-status-danger). Background/border derive from currentColor so
 *    each variant only sets `color`, same recipe as .gg-pill.
 *
 *    Render via gg_ui_row_btn() (gg-brand/inc/components.php). New free
 *    class name — verified by grep across wp-content (not .gg-toggle's
 *    problem, but checked all the same). */
.gg-row-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	font-family: var(--gg-font-body);
	font-size: 10.5px;
	font-weight: 700;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	line-height: 1.4;
	padding: 6px 12px;
	background: transparent;
	color: var(--gg-on-surface-muted);
	border: var(--gg-border-width) solid color-mix(in srgb, currentColor 35%, transparent);
	border-radius: 0;
	cursor: pointer;
	transition: background var(--gg-transition-speed) var(--gg-transition-easing),
	            border-color var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-row-btn:hover {
	background: color-mix(in srgb, currentColor 10%, transparent);
	border-color: color-mix(in srgb, currentColor 55%, transparent);
}
.gg-row-btn:focus-visible { outline: 2px solid var(--gg-status-info); outline-offset: 2px; }
.gg-row-btn--primary { color: var(--gg-status-info); }
.gg-row-btn--danger  { color: var(--gg-status-danger); }
.gg-row-btn:disabled,
.gg-row-btn.is-disabled {
	opacity: 0.4;
	cursor: not-allowed;
	pointer-events: none;
}

/* .gg-row-btn--add — full-width dashed "+" affordance (backlog #16).
   Overrides the compact uppercase-pill look above: this variant reads as
   an empty slot inviting a click, not a small action on an existing row.
   Donor: gg-roadmap-v2's .gg-rv2-pin-add-btn (READ-ONLY). Render via
   gg_ui_row_btn( [ 'variant' => 'add', 'label' => 'Add note' ] ). */
.gg-row-btn--add {
	display: flex;
	width: 100%;
	justify-content: flex-start;
	gap: 10px;
	min-height: 44px;
	padding: 10px 14px;
	font-size: 12.5px;
	font-weight: 500;
	letter-spacing: 0.02em;
	text-transform: none;
	background: transparent;
	color: var(--gg-on-surface-muted);
	border: var(--gg-border-width) dashed var(--gg-surface-border);
}
.gg-row-btn--add:hover {
	background: color-mix(in srgb, var(--gg-on-surface) 4%, transparent);
	border-color: color-mix(in srgb, var(--gg-on-surface) 32%, transparent);
	color: var(--gg-on-surface);
}
.gg-row-btn--add svg { width: 16px; height: 16px; flex-shrink: 0; }

/* .gg-page-bg — nature/photo page backdrop + dark scrim (backlog #10).
   Layout-agnostic on purpose: only the background-image/scrim recipe lives
   here, not margin/padding/min-height, so a page keeps its own wrap class
   for those and adds .gg-page-bg alongside it. Render via
   gg_ui_page_bg( \$image_url ) in gg-brand/inc/components.php — echoes the
   opening <div>, caller closes with a plain </div>. */
/* The full-bleed recipe below pulls out by `calc(50% - 50vw)`, and that 50% is
 * half the PARENT's width, not half the viewport. It only lands on the edge of
 * the screen if the parent is centred. WordPress's own `.wrap` is not: it
 * carries 2px left against 20px right on a desktop admin screen, 0 against 12px
 * on a phone. A .gg-page-bg inside one is therefore displaced by half that
 * difference — measured on Page Review, 9px at 1440 and 6px at 390, showing as
 * a strip of WP grey down the right-hand edge. That is the exact seam the
 * full-bleed exists to remove, reintroduced by the box it sits in.
 * Squaring the host up is what makes the pull-out exact, so do that rather than
 * try to out-guess WP's margins a second time. (2026-08-16, from the code
 * review of the page-gutter change.) */
.wrap:has(.gg-page-bg) {
	margin-left: 0;
	margin-right: 0;
}

.gg-page-bg {
	/* FULL BLEED (2026-08-03, Sam: "some backgrounds aren't full width, or full
	   height, or have weird borders").
	   Measured across nine grid pages: seven sat 22px short of the viewport and
	   two ran 40px past it, so every page had a seam down one edge or a sliver
	   of scrollbar-coloured gap. The cause is WP admin's own chrome — #wpcontent
	   carries a left padding for the collapsed menu rail, and .wrap adds a right
	   margin — so a backdrop sized to its parent is always wrong by whatever
	   those happen to be. Pulling it out to the viewport edge on both sides
	   makes it independent of both, and min-height covers the short-page case
	   where the photo stopped where the content did. */
	--gg-page-bg-scrim: rgba(0, 0, 0, 0.45);
	position: relative;
	width: 100vw;
	max-width: 100vw;
	margin-left: calc(50% - 50vw);
	margin-right: calc(50% - 50vw);
	/* AND FLUSH TO THE TOP (2026-08-04, Sam: "most of the pages have a weird
	   line at the top of them where the background image stops, before the
	   header"). Measured: any page whose shell also carries WordPress's own
	   `.wrap` class inherited its `margin-top: 10px`, so the backdrop started
	   10px down and WP admin's body colour — rgb(240,240,240), light grey —
	   showed through as a bright band across the top of a navy page. The two
	   pages without `.wrap` sat flush, which is why it looked like "most" and
	   not "all". Same class of bug as the full-bleed fix above: WP chrome
	   leaking into a surface that should not know it exists. */
	margin-top: 0;
	/* AND A GUTTER BACK IN (2026-08-16, Sam: "the grid should always have
	   padding left and right, even when the page is made smaller").
	   The header above says this class stays layout-agnostic and leaves
	   margin/padding to the page's own wrap class. That held right up until
	   the window got narrow. Measured at 390px and 600px: the board grid — the
	   shape behind Project Manager, Roadmap, Assets, Listings, Outreach, Social
	   and six more — ran flush against both edges of the screen, cards welded
	   to the glass, with the floating Assistant button sitting on top of the
	   last one. The pages were not careless: .gg-rv2-body holds itself in with
	   `max-width: 1220px; margin: 0 auto`, which is a CEILING. A ceiling holds
	   nothing once the window is narrower than it is, and none of them had a
	   floor underneath.
	   The floor belongs here rather than on each page, because this is the one
	   box on the platform that knows where the edge of the screen is — it is
	   pulled out to the viewport on purpose, six lines up. A page that genuinely
	   wants a child to bleed past the gutter cancels it by naming it, the same
	   way .gg-page-column-pad-x is cancelled by the masthead. */
	padding-left: var(--gg-page-gutter);
	padding-right: var(--gg-page-gutter);
	min-height: calc(100vh - var(--gg-admin-bar-h, 32px));
	box-sizing: border-box;
	background-color: var(--gg-surface-panel);
	background-size: cover;
	background-position: center top;
	background-attachment: fixed;
	background-repeat: no-repeat;
}
.gg-page-bg::before {
	content: '';
	position: fixed;
	inset: 0;
	background: var(--gg-page-bg-scrim);
	pointer-events: none;
	z-index: 0;
}
.gg-page-bg > * {
	position: relative;
	z-index: 1;
}

/* .gg-page-bg--column + .gg-page-column — THE reading-column page shape
   (Sam, 2 Aug): "navy in the middle, and still navy outside that, but
   slightly transparent". The wrapper is the ordinary .gg-page-bg backdrop
   run full-bleed; the column is one solid navy child sitting on top of the
   scrim. Two different jobs — the column is for reading, the surround is for
   placing the page in the platform.
   The shared Project page opens with this, so every project type inherits
   the same backdrop as the grid it was opened from. It used to paint the
   photograph on #wpwrap behind a body class that was never printed, so an
   Offer's own project page and a board project's showed no photograph at
   all. Nothing here is page-specific: a page adds the classes, it does not
   restate the recipe. */
.gg-page-bg--column {
	/* The platform scrim, not .gg-page-bg's generic black — the page asks for
	   the shape and gets the whole shape, rather than naming a colour itself. */
	--gg-page-bg-scrim: var(--gg-page-scrim);
	/* Full bleed — without it WP's grey admin gutter runs down the side of the
	   photograph. This used to copy the board's `margin: 0 -20px`, which is a
	   guess at how wide WP's gutter happens to be: right on a wide screen,
	   20px PAST the edge of the screen once WP drops that gutter below 782px.
	   .gg-page-bg above already had the measured recipe — pull out by the
	   difference between this box and the viewport, which is exact at every
	   width — so use that rather than keep a second, wrong version of it. */
	width: 100vw;
	max-width: 100vw;
	margin-left: calc(50% - 50vw);
	margin-right: calc(50% - 50vw);
	min-height: calc(100vh - 32px);
}
.gg-page-column {
	box-sizing: border-box;
	max-width: 860px;
	margin-left: auto;
	margin-right: auto;
	padding: var(--gg-page-column-pad-y) var(--gg-page-column-pad-x) 64px;
	background: var(--gg-page-column-bg);
	color: var(--gg-on-surface);
}

/* A --wide variant was built and removed on 9 Aug: it was never duplicated and
   nothing adopted it. gg-connections needs its whole page shell migrating, not
   a wider column; see the Advance Plan note. */

/* .gg-video — THE 16:9 media frame (2026-08-09).
   One box meaning "a video plays here", whichever thing draws the video. The
   recipe — a responsive 16:9 box whose content fills it edge to edge — had been
   written out independently four times: gg-course-style's .gg-cs-video
   (assets/css/module.css:223), that plugin's second copy for its module-builder
   preview (gg-course-style.php:211), gg-roadmap-v2's .gg-rv2-tg-video scope
   (src/AdminRoadmapV2.php:8092) and the Academy's own .gg-academy-player, which
   existed only because gg-course-style's stylesheet does not load on the Academy
   page. This is the shared definition. The Academy reads it and ships no copy;
   the three older copies stay where they are for now, each being read by a
   plugin outside that slice's fence, and are the obvious next de-duplication.

   Deliberately tolerant about what sits inside it, because the two things that
   land here are not the same shape: a bare <iframe>, and the wrapper <div> an
   embed helper returns with the iframe inside THAT. Both fill the box.

   Sharp corners, like every other surface in this file. */
.gg-video {
	position: relative;
	width: 100%;
	aspect-ratio: 16 / 9;
	overflow: hidden;
	background: var(--gg-media-backing);
	box-shadow: var(--gg-shadow-lg);
}
.gg-video > *,
.gg-video iframe,
.gg-video video {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border: 0;
}

/* .gg-prevnext — THE previous/next pager (2026-08-09).
   Two cards at the foot of an item screen: where the reader came from on the
   left, where they are going on the right, each NAMING the thing rather than
   saying only "Next", so the choice can be made without clicking it. Either
   side can be absent — a first or last item keeps the surviving card in its own
   half instead of letting it slide across the page.

   Nothing on the platform carried this before; the Academy's video page was the
   only screen with a pager and it had its own. It goes here rather than there
   because a course lesson, a project stage and a client record all want the
   same part. Render via gg_ui_prevnext() (gg-brand/inc/components.php).

   CONTRAST NOTE (same caveat as .gg-disclosure/.gg-entry-list above): renders
   near-white text — always sits on a dark surface. */
.gg-prevnext {
	display: flex;
	justify-content: space-between;
	gap: calc(var(--gg-space-base) * 2);
	margin-top: calc(var(--gg-space-base) * 3.5);
}
.gg-prevnext__link {
	display: flex;
	flex-direction: column;
	gap: calc(var(--gg-space-base) * 0.25);
	max-width: 48%;
	padding: calc(var(--gg-space-base) * 1.5) calc(var(--gg-space-base) * 2);
	background: var(--gg-surface-card);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	color: var(--gg-on-surface);
	text-decoration: none;
	transition: border-color var(--gg-transition-speed) var(--gg-transition-easing),
	            background var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-prevnext__link:hover,
.gg-prevnext__link:focus-visible {
	border-color: color-mix(in srgb, var(--gg-status-info) 55%, transparent);
	color: var(--gg-on-surface);
}
/* The "going to" card sits hard against the right edge whether or not it has a
   sibling — a last item's "Previous" must not drift into the middle. */
.gg-prevnext__link--next {
	margin-left: auto;
	text-align: right;
	align-items: flex-end;
}
.gg-prevnext__dir {
	font-size: 11.5px;
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--gg-on-surface-muted);
}
.gg-prevnext__title {
	font-size: 13.5px;
	font-weight: 600;
	line-height: 1.35;
}
@media (max-width: 640px) {
	.gg-prevnext { flex-direction: column; }
	.gg-prevnext__link,
	.gg-prevnext__link--next {
		max-width: none;
		margin-left: 0;
		text-align: left;
		align-items: flex-start;
	}
}

/* .gg-drag-grip — six-dot drag-to-REORDER handle (backlog #15). Distinct
   from the RESIZE grip .gg-grip (gg-rcp/assets/gg-chat.css) which drags a
   panel's width/height, not a list order. Donor: gg-roadmap-v2's
   .gg-rv2-grip (READ-ONLY). Render via gg_ui_drag_grip() in
   gg-brand/inc/components.php; wire as a SortableJS handle: selector. */
.gg-drag-grip {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 22px;
	height: 20px;
	padding: 0;
	color: var(--gg-on-surface-muted);
	background: transparent;
	border: none;
	cursor: grab;
	user-select: none;
	-webkit-user-select: none;
	touch-action: none;
	transition: color var(--gg-transition-speed) var(--gg-transition-easing);
}
.gg-drag-grip svg { width: 12px; height: 18px; display: block; }
.gg-drag-grip:hover { color: var(--gg-on-surface); }
.gg-drag-grip:active,
.sortable-chosen .gg-drag-grip { cursor: grabbing; color: var(--gg-status-warn); }
.gg-drag-grip--rotated { transform: rotate(90deg); }

/* ════════════════════════════════════════════════════════════════════
 * FIELD BOX — the Offer-Clarity autogrow field (Playbook restyle).
 * Donor: gg-roadmap-v2's .gg-bv2-field family (READ-ONLY) — lifted here,
 * DE-SCOPED off body.gg-boardroom-layout--v2. Dark variant kept. Render via
 * gg_ui_field_box() (components.php); wire via assets/gg-field-box.js.
 * ════════════════════════════════════════════════════════════════════ */
.gg-bv2-fields {
	display: flex;
	flex-direction: column;
	gap: 18px;
	margin: 0 0 22px;
	max-width: none;
}
.gg-bv2-field {
	display: flex;
	flex-direction: column;
}
.gg-bv2-field-label-row {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-bottom: 8px;
}
/* THE ROW'S OWN GLYPH ON AN OPEN FIELD (2026-08-18, Sam: "each one should have
   an icon, like the toggles do below"). Deliberately the same size, colour and
   weight as .gg-disclosure__glyph, because it means the same thing one line up:
   what this row is about. A collapsed row keeps carrying its glyph on the
   toggle summary and never draws this one. */
.gg-bv2-field-glyph {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 18px;
	height: 18px;
	color: var(--gg-on-surface-muted, var(--ggb-text-soft, rgba(255,255,255,0.66)));
	opacity: 0.75;
}
.gg-bv2-field-glyph svg { width: 15px; height: 15px; display: block; }
/* A noted row sends its label to the end so the description reads first; the
   glyph belongs WITH the label, so it follows it rather than staying put. */
.gg-bv2-field-label-row--noted > .gg-bv2-field-glyph { order: 98; }
.gg-bv2-field-label {
	font: 700 11px/1 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	letter-spacing: 0.10em;
	text-transform: uppercase;
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
	margin: 0;
}
/* THE HELP MARK (2026-08-24, Sam: "some tooltips on the platform are wrong
   logo").

   Four pictures used to mean "there is an explanation here": a filled grey dot
   with a text "?", an outlined circled-question, a text "?" inside a round
   border on decision options, and a square "?" button with its own gold
   popover on the Offer Details tab. The last one could not render bold, blank
   lines or a list, so identical copy read differently depending on which mark
   happened to be drawn.

   There is one mark now. Its shape and size live with the tooltip engine
   (.gg-tip-icon in the gg-tooltips plugin), which inherits its colour so the
   mark is correct on a light admin screen and on a dark PM screen alike. What
   belongs HERE is the brand colour it takes on hover, and nothing else. */
.gg-tip-icon:hover,
.gg-tip-icon:focus-visible {
	color: var(--ggb-accent-2, #fbc24e);
	opacity: 1;
	outline: none;
}

.gg-bv2-field-status {
	font: 600 10px/1 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ggb-muted, rgba(255,255,255,0.55));
	margin-left: auto;
	min-height: 12px;
}
.gg-bv2-field-status.is-saving { color: var(--ggb-accent-2, #fbc24e); }
.gg-bv2-field-status.is-saved  { color: #4ade80; }
.gg-bv2-field-status.is-error  { color: #f87171; }
@keyframes ggBv2FieldFilled {
	0%   { background: rgba(251,194,78,0.22); border-color: var(--ggb-accent-2, #fbc24e); box-shadow: 0 0 0 3px rgba(251,194,78,0.18); }
	100% { background: rgba(255,255,255,0.04); border-color: var(--ggb-border, rgba(255,255,255,0.12)); box-shadow: none; }
}
.gg-bv2-field-input.gg-bv2-field-filled {
	animation: ggBv2FieldFilled 1.6s ease-out;
}
.gg-bv2-field-input {
	width: 100%;
	padding: 11px 14px;
	border: 1px solid var(--ggb-border, rgba(255,255,255,0.12));
	border-radius: 0;
	background: rgba(255,255,255,0.04);
	color: var(--ggb-text, rgba(255,255,255,0.92));
	font: 400 14px/1.5 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	box-sizing: border-box;
	transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
	-webkit-appearance: none;
	appearance: none;
	resize: vertical;
}
/* WP admin core forces a light background on input[type=text|password] and a
 * grey one on input[readonly] (specificity 0,1,1 — it beats the .gg-bv2-field-
 * input class). Raise our specificity to keep text / password / readonly field-
 * boxes dark everywhere the component renders as an <input>. */
input.gg-bv2-field-input,
input.gg-bv2-field-input[readonly] {
	background: rgba(255,255,255,0.04);
	color: var(--ggb-text, rgba(255,255,255,0.92));
	border-color: var(--ggb-border, rgba(255,255,255,0.12));
}
input.gg-bv2-field-input:focus {
	border-color: var(--ggb-accent, #f5a623);
}
.gg-bv2-field-input:hover {
	border-color: rgba(255,255,255,0.22);
}
.gg-bv2-field-input:focus {
	outline: none;
	border-color: var(--ggb-accent, #f5a623);
	background: rgba(255,255,255,0.07);
	box-shadow: 0 0 0 2px rgba(245,166,35,0.20);
}
.gg-bv2-field-input::placeholder {
	color: rgba(255,255,255,0.28);
	opacity: 1;
	white-space: pre-wrap;
	font-style: italic;
}
/* Field-box choice shapes (2026-07-23): select keeps the input skin; the
 * checkbox / radio group is a bordered stack matching the input footprint. */
/* The select (2026-08-03, Sam: "when you hover over a dropdown box ... it goes
   dark so you cant read it. and the dropdown is not brand styled properly").
   Two separate faults:
     1. appearance:none stripped the arrow and nothing replaced it, so the
        control read as a plain box with no sign it opened anything.
     2. The open list is drawn by the browser, and on a dark option background
        its highlight came out dark-on-dark. An option list needs its selected
        and hovered states stated explicitly or the OS picks, and picks badly.
   The arrow is an inline SVG in the brand's own ink so it needs no asset and
   inherits nothing from WP admin. */
select.gg-bv2-field-input {
	background-color: rgba(255,255,255,0.04);
	background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-opacity='0.55' stroke-width='2.2' stroke-linecap='square'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: right 12px center;
	background-size: 15px 15px;
	padding-right: 38px;
	color: var(--ggb-text, rgba(255,255,255,0.92));
	cursor: pointer;
	/* THE BORDER HAS TO BE SAID OUT LOUD TOO (2026-08-20, Sam: "Some dropdown
	   boxes still have a white border - they shouldn't, should follow normal
	   brand styles").

	   Exactly the fault documented in the rule below this one, on a different
	   property. .gg-bv2-field-input sets the border with a single class, WP
	   admin core styles `select` with its own, and core wins - so on a client's
	   page the three dropdowns measured rgb(148,148,148) while every input and
	   the multiselect beside them were rgba(255,255,255,0.12). One pale outline
	   in a column of faint ones, on the same row.

	   STATED ON THE SELECT, NOT MOVED IN THE TOKEN. --ggb-border and
	   --gg-control-border are worn by other things - the featured image tile
	   among them - and shifting either to fix a dropdown would drag them with
	   it. This raises specificity for selects and touches nothing else. */
	border-color: var(--ggb-border, rgba(255,255,255,0.12));
}
/* COLOUR, NOT JUST BACKGROUND (2026-08-13, Sam: "hovering over dropdown field
   makes the text become blank/unreadable").
   WP admin core carries `select:hover { color: #1e1e1e }`, which ties on
   specificity with our own `select.gg-bv2-field-input` and wins on order — so
   the moment a coach moved the mouse over a dropdown its text went near-black
   on a dark box and vanished. Stating the colour on the hovered and focused
   states raises us to 0,2,1 and settles it. Same fault as the option-list
   highlight fixed on 3 Aug, one level up: a state nobody stated, chosen for us
   by somebody else's stylesheet. */
select.gg-bv2-field-input:hover,
select.gg-bv2-field-input:focus {
	background-color: rgba(255,255,255,0.07);
	color: var(--ggb-text, rgba(255,255,255,0.92));
}
select.gg-bv2-field-input option {
	background: #1b2637;
	color: var(--ggb-text, rgba(255,255,255,0.92));
}
/* Say the highlight explicitly, in both directions, so neither the OS default
   nor a dark option background can leave the text unreadable. */
select.gg-bv2-field-input option:hover,
select.gg-bv2-field-input option:focus,
select.gg-bv2-field-input option:checked {
	background: var(--ggb-accent, #f5a623);
	color: #0a1628;
	font-weight: 600;
}
/* OPTION SPACING (2026-08-13, Sam: "the radio button options are too far
   apart"). 8px between rows of a 21px line-height read as a list of separate
   things rather than one set to choose from — the eye had to travel between
   answers to the same question. 3px keeps them touching without crowding. */
.gg-bv2-field-checks {
	display: flex;
	flex-direction: column;
	gap: 3px;
	padding: 9px 14px;
	border: 1px solid var(--ggb-border, rgba(255,255,255,0.12));
	background: rgba(255,255,255,0.04);
}
.gg-bv2-field-check {
	display: flex;
	align-items: center;
	gap: 10px;
	margin: 0;
	color: var(--ggb-text, rgba(255,255,255,0.92));
	font: 400 14px/1.5 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	cursor: pointer;
}
.gg-bv2-field-check input {
	margin: 0;
	flex: 0 0 auto;
}
/* A DECISION, NOT A SETTING (2026-08-09).
   Where a question hides a real choice, each option says what you get, what it
   costs and who it suits — so a coach can locate themselves instead of weighing
   three bare names. Same list, same control, more room. Sharp corners, shared
   tokens, no new colour. */
/* The declared length, immediately above the box. Quiet, because it is a
   steer and not an instruction. */
.gg-bv2-field-length {
	margin: 0 0 6px;
	font-size: 12px;
	line-height: 1.4;
	letter-spacing: .01em;
	color: var(--ggb-text-soft, rgba(255,255,255,0.55));
}
.gg-bv2-field-narrowing {
	margin: 0 0 10px;
	font: 500 14px/1.5 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-text, rgba(255,255,255,0.92));
}
.gg-bv2-field-checks--decision { gap: 2px; padding: 6px; }
/* A decision option used to carry three labelled blocks of explanation, so it
   needed 12px of breathing room top and bottom. Those moved into the tooltip on
   10 Aug and the padding never came down with them — leaving five one-line
   options spread over 250px of page (2026-08-13, Sam: "the radio button options
   are too far apart"). Sized for what the row actually holds now. */
.gg-bv2-field-check--decision {
	align-items: center;
	gap: 10px;
	padding: 5px 10px;
	border: 1px solid transparent;
}
.gg-bv2-field-check--decision:hover { background: rgba(255,255,255,0.04); }
.gg-bv2-field-check--decision:has(input:checked) {
	border-color: var(--ggb-accent, #f5a623);
	background: rgba(255,255,255,0.05);
}
/* Centred with its label now the row is a single line. */
.gg-bv2-field-check--decision input { margin-top: 0; }
.gg-bv2-field-check-body { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.gg-bv2-field-check-name {
	font-weight: 600;
	color: var(--ggb-text, rgba(255,255,255,0.92));
}
.gg-bv2-field-check-tradeoff {
	display: flex;
	gap: 8px;
	font-size: 13px;
	line-height: 1.45;
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
}
.gg-bv2-field-check-tradeoff-label {
	flex: 0 0 96px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: .04em;
	text-transform: uppercase;
	padding-top: 2px;
	color: var(--ggb-text-soft, rgba(255,255,255,0.5));
}
.gg-bv2-field-check-tradeoff-text { flex: 1 1 auto; min-width: 0; }
@media (max-width: 640px) {
	.gg-bv2-field-check-tradeoff { flex-direction: column; gap: 1px; }
	.gg-bv2-field-check-tradeoff-label { flex: 0 0 auto; padding-top: 0; }
}
textarea.gg-bv2-field-input::placeholder {
	white-space: pre-wrap;
	line-height: 1.5;
}
textarea.gg-bv2-field-input {
	min-height: 88px;
	line-height: 1.55;
	font-family: var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
}
.gg-bv2-field-autogrow {
	overflow: hidden;
	resize: vertical;
	min-height: calc(1.5em * 4 + 22px);
}
.gg-bv2-field-help-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 5px;
	padding: 3px 9px;
	color: var(--ggb-accent, #f98a09);
	background: color-mix(in srgb, var(--ggb-accent, #f98a09) 12%, transparent);
	border: 1px solid color-mix(in srgb, var(--ggb-accent, #f98a09) 48%, transparent);
	border-radius: 0;
	cursor: pointer;
	position: relative;
	flex: 0 0 auto;
	transition: background .14s, border-color .14s, color .14s;
}
.gg-bv2-field-help-btn:hover {
	color: #fff;
	background: color-mix(in srgb, var(--ggb-accent, #f98a09) 80%, transparent);
	border-color: var(--ggb-accent, #f98a09);
}
.gg-bv2-field-help-btn:focus-visible {
	outline: 2px solid var(--ggb-accent, #f98a09);
	outline-offset: 2px;
}
.gg-bv2-field-help-btn svg { width: 12px; height: 12px; display: block; flex: 0 0 auto; }
.gg-bv2-field-help-tip {
	font: 600 11px/1 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	letter-spacing: 0.02em;
	white-space: nowrap;
	color: inherit;
}
/* "Draft this for me" — a small filled-accent chip in the field's label row,
   distinct from the outline "Discuss with assistant" button. Sharp corners per
   brand rule. Rendered by gg_ui_field_box()'s opt-in `draft_btn` argument;
   lifted here verbatim from gg-roadmap-v2's inline <style> (de-duplication,
   2026-07-29) so the shared component owns the whole field box's look. */
.gg-bv2-field-draft-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 5px;
	padding: 3px 9px;
	color: #fff;
	background: color-mix(in srgb, var(--ggb-accent, #f98a09) 82%, transparent);
	border: 1px solid var(--ggb-accent, #f98a09);
	border-radius: 0;              /* sharp corners per brand rule */
	cursor: pointer;
	flex: 0 0 auto;
	transition: background .14s, border-color .14s, color .14s, opacity .14s;
}
.gg-bv2-field-draft-btn:hover { background: var(--ggb-accent, #f98a09); }
.gg-bv2-field-draft-btn:focus-visible {
	outline: 2px solid var(--ggb-accent, #f98a09);
	outline-offset: 2px;
}
.gg-bv2-field-draft-btn[disabled] { opacity: .6; cursor: default; }
/* HIDDEN MUST WIN (2026-08-19). The binder hides this chip on any surface that
   did not give it a draft endpoint (see wireDraftButton in gg-field-box.js) -
   its whole point is that a control which does nothing on click never reaches a
   coach. It was still on screen: display:inline-flex above beats the browser's
   own [hidden] rule, so `btn.hidden = true` changed nothing at all. Found on the
   client page, where five details drew a bright orange "Draft this for me" that
   was dead on arrival; it was true of every such surface. Same class of fault as
   the wrapped removal button in gg-shepherd, and the same one-line fix. */
.gg-bv2-field-draft-btn[hidden] { display: none; }
.gg-bv2-field-draft-btn svg { width: 12px; height: 12px; display: block; flex: 0 0 auto; }
.gg-bv2-field-draft-tip {
	font: 600 11px/1 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	letter-spacing: 0.02em;
	white-space: nowrap;
	color: inherit;
}
/* WHAT THE BOX IS SHOWING, IN WORDS (2026-08-08). A drafted value looks
   exactly like a typed one, which is the whole risk: a machine's first pass
   wearing the same clothes as an answer the coach thought about. So the line
   under the box says which it is, every time — and the same line is where the
   generator says it does not know enough yet, or that the answer is locked.
   Written by the shared binder (assets/gg-field-box.js); nothing is baked into
   the saved value, so there is never a marker for a coach to delete by hand.
   Empty renders as nothing: no reserved gap under an untouched field. */
.gg-bv2-field-draft-note {
	font: 400 12px/1.5 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-muted, rgba(255,255,255,0.55));
	margin: 8px 0 0;
	padding-left: 9px;
	border-left: 2px solid transparent;
}
.gg-bv2-field-draft-note:empty { display: none; }
.gg-bv2-field-draft-note.is-working { color: var(--ggb-accent-2, #fbc24e); }
.gg-bv2-field-draft-note.is-draft {
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
	border-left-color: var(--ggb-accent, #f98a09);
}
.gg-bv2-field-draft-note.is-warn {
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
	border-left-color: var(--ggb-accent-2, #fbc24e);
}

/* ── APPROVE / LOCK / REOPEN — the third state of an answer (2026-08-08) ──
 *
 * A field used to be filled or empty. APPROVED is the state that says the
 * coach has settled it, and approved IS locked: the box goes read-only and
 * nothing writes over it silently.
 *
 * NO NEW COMPONENT IS INVENTED HERE. The chip is the platform's .gg-pill
 * (--ok, --dot) and the actions are gg_ui_row_btn(), both straight off the
 * Design System page. Everything below is the strip's own layout plus what
 * "locked" looks like on the input — nothing that duplicates an existing
 * class. Rendered by gg_ui_field_box()'s opt-in `approve` argument; wired by
 * assets/gg-field-box.js. Lives in gg-brand so every interview surface
 * inherits one look, and a change here changes all of them at once.
 *
 * The lock is deliberately QUIET: a muted border and a slightly recessed
 * fill, not a warning colour. A settled answer is a good outcome, so it must
 * not read as an error or a disabled/broken control. */
.gg-bv2-field-approval {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 10px;
	margin-top: 10px;
}
.gg-bv2-field-approval-note {
	font: 400 12px/1.5 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-muted, rgba(255,255,255,0.55));
	margin-right: auto;   /* the actions sit at the far edge of the row */
}
.gg-bv2-field-approval-status {
	font: 600 10px/1 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ggb-muted, rgba(255,255,255,0.55));
	min-height: 12px;
}
.gg-bv2-field-approval-status.is-working { color: var(--ggb-accent-2, #fbc24e); }
.gg-bv2-field-approval-status.is-error   { color: #f87171; }

/* The locked box. Read-only is enforced on the server and by the binder; this
   is only how it LOOKS, so a coach can tell at a glance which answers are
   settled without opening any of them. */
.gg-bv2-field--locked .gg-bv2-field-input,
.gg-bv2-field--locked input.gg-bv2-field-input,
.gg-bv2-field--locked input.gg-bv2-field-input[readonly] {
	background: rgba(255,255,255,0.02);
	border-style: dashed;
	border-color: color-mix(in srgb, var(--gg-status-ok, #4ade80) 42%, transparent);
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
	cursor: default;
}
.gg-bv2-field--locked .gg-bv2-field-input:focus {
	box-shadow: none;
	background: rgba(255,255,255,0.02);
	border-color: color-mix(in srgb, var(--gg-status-ok, #4ade80) 42%, transparent);
}
.gg-bv2-field--locked .gg-bv2-field-checks {
	opacity: 0.75;
}
/* A locked field is settled, so the two "help me write this" affordances have
   nothing left to do on it. Hidden rather than removed in PHP so reopening in
   the browser brings them straight back with no re-render. */
.gg-bv2-field--locked .gg-bv2-field-draft-btn { display: none; }

/* ── CREATION FORM — the frame that says "this is a task with an end" ─────
 *
 * (2026-08-23.) A project page carries two kinds of field and drew them
 * identically: the ones a coach keeps for good, and the ones that exist only to
 * be handed to a builder. This is the frame around the second kind.
 *
 * NO NEW FURNITURE. Same hairline, same recessed fill and same padding as the
 * review gate above — they are the same idea at two moments, and a second frame
 * shape would say they were different things. What is new here is only the
 * inset the fields sit in, so the rows read as being INSIDE something rather
 * than continuing the page.
 *
 * Rendered by gg_ui_creation_form() (components.php). */
/* ITS OWN GROUND (2026-08-24, Sam: "create form could still look slightly
   different background ... looks too similar to bit above"). The panel sat on
   the same near-invisible tint as everything else on the page, so the frame was
   doing all the work on its own. A darker well and a slightly brighter edge
   separate it without introducing a colour: the page is a reading column, this
   is a thing you are filling in. */
.gg-creation {
	border: 1px solid var(--ggb-line, rgba(255,255,255,0.22));
	background: rgba(0,0,0,0.20);
	padding: 24px 22px;
	margin: 0 0 28px;
	box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
}
/* CENTRED (2026-08-24, Sam). The heading names the thing being made and the
   line under it says the fields are temporary — both are about the panel as a
   whole rather than about the row under them, and the button they lead to is
   centred too. */
.gg-creation__head {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-wrap: wrap;
	gap: 12px;
	margin: 0 0 8px;
	text-align: center;
}
.gg-creation__title { margin: 0; }
.gg-creation__badge { margin: 0; }
.gg-creation__lead {
	font: 400 13px/1.6 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
	margin: 0 auto 18px;
	max-width: 60ch;
	text-align: center;
}
/* The rows sit in a well inside the frame. A hairline at the top only: enough
   to say the list belongs to the panel, not enough to draw a second box inside
   the first one. */
.gg-creation__body {
	border-top: 1px solid var(--ggb-line, rgba(255,255,255,0.16));
	padding-top: 18px;
}
.gg-creation__body > .gg-ofp-section { padding: 0; margin: 0; }
/* The button belongs to this form, so it sits inside the frame with a rule
   above it rather than floating under the panel as a page-level action. */
.gg-creation__action { margin-top: 4px; }
.gg-creation__action .gg-big-action { margin: 0; padding-bottom: 0; }
/* A creation form with nothing to fill in is a real state, not a bug: some
   things are made from what is already at the top of the page. The button then
   sits directly under the line explaining what will be made. */
.gg-creation--open .gg-creation__lead:last-of-type { margin-bottom: 14px; }
.gg-creation--built .gg-creation__lead { margin-bottom: 16px; }
/* THE DOOR ONTO THE REAL THING, and on a finished panel the only thing in it
   (2026-08-24, Sam). Centred, because it is the one action the panel is now
   for — the same placement every other decisive button on the platform gets.
   One per thing the build made: several is the right answer for a funnel that
   made two pages and a sequence. */
.gg-creation__links {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	justify-content: center;
	margin: 0;
}
.gg-creation__links .gg-btn { display: inline-flex; align-items: center; gap: 10px; }
.gg-creation__links .gg-btn svg { width: 17px; height: 17px; flex: 0 0 auto; }
/* A FINISHED PROJECT IS NOT A FORM (2026-08-24, Sam: "the View this campaign
   button doesn't need to be inside a box").
   The built state used to stand in the Creation Form's own frame, so a coach
   arriving at something already made met a bordered box with a link inside it
   and nothing saying what had happened. That frame belongs to a form being
   filled in. What is left is a line saying the thing exists and the door onto
   it, centred on the page with no box at all. It borrows .gg-creation__links
   above, which is already the one rule for "the door onto the real thing" and
   is centred for exactly this. */
.gg-project-built { text-align: center; margin: 0 0 28px; }
.gg-project-built__said {
	font: 400 14px/1.6 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
	margin: 0 0 14px;
}

/* With no heading and no lead above it, the panel is the button plus the record
   line, so it needs its own breathing room rather than a title's. */
.gg-creation--built { padding: 26px 22px 20px; }
.gg-creation--built .gg-creation__head { display: none; }
.gg-creation--built .gg-creation__record { text-align: center; }
.gg-creation--built .gg-creation__rec-body { text-align: left; max-width: 68ch; margin: 0 auto; }
/* AND THE SAME INSIDE THE NEWER BUILT STATE (2026-08-25). .gg-project-built
   replaced .gg-creation--built as where a finished project's record sits, and
   it centres everything for the line and the button above - correct for those,
   and wrong the moment there is prose underneath. Email Marketing now reads a
   coach's own emails back into this slot, and four paragraphs centred on the
   page cannot be read. The rule that fixed it for the old container follows it
   into the new one rather than being written again next to it. */
.gg-project-built .gg-creation__rec-body { text-align: left; max-width: 68ch; margin: 0 auto; }
/* THE RECORD OF WHAT WAS ASKED FOR — prose, not a form with its controls taken
   out (2026-08-24, Sam: "the form shouldn't be there after the button has been
   submitted"). No row, no box, no icon, no chevron: a label and the words under
   it. It cannot be mistaken for a control because it does not look like one.
   Closed by default — a coach opening a finished project wants the thing they
   made, not the paperwork that made it. */
.gg-creation__record {
	margin: 18px 0 0;
	padding: 16px 0 0;
	border-top: 1px solid var(--ggb-line, rgba(255,255,255,0.16));
}
.gg-creation__rec-summary {
	cursor: pointer;
	list-style: none;
	display: inline-flex;
	align-items: center;
	gap: 8px;
	font: 700 11px/1 var(--ggb-font-head, inherit);
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
}
.gg-creation__rec-summary::-webkit-details-marker { display: none; }
.gg-creation__rec-summary::before {
	content: "";
	width: 0; height: 0;
	border-left: 5px solid currentColor;
	border-top: 4px solid transparent;
	border-bottom: 4px solid transparent;
	transition: transform 0.15s ease;
}
.gg-creation__record[open] .gg-creation__rec-summary::before { transform: rotate(90deg); }
.gg-creation__rec-summary:hover { color: var(--ggb-text, #fff); }
.gg-creation__rec-body { padding-top: 16px; }
.gg-creation__rec-note {
	font: 400 13px/1.6 var(--ggb-font-body, inherit);
	color: var(--ggb-text-soft, rgba(255,255,255,0.55));
	margin: 0 0 18px;
	max-width: 68ch;
}
.gg-creation__rec-item { margin: 0 0 16px; max-width: 68ch; }
.gg-creation__rec-item:last-child { margin-bottom: 0; }
.gg-creation__rec-label {
	font: 700 12px/1.4 var(--ggb-font-head, inherit);
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
	margin: 0 0 2px;
}
.gg-creation__rec-value {
	font: 400 14px/1.6 var(--ggb-font-body, inherit);
	color: var(--ggb-text, rgba(255,255,255,0.92));
	margin: 0;
}
.gg-creation__rec-value--empty { color: var(--ggb-text-soft, rgba(255,255,255,0.45)); font-style: italic; }

/* ── REVIEW GATE — the whole set, read through once, then signed off ──────
 *
 * The second half of approval. The strip above settles ONE answer; this panel
 * is where the coach reads the finished set as one piece and gives the single
 * final confirmation that lets it be sent. Rendered by gg_ui_review_gate()
 * (components.php), wired by assets/gg-field-box.js, and it talks to the SAME
 * approval endpoint the per-field strip does.
 *
 * NO NEW FURNITURE. The chip is .gg-pill, the actions are .gg-row-btn and the
 * set is .gg-disclosure-list. Everything below is this panel's own layout —
 * the frame, the blocker list and the action row — and nothing here restates a
 * colour, a border or a type scale that a token already owns.
 *
 * THE BLOCKED STATE IS NOT AN ERROR. A coach who has three questions left has
 * done nothing wrong, so the panel wears the same hairline and recessed fill
 * as every other panel and only its chip carries a colour. Red is reserved for
 * something that actually broke. */
.gg-review-gate {
	border: 1px solid var(--ggb-line, rgba(255,255,255,0.16));
	background: rgba(255,255,255,0.02);
	padding: 22px;
	margin: 0 0 28px;
}
.gg-review-gate__head {
	display: flex;
	align-items: baseline;
	flex-wrap: wrap;
	gap: 12px;
	margin: 0 0 8px;
}
.gg-review-gate__title { margin: 0; }
.gg-review-gate__lead {
	font: 400 13px/1.6 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-text-soft, rgba(255,255,255,0.66));
	margin: 0 0 14px;
	max-width: 68ch;
}
/* One blocker per line, named, with the reason beside it. A left rule rather
   than a bullet: the same quiet marker the draft note uses. */
.gg-review-gate__blockers { list-style: none; margin: 0 0 16px; padding: 0; }
.gg-review-gate__blocker {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 8px;
	padding: 7px 0 7px 12px;
	border-left: 2px solid var(--ggb-accent-2, #fbc24e);
	margin: 0 0 6px;
}
.gg-review-gate__blocker-name {
	font: 600 13px/1.4 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-text, #e8edf3);
	text-decoration: none;
}
a.gg-review-gate__blocker-name:hover { color: var(--ggb-accent, #f98a09); }
.gg-review-gate__blocker-why {
	font: 400 12px/1.5 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-muted, rgba(255,255,255,0.55));
}
.gg-review-gate__set { margin: 0 0 16px; }
.gg-review-gate__actions {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 12px;
}
.gg-review-gate__note {
	font: 400 12px/1.5 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-muted, rgba(255,255,255,0.55));
	margin-right: auto;
}
.gg-review-gate__status {
	font: 600 10px/1 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ggb-muted, rgba(255,255,255,0.55));
	min-height: 12px;
}
.gg-review-gate__status.is-working { color: var(--ggb-accent-2, #fbc24e); }
.gg-review-gate__status.is-error   { color: #f87171; }
/* The submit control the caller handed over. It only exists once the set is
   signed off, so it gets the room a final action deserves. */
.gg-review-gate__submit {
	display: flex;
	justify-content: center;
	margin: 18px 0 0;
	padding: 18px 0 0;
	border-top: 1px solid var(--ggb-line, rgba(255,255,255,0.16));
}

/* ── IMAGE FIELD — the row that asks for a picture (2026-08-08) ───────────
 *
 * The field box's fifth body. Every other type is a box the coach types into;
 * this one is a preview, two shared gg_ui_row_btn() actions and two lines of
 * plain English. NO NEW COMPONENT: the buttons are the platform's row buttons,
 * the frame borrows the same 1px hairline and recessed fill as .gg-bv2-field-
 * input, and the corners are sharp like everything else here.
 *
 * TWO KINDS, TWO SHAPES, ON PURPOSE. A picture of the coach previews SQUARE
 * because that is what a circular about-section frame wants; a background
 * picture previews WIDE because that is what sits behind an overlay. A coach
 * can see which kind a row wants before they open a file browser, which is
 * most of what stops a headshot ending up behind a dark scrim.
 *
 * Rendered by gg_ui_field_box() with type image_portrait | image_wide; wired
 * by assets/gg-field-box.js; the file goes to POST gg/v1/fields/image. */
.gg-bv2-field-upload {
	display: flex;
	flex-direction: column;
	gap: 10px;
}
.gg-bv2-field-upload-preview {
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
	width: 100%;
	/* 320px until 24 Aug 2026 (Sam: the featured image box is too big). A field
	   in a form, not the subject of the page - it still shows the picture at
	   this size and stops the row being the tallest thing on the screen. */
	max-width: 200px;
	aspect-ratio: 3 / 2;
	background: rgba(255,255,255,0.03);
	border: 1px solid var(--ggb-line, rgba(255,255,255,0.16));
	border-radius: 0;
}
.gg-bv2-field-upload--portrait .gg-bv2-field-upload-preview {
	max-width: 128px;
	aspect-ratio: 1 / 1;
}
.gg-bv2-field-upload-preview img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}
.gg-bv2-field-upload-empty {
	font: 400 12px/1.4 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-muted, rgba(255,255,255,0.55));
	text-align: center;
	padding: 0 12px;
}
.gg-bv2-field-upload-actions {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 10px;
}
/* The real file input is never the control a coach clicks — the shared row
   button is. Kept in the DOM (not display:none) so it stays focusable for
   keyboard and assistive tech. */
.gg-bv2-field-upload-file {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0 0 0 0);
	white-space: nowrap;
	border: 0;
}
.gg-bv2-field-upload-remove.is-hidden { display: none; }
.gg-bv2-field-upload-status {
	font: 600 10px/1 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ggb-muted, rgba(255,255,255,0.55));
	min-height: 12px;
}
.gg-bv2-field-upload-status.is-working { color: var(--ggb-accent-2, #fbc24e); }
.gg-bv2-field-upload-status.is-saved   { color: var(--gg-status-ok, #4ade80); }
.gg-bv2-field-upload-status.is-error   { color: #f87171; }
.gg-bv2-field-upload-hint {
	font: 400 12px/1.5 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-muted, rgba(255,255,255,0.55));
	margin: 0;
	max-width: 62ch;
}
/* The "you can skip this" line is quieter still — it is reassurance, not an
   instruction, and it must never compete with the size guidance above it. */
.gg-bv2-field-upload-hint--skip {
	border-left: 2px solid var(--ggb-line, rgba(255,255,255,0.16));
	padding-left: 10px;
}
/* Approved = settled, so an image row's actions go the same way the draft chip
   does. The preview stays: seeing the settled picture is the point. */
.gg-bv2-field--locked .gg-bv2-field-upload-actions { display: none; }

/* ── A FILM (2026-08-20) ───────────────────────────────────────────────────
 * The same row as a picture, with the shape left alone. Sam: "A film's shape
 * is read, never changed."
 *
 * So the preview has NO aspect-ratio of its own and the film is contained,
 * never covered - a portrait film shows as a portrait film, letterboxed in
 * the well, because a well that lies about the shape is the one thing this
 * row exists not to do. Wider than a picture's, because a coach is watching
 * it back rather than checking a thumbnail.
 *
 * Under it, two lines. The shape, read off the film and stated plainly. Then
 * an empty warning line the owning plugin fills when that shape fights what
 * the coach has ticked - amber, because it is worth reading and is never a
 * refusal. Both are drawn by gg_ui_field_box() with type video; wired by
 * assets/gg-field-box.js; the file goes to POST gg/v1/fields/video, which
 * stores it and changes nothing about it. */
.gg-bv2-field-upload--video .gg-bv2-field-upload-preview {
	max-width: 420px;
	aspect-ratio: auto;
	min-height: 120px;
	background: #000;
}
.gg-bv2-field-upload--video .gg-bv2-field-upload-preview video {
	width: 100%;
	max-height: 320px;
	object-fit: contain;
	display: block;
}
.gg-bv2-field-video-shape {
	font: 600 10px/1.4 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ggb-muted, rgba(255,255,255,0.55));
	margin: 0;
	min-height: 12px;
}
.gg-bv2-field-video-warning {
	font: 400 12px/1.5 var(--ggb-font-body, -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif);
	color: var(--ggb-accent-2, #fbc24e);
	border-left: 2px solid var(--ggb-accent-2, #fbc24e);
	padding-left: 10px;
	margin: 0;
	max-width: 62ch;
}
.gg-bv2-field-video-warning.is-hidden { display: none; }

/* ════════════════════════════════════════════════════════════════════
 * CHAT / CONVERSATION KIT (backlog #6/#7/#8, audited 2026-07-15/16).
 * Donor: gg-rcp's .gg-boardroom-msg-bubble family + composer/mic —
 * READ-ONLY, gg-chat.css:570,701-830,902-923 + gg-chat.js:766-874. The
 * LCP is the single LIVE full-featured chat surface platform-wide
 * (persona routing, resize, quick-prompts, real voice transcription);
 * this is the reusable LOOK — thread + bubbles + composer — for a
 * second, non-LCP surface that wants the identical visual language
 * (e.g. an expanded notification/feed item) without pulling in the
 * LCP's panel machinery. New prefix `.gg-convo-*` — grepped free
 * sitewide; `.gg-chat-*` is already the LCP scaffold's own namespace
 * (.gg-chat-panel, .gg-chat-composer, .gg-chat-body, etc.) so it was
 * NOT reused here to avoid collision. Sharp corners (radius 0) on the
 * bubble — matches the LCP's own bubbles EXACTLY; there is no rounded
 * exception here despite WhatsApp-style bubbles being a plausible
 * guess, the master itself is sharp. Render via gg_ui_chat_thread() /
 * gg_ui_chat_bubble() / gg_ui_chat_composer() in
 * gg-brand/inc/components.php. Tokens: --gg-convo-* in :root above.
 * ════════════════════════════════════════════════════════════════════ */

/* Fixed dark chat-surface wrapper — same navy as the LCP panel itself.
   Use when hosting a thread/composer outside the LCP; skip if the host
   surface is already this dark (e.g. this Style Guide page's own bg). */
.gg-convo-bg {
	background: var(--gg-convo-bg);
	color: var(--gg-convo-text);
	padding: var(--gg-space-base, 8px) calc(var(--gg-space-base, 8px) * 2.5);
	box-sizing: border-box;
}

.gg-convo-thread {
	display: flex;
	flex-direction: column;
	gap: 22px;
}
.gg-convo-msg {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 6px;
	max-width: 100%;
}
.gg-convo-msg-head {
	display: flex;
	align-items: center;
	gap: 10px;
	min-width: 0;
	max-width: 100%;
}
/* 36px circular avatar — matches the LCP's canonical size exactly. */
.gg-convo-avatar {
	flex: 0 0 auto;
	width: 36px;
	height: 36px;
	border-radius: 50%;
	overflow: hidden;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: #fff;
	font: 700 12px/1 var(--gg-font-body);
	letter-spacing: 0.05em;
	background: rgba(255,255,255,0.10);
	border: 1px solid rgba(255,255,255,0.18);
}
.gg-convo-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; max-width: none; }
.gg-convo-avatar svg { width: 18px; height: 18px; }
.gg-convo-author {
	font: 600 11px/1 var(--gg-font-body);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: rgba(255,255,255,0.62);
	display: inline-flex;
	gap: 8px;
	align-items: baseline;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.gg-convo-time {
	font: 400 10px/1 var(--gg-font-body);
	letter-spacing: 0;
	text-transform: none;
	color: var(--gg-convo-muted);
	margin-left: 4px;
	white-space: nowrap;
}
.gg-convo-bubble {
	max-width: min(92%, 720px);
	width: fit-content;
	padding: 11px 15px;
	border-radius: 0;
	font: 400 14.5px/1.55 var(--gg-font-body);
	box-sizing: border-box;
	box-shadow: 0 2px 10px rgba(0,0,0,0.30);
	overflow-wrap: anywhere;
}
.gg-convo-bubble > * { margin: 0 0 6px 0; }
.gg-convo-bubble > *:last-child { margin-bottom: 0; }

/* Them (assistant) — left-aligned, accent border-left. */
.gg-convo-msg--them { align-items: flex-start; }
.gg-convo-msg--them .gg-convo-bubble {
	background: color-mix(in srgb, var(--gg-convo-accent) 22%, rgba(10,18,34,0.94));
	border: 1px solid color-mix(in srgb, var(--gg-convo-accent) 38%, rgba(255,255,255,0.10));
	border-left: 3px solid var(--gg-convo-accent);
	color: var(--gg-convo-text);
	align-self: flex-start;
}

/* Me — right-aligned amber bubble; head reverses so the avatar sits to
   the right of the name, same recipe the LCP uses to avoid squeezing a
   short "Hi" into a vertical strip. */
.gg-convo-msg--me { align-items: flex-end; }
.gg-convo-msg--me .gg-convo-msg-head { flex-direction: row-reverse; }
.gg-convo-msg--me .gg-convo-author { color: var(--gg-convo-accent-2); }
.gg-convo-msg--me .gg-convo-bubble {
	background: var(--gg-convo-self-bg);
	border: 1px solid var(--gg-convo-self-border);
	color: #0a1628;
	align-self: flex-end;
	text-align: left;
}
.gg-convo-msg--me .gg-convo-bubble strong { color: #0a1628; }

/* Composer — textarea + mic + send, same chrome as the LCP's input row.
   position:relative so the prompts panel can float above it (bottom:100%). */
.gg-convo-composer {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 8px;
	box-sizing: border-box;
}
.gg-convo-row {
	display: flex;
	gap: 8px;
	align-items: flex-end;
}
.gg-convo-input {
	flex: 1 1 auto;
	min-width: 0;
	min-height: 80px;
	background: rgba(255,255,255,0.06);
	color: var(--gg-convo-text);
	border: 1px solid var(--gg-convo-border);
	padding: 14px 18px;
	font-family: var(--gg-font-body);
	font-size: 14.5px;
	line-height: 1.5;
	resize: none;
	border-radius: 0;
	box-sizing: border-box;
}
.gg-convo-input::placeholder { color: var(--gg-convo-muted); }
.gg-convo-input:focus {
	outline: none;
	border-color: var(--gg-convo-border-hot);
	background: rgba(255,255,255,0.10);
}
.gg-convo-actions {
	display: flex;
	flex-direction: column;
	gap: 4px;
	align-items: stretch;
	flex: 0 0 auto;
}
.gg-convo-mic,
.gg-convo-send {
	width: 48px;
	height: 48px;
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	cursor: pointer;
	padding: 0;
	border-radius: 0;
	transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.gg-convo-mic svg,
.gg-convo-send svg { width: 18px; height: 18px; }
/* Mic — the CONTROL and its states only (idle / hover / recording / busy).
   Real MediaRecorder→Whisper wiring is page-specific — a stub/hook, same
   as manage-tags persistence; see gg-rcp/assets/gg-chat.js wireMic() for
   the reference implementation to hook up on a page that needs it live. */
.gg-convo-mic {
	border: 1px solid var(--gg-convo-border);
	background: rgba(255,255,255,0.04);
	color: var(--gg-convo-text-soft);
}
.gg-convo-mic:hover {
	color: #fff;
	border-color: var(--gg-convo-border-hot);
	background: var(--gg-convo-accent-soft);
}
.gg-convo-mic.is-recording {
	color: #fff;
	border-color: var(--gg-convo-accent);
	background: var(--gg-convo-accent);
}
.gg-convo-mic.is-busy { opacity: 0.6; cursor: progress; }
.gg-convo-send {
	border: 1px solid var(--gg-convo-accent);
	background: var(--gg-convo-accent);
	color: #0a1628;
}
.gg-convo-send:hover { background: var(--gg-convo-accent-2); border-color: var(--gg-convo-accent-2); }
.gg-convo-send:disabled,
.gg-convo-send.is-disabled { opacity: 0.5; cursor: not-allowed; }

/* ── Composer controls row: "+ Prompts" + "Just Humans" pills ──────────
   Donor: gg-rcp's .gg-boardroom-composer-controls / .gg-boardroom-comp-pill
   (READ-ONLY, gg-chat.css:843-899). The amber tints mirror the LCP's
   rgba(251,191,36,…) values via --gg-convo-accent-2 (#fbc24e ≈ that amber).
   Behaviour: gg-brand/assets/gg-components.js window.GGConvo — the pill
   toggles the panel, a chip drops its text into the textarea; the prompt
   LIST is page-specific data (pass $args['prompts'] to gg_ui_chat_composer()),
   same "shell shared, data per-page" split as the manage-tags panel. */
.gg-convo-controls {
	display: flex;
	gap: 6px;
	width: 100%;
	flex-shrink: 0;
}
.gg-convo-pill {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	flex: 1 1 0;
	min-width: 0;
	padding: 8px 10px;
	font: 600 11px/1 var(--gg-font-body);
	letter-spacing: 0.10em;
	text-transform: uppercase;
	background: rgba(255,255,255,0.04);
	border: 1px solid rgba(255,255,255,0.14);
	color: var(--gg-convo-text-soft);
	border-radius: 0;
	cursor: pointer;
	transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.gg-convo-pill:hover {
	background: color-mix(in srgb, var(--gg-convo-accent-2) 16%, transparent);
	border-color: color-mix(in srgb, var(--gg-convo-accent-2) 60%, transparent);
	color: #fff;
}
.gg-convo-pill.is-active {
	background: color-mix(in srgb, var(--gg-convo-accent-2) 22%, transparent);
	border-color: color-mix(in srgb, var(--gg-convo-accent-2) 85%, transparent);
	color: #fff;
	box-shadow: 0 0 0 1px color-mix(in srgb, var(--gg-convo-accent-2) 35%, transparent) inset;
}
.gg-convo-pill-glyph { font: 700 14px/1 var(--gg-font-body); color: #fff; }
.gg-convo-pill-value { color: #fff; font-size: 12px; }
.gg-convo-pill-label { color: var(--gg-convo-muted); font-size: 9px; opacity: 0.7; }
.gg-convo-pill-icon { color: var(--gg-convo-muted); display: inline-flex; }
.gg-convo-pill-icon svg { width: 12px; height: 12px; }
.gg-convo-pill-state {
	font-size: 9px;
	letter-spacing: 0.14em;
	padding: 3px 6px;
	background: rgba(0,0,0,0.4);
	border: 1px solid rgba(255,255,255,0.18);
	color: var(--gg-convo-muted);
}
.gg-convo-pill.is-active .gg-convo-pill-state {
	background: color-mix(in srgb, var(--gg-convo-accent-2) 85%, transparent);
	border-color: color-mix(in srgb, var(--gg-convo-accent-2) 95%, transparent);
	color: #1a1207;
}

/* ── Prompts panel: pop-up grid of quick-prompt chips ──────────────────
   Donor: gg-rcp's .gg-boardroom-chips / .gg-boardroom-chip (READ-ONLY,
   gg-chat.css:929-991). Hidden until the "+ Prompts" pill opens it;
   floats UPWARD above the composer (bottom:100%) so it never pushes the
   input row down, exactly like the LCP. */
.gg-convo-prompts {
	display: none;
	position: absolute;
	left: 0;
	right: 0;
	bottom: 100%;
	margin-bottom: 8px;
	grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
	gap: 6px;
	align-items: stretch;
	padding: 10px;
	background: rgba(8,14,28,0.96);
	backdrop-filter: blur(12px);
	-webkit-backdrop-filter: blur(12px);
	border: 1px solid rgba(255,255,255,0.10);
	max-height: 60vh;
	overflow-y: auto;
	z-index: 5;
	box-sizing: border-box;
}
.gg-convo-prompts.is-open { display: grid; }
.gg-convo-chip {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	background: rgba(255,255,255,0.04);
	border: 1px solid rgba(255,255,255,0.10);
	color: var(--gg-convo-text-soft);
	font-family: var(--gg-font-body);
	font-size: 11.5px;
	font-weight: 400;
	letter-spacing: 0.015em;
	padding: 6px 8px;
	min-height: 30px;
	text-align: center;
	cursor: pointer;
	border-radius: 0;
	transition: background 0.12s, border-color 0.12s, color 0.12s, transform 0.08s;
}
.gg-convo-chip:hover {
	background: color-mix(in srgb, var(--gg-convo-accent-2) 16%, transparent);
	border-color: color-mix(in srgb, var(--gg-convo-accent-2) 60%, transparent);
	color: #fff;
}
.gg-convo-chip:active { transform: translateY(1px); }
.gg-convo-chip-icon {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: color-mix(in srgb, var(--gg-convo-accent-2) 65%, transparent);
}
.gg-convo-chip-icon svg,
.gg-convo-chip svg { width: 11px; height: 11px; }
.gg-convo-chip-label {
	flex: 0 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* ── Meta head  .gg-meta-head  (2026-07-30) ─────────────────────────────
 *    The page-identity masthead: one compact block that states what a
 *    record IS — cover picture, name, one-sentence summary, and a hairline
 *    strip of small labelled facts underneath. Read-only by design: it
 *    displays, it never edits, and nothing in it needs clicking.
 *
 *    WHY IT EXISTS: the Offer page stated its six fundamentals (name,
 *    featured image, colour, short summary, what's in it, status) as six
 *    collapsed .gg-disclosure rows — 364px of chrome hiding six short
 *    values behind six clicks. Those six things are the offer's identity,
 *    not a checklist, so they get a masthead. Shaped for any record with
 *    the same job: a project, a contact record, an asset.
 *
 *    TWO TIERS, deliberately. The BAND is identity — the picture, the name
 *    and the sentence, the things a human reads. The FACTS strip is
 *    configuration — the small set-or-not values, each under its own gold
 *    micro-label (the same 10.5px/700/.14em uppercase label as
 *    .gg-entry-daygroup__label, so the block rhymes with the section
 *    kickers that follow it down the page). Nothing appears in both tiers.
 *
 *    ACCENT. The record's own colour paints a 3px rule down the left edge
 *    via the --gg-meta-head-accent custom property — any CSS <image>, so a
 *    gradient or a flat colour both work. Unset paints nothing, which is
 *    the honest empty state. Same opt-in inline-property hook as .gg-tag's
 *    --gg-tag-color; the rule is a ::before so the facts strip's own
 *    background cannot cover it.
 *
 *    REFLOW is flex-wrap, not media queries — the block behaves the same
 *    in a 780px column, on a full-width board, and in a narrow window,
 *    because it responds to its own width rather than the viewport's.
 *    Sharp corners throughout; no radius token is read, none is wanted.
 *
 *    Render via gg_ui_meta_head() (gg-brand/inc/components.php) — one
 *    <header> per call. Don't hand-roll this markup.
 *
 *    CONTRAST NOTE (same caveat as .gg-disclosure/.gg-entry-list above):
 *    renders near-white text on --gg-surface-panel — always dark. */
/* Self-contained box model: admin surfaces do not agree on a global
   box-sizing, and the cover plate has to measure 168x126 whether it is
   holding a photo (border only) or the dashed empty state (border AND
   padding). Scoped to the block, so it changes nothing outside it. */
.gg-meta-head,
.gg-meta-head * { box-sizing: border-box; }
.gg-meta-head {
	position: relative;
	background: var(--gg-surface-panel);
	border: var(--gg-border-width) solid var(--gg-surface-border);
	border-radius: 0;
	color: var(--gg-on-surface);
	font-family: var(--gg-font-body);
}
.gg-meta-head::before {
	content: '';
	position: absolute;
	left: 0;
	top: 0;
	bottom: 0;
	width: 3px;
	z-index: 1;
	background-image: var(--gg-meta-head-accent, none);
	pointer-events: none;
}
.gg-meta-head__band {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	gap: calc(var(--gg-space-base) * 3);
	padding: calc(var(--gg-space-base) * 3) calc(var(--gg-space-base) * 3.5);
}
.gg-meta-head__media {
	flex: 0 0 168px;
	width: 168px;
	aspect-ratio: 4 / 3;
	display: flex;
	align-items: center;
	justify-content: center;
	overflow: hidden;
	background: var(--gg-surface-card);
	border: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-meta-head__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}
.gg-meta-head__media--empty {
	border-style: dashed;
	padding: calc(var(--gg-space-base) * 1.5);
}
.gg-meta-head__media-empty-label {
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: .08em;
	line-height: 1.5;
	text-transform: uppercase;
	text-align: center;
	color: var(--gg-on-surface-muted);
}
.gg-meta-head__id {
	flex: 1 1 320px;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: calc(var(--gg-space-base) * 1.25);
}
.gg-meta-head__eyebrow {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: calc(var(--gg-space-base) * 1);
	margin: 0;
	padding: 0;
}
.gg-meta-head__title,
h1.gg-meta-head__title,
h2.gg-meta-head__title,
h3.gg-meta-head__title {
	font-family: var(--gg-font-heading);
	font-size: clamp(23px, 2.4vw, 29px);
	font-weight: 500;
	line-height: 1.15;
	letter-spacing: -0.01em;
	color: var(--gg-on-surface);
	margin: 0;
	padding: 0;
	overflow-wrap: anywhere;
}
.gg-meta-head__title.is-empty { color: var(--gg-on-surface-muted); }
.gg-meta-head__summary {
	margin: 0;
	padding: 0;
	max-width: 58ch;
	font-size: 14.5px;
	line-height: 1.55;
	color: var(--gg-on-surface-muted);
}
/* Hairline dividers without a border per cell: the strip paints the border
   colour and each cell paints the panel over it, so the 1px flex gaps ARE
   the rules — and they stay correct when the cells wrap onto a second row. */
/* ── LIST VARIANT (2026-07-30, Sam) ──────────────────────────────────────
   WHY IT EXISTS: a variant, not a new component — the same meta-head values
   for pages that carry their picture elsewhere or have none worth the space.

   The same values stated as a plain stacked list rather than a picture
   beside a divided strip. No cover plate — a page using this carries its
   picture elsewhere, or has none worth the space. Text only, one column,
   so it reads top to bottom exactly as it would be spoken.               */
.gg-meta-head--list {
	padding: 0;
	/* THE COMPONENT'S OWN OUTER RHYTHM (2026-08-04). Three pages each carried a
	   private `.<their-wrapper> .gg-meta-head { margin: 0 0 30px }` rule saying
	   the same thing, which is a design decision living in three places — and it
	   outranked the block's own layout, so the hero below could not bleed to the
	   column edge without a page being edited too. The margin belongs to the
	   variant. */
	margin: 0 0 30px;
	background: none;
	border: 0;
	/* The 3px accent rule belongs to the panel treatment. With no panel there
	   is nothing for it to edge, and it collided with the text. */
	border-left: 0;
}
.gg-meta-head--list::before { content: none; }
.gg-meta-head--list .gg-meta-head__band { display: block; padding: 0; }
/* Cover image stacked above the text rather than beside it, and given real
   width — in a stacked list the 168px plate reads as a thumbnail afterthought. */
/* Full width of the column (Sam, 30 Jul). A wider crop than 4:3 — at full
   width a 4:3 photo pushes everything below it off the first screen. */
.gg-meta-head--list .gg-meta-head__media { width: 100%; max-width: none; aspect-ratio: 21 / 9; margin: 0 0 18px; }
.gg-meta-head--list .gg-meta-head__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* EMPTY, IN LIST MODE (2026-07-30). With a picture the plate is the page's
   hero banner and stays one. With none it was that same banner drawn as a
   340px dashed rectangle whose entire content was the words "No featured
   image" — a box holding a sentence about the box being empty, at the very
   top of the page, on every record that has not had a picture set. Empty now
   says so in ONE quiet line: no banner, no reserved height, no border, no
   fill. The BAND variant is untouched — there the plate is the layout's left
   column and a hole in it would collapse the row. */
.gg-meta-head--list .gg-meta-head__media--empty {
	aspect-ratio: auto;
	min-height: 0;
	padding: 0;
	margin: 0 0 12px;
	border: 0;
	background: none;
}
/* The half-removed panel behind the facts — there is no panel in list mode. */
.gg-meta-head--list .gg-meta-head__fact { background: none; }
/* Order in the list variant: image (already above), then the NAME, then the
   status chip, then the sentence. The markup renders the chip before the title
   because the band variant wants it as an eyebrow; here the name leads. */
.gg-meta-head--list .gg-meta-head__id { padding: 0; display: flex; flex-direction: column; }
.gg-meta-head--list .gg-meta-head__title   { order: 1; text-align: center; margin: 0 0 8px; }
/* The description reads as part of the identity, so it sits centred directly
   under the name (Sam, 30 Jul). The status chip follows it. */
/* align-self, not margin:auto — these are flex items in a column, so they
   stretch full width and their own text-align centres inside a box that is
   itself off-centre. align-self centres the BOX. */
.gg-meta-head--list .gg-meta-head__summary { order: 2; text-align: center; align-self: center; margin: 0 0 14px; max-width: 60ch; }
.gg-meta-head--list .gg-meta-head__eyebrow { order: 3; text-align: center; align-self: center; }
/* Whole block reads as one centred identity (Sam, 30 Jul): name, description,
   status and the attributes all on the same axis. */
.gg-meta-head--list .gg-meta-head__fact-label { text-align: center; margin: 0; }

/* The quiet fact line (2026-08-03). Reference facts — type, dates — stated as
   one muted sentence under the loud ones, with no labels: the words carry their
   own meaning, and a gold label above each said nothing twice. */
.gg-meta-head__quiet {
	margin: 10px 0 0;
	font-size: 12px;
	line-height: 1.5;
	color: var(--gg-on-surface-muted);
	text-align: center;
}
.gg-meta-head__quiet-sep {
	display: inline-block;
	margin: 0 8px;
	opacity: 0.45;
}
/* The corner (2026-08-03). Status and the reference dates sit top-right, out of
   the centre column, so the middle of the masthead is left for progress — the
   one thing a coach is working through rather than glancing at.

   NORMAL FLOW, not absolute. It was absolute at first, and a long record name
   ran 128px underneath it — the title is centred, so the longer it gets the
   further right it reaches, and no amount of reserved padding is right for both
   a short name and a long one. A right-aligned row above the title cannot
   collide with anything at any width, and costs one line. */
.gg-meta-head__corner {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	align-items: baseline;
	gap: 4px 12px;
	margin: 0 0 6px;
	text-align: right;
}

/* A HEADER ROW, not a centred stack (2026-08-03, Sam: "you could use the left
   surely too? somehow make it look right").

   The centred version left the whole left third empty while the name, the
   progress bar and the facts all queued down the middle. This is the shape a
   header actually wants: what the record IS on the left, what you glance at on
   the right, and the two of them on one line. Scoped to the 'list' variant, so
   the band variant every grid page uses is untouched. */
/* CENTRED, LIKE EVERY OTHER PAGE (2026-08-04, Sam: why is the project page's
   title left-aligned when everything else is centred?).

   It was a two-column header row — identity on the left, reference on the right
   — which is a good shape in isolation and the wrong shape here: the twelve grid
   pages all open with a centred lockup and a centred title (gg_page_header), so
   a coach clicking a card went from a centred page to a left-aligned one and the
   product changed shape under them. One axis, both screens.

   ONE column, three rows: the reference facts in a right-aligned row along the
   top, then the identity centred under it, then the progress. The corner leads
   because it is the only thing that is not centred — putting it first means it
   can never collide with a long name, which is exactly why it stopped being
   absolutely positioned in the first place. */
.gg-meta-head--list {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	grid-template-areas:
		"corner"
		"band"
		"facts";
	align-items: start;
}
.gg-meta-head--list .gg-meta-head__band   { grid-area: band; }
.gg-meta-head--list .gg-meta-head__corner { grid-area: corner; margin: 0 0 10px; align-content: start; }
.gg-meta-head--list .gg-meta-head__facts { grid-area: facts; }
.gg-meta-head__corner .gg-meta-head__quiet {
	margin: 0;
	text-align: right;
}
/* The edit door, sitting with the facts it changes rather than adrift on a
   line of its own below them. Quiet by default — it is a way in, not the
   page's action — and it states itself on hover and focus. */
.gg-meta-head__edit {
	appearance: none;
	background: none;
	border: 0;
	padding: 0;
	font: inherit;
	font-size: 12px;
	color: var(--gg-on-surface-muted);
	text-decoration: underline;
	text-underline-offset: 3px;
	cursor: pointer;
}
.gg-meta-head__edit:hover,
.gg-meta-head__edit:focus-visible { color: var(--gg-on-surface); }

/* Linked things in the masthead, grouped by what they are (2026-08-03). The
   label is the relationship — "Offer", "Campaigns" — so the line reads as a
   sentence rather than a run of unexplained names. */
.gg-project-linkgroup { display: inline; }
.gg-project-linkgroup + .gg-project-linkgroup::before {
	content: '·';
	display: inline-block;
	margin: 0 8px;
	opacity: 0.45;
}
.gg-project-linkgroup-label {
	color: var(--gg-on-surface-muted);
	margin-right: 6px;
}
.gg-project-linkgroup a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
/* The overflow count on a masthead link group — plain text, not a link, because
   the things it counts are listed in full in the grids on the same page. */
.gg-project-linkgroup-more { color: var(--gg-on-surface-muted); }
@media (max-width: 782px) {
	.gg-meta-head__corner { justify-content: center; text-align: center; }
	.gg-meta-head__corner .gg-meta-head__quiet { text-align: center; }
}

/* ── THE HERO (2026-08-04, Sam: "could the top section of the project page
   display the featured image, but gradiented overlay downwards, and the text
   about that project — title, status, etc? could look very stylish") ──────

   Opt-in with `hero => true` on gg_ui_meta_head(); no caller changes without it.

   The picture runs the full width of whatever column the block sits in and
   fades DOWNWARD into that column's own colour, so the identity text is read on
   flat colour and never over a photograph. That is the whole trick and it is
   one gradient: transparent at the top of the picture, the platform's page
   scrim through the middle, the column's solid navy by the time the first word
   starts. NO NEW OVERLAY VALUE — both colours are the tokens the page backdrop
   already uses (--gg-page-scrim, --gg-page-column-bg), so a hero and the page
   around it can never be two different navies.

   TWO NUMBERS, NAMED ONCE. --gg-hero-h is how tall the picture is; --gg-hero-
   solid is how far down it has gone completely solid, and it is ALSO the block's
   top padding, so "where the gradient finishes" and "where the text starts" are
   the same number by construction rather than by two guesses that drift.

   FULL BLEED inside the reading column: the block cancels exactly the column's
   own horizontal inset by naming its token, then restores it on its own
   content. Nothing here knows a pixel value the column does not also know.

   SIDEWAYS ONLY. It does not also pull up to the top of the column, because it
   is not always the first thing in it — the project page prints the "Back to
   …" link above it, and a hero that reached the top edge sat on top of that
   link's words. A picture that eats the way out is not stylish. */
/* THE TITLE CAME OUT FROM UNDER THE PICTURE (2026-08-20, Sam: "project titles
   on project pages need to be left aligned, large text, below the picture
   header. remove the gradient overlay from picture header").

   The 4 August arrangement laid the name INSIDE the picture's box, on the band
   where a downward gradient had already been driven to solid, and centred it.
   That bought a stylish top of page and cost three things: the photograph was
   mostly overlay by the time it reached the words, the name sat centred while
   every other heading on the page is hard left, and it read as printed ON the
   picture rather than as the record's own name.

   So the two are separated. The picture is a clean band, showing what it
   actually is; the name starts underneath it, aligned with everything else in
   the reading column. --gg-hero-solid is gone as a concept: there is no longer
   a point partway down the picture where the text has to start, because the
   text does not start until the picture has finished. */
.gg-meta-head--hero {
	--gg-hero-h: 300px;
	position: relative;
	isolation: isolate;
	margin: 0 calc(var(--gg-page-column-pad-x, 24px) * -1) 30px;
	/* Top padding IS the picture's height — the one number, named once, so
	   "where the picture ends" and "where the name starts" cannot drift. */
	padding: var(--gg-hero-h) var(--gg-page-column-pad-x, 24px) 0;
}
.gg-meta-head__hero {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	height: var(--gg-hero-h);
	/* Above the column's navy, below every word. Stated on both sides rather
	   than leaning on a negative z-index, which would sit behind the block's own
	   background the moment a caller used the hero outside the list variant. */
	z-index: 0;
	/* NO OVERLAY (2026-08-20, Sam: "remove the gradient overlay from picture
	   header").

	   What was here: a black gradient that started at 42% of the way down and
	   reached full opacity at the base. It existed to make the title readable —
	   the title was laid on the picture's lower half, and a busy photograph
	   underneath it would have won. The title is not on the picture any more, so
	   the whole reason for the overlay went with it, and every project page was
	   showing roughly the bottom half of its picture as black.

	   The picture is now just the picture. */
	background-image: var(--gg-meta-head-hero, none);
	background-size: cover;
	/* The picture is CENTRE-cropped, not top-cropped like the page backdrop
	   behind it. A 300px band takes a slice out of a tall photograph, and the
	   top slice of a landscape is sky: the area backdrops came back as flat
	   colour washes until this was centred, and the mountains, water and trees
	   they were chosen for only appear in the middle. */
	background-position: center;
	background-repeat: no-repeat;
	pointer-events: none;
}

/* ── THE PICTURE IS THE CONTROL (2026-08-24, Sam: "the featured image should be
   changed by clicking in the image in the header of the page. it doesn't need
   it's own field on the page with the upload box") ──────────────────────────

   Opt-in with `image_edit` on gg_ui_meta_head(); a masthead without it is
   byte-identical to before.

   THE WHOLE BAND, AND NOTHING ON TOP OF IT. The picture is the biggest thing on
   the page and a coach has just been told they can click it, so the hit area is
   the band itself rather than a button parked in a corner of it. That means the
   resting state has to be the picture, plainly: no permanent chrome, no dashed
   overlay, nothing that turns a chosen photograph into a form control. The
   words only arrive on hover and on keyboard focus.

   ON A TOUCH SCREEN THERE IS NO HOVER, so the cue is simply always shown. A
   phone user cannot discover an affordance by pointing at it, and an invisible
   300px tap target that changes the page is worse than a visible label.

   IT SITS ABOVE THE PICTURE AND BELOW EVERY WORD. z-index 1 against the hero's
   0, and it is only as tall as the band, so the name, the facts and the fields
   underneath are untouched and un-overlapped.

   THE SELECTOR NAMES ITS PARENT ON PURPOSE. The shared image row this box also
   wears (.gg-bv2-field-upload) makes it a flex column, and written as one class
   this rule tied with that one and lost on source order — the control dropped
   into the page as a 300px hole under the title (seen on the screen, then
   fixed). The stacking rule further down this block excludes the cover for the
   same reason, stated there. */
.gg-meta-head--hero-edit > .gg-meta-head__cover {
	position: absolute;
	display: block;
	top: 0;
	left: 0;
	right: 0;
	height: var(--gg-hero-h);
	z-index: 1;
	/* The box is a frame for the two controls in it, not a surface of its own:
	   the picture behind it has to show through everywhere they are not. */
	background: none;
	border: 0;
	padding: 0;
	margin: 0;
}
/* THE WELL IS NOT DRAWN HERE. The hero IS the preview; the shared row's own
   200px well is kept in the DOM only so GGFieldBox still has the node it
   redraws (see the note in components.php). Its base rule is display:flex,
   which outranks the `hidden` attribute, so the hiding has to be said. */
.gg-meta-head__cover .gg-bv2-field-upload-preview { display: none; }
.gg-meta-head__cover-file {
	/* Reachable by the browser, never seen: the band above is what a coach
	   clicks and the shared binder is what opens this. */
	position: absolute;
	width: 1px;
	height: 1px;
	opacity: 0;
	pointer-events: none;
}
.gg-meta-head__cover-hit {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	padding: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	cursor: pointer;
	/* The scrim is the only thing that changes at rest-to-hover, and it is a
	   wash rather than a curtain: enough to say the band is live, not enough to
	   stop the coach seeing the picture they are about to replace. */
	transition: background-color 140ms ease;
}
.gg-meta-head__cover-hit:hover,
.gg-meta-head__cover-hit:focus-visible {
	background-color: rgba(0, 0, 0, 0.28);
	outline: none;
}
.gg-meta-head__cover-hit:focus-visible { box-shadow: inset 0 0 0 2px var(--ggb-accent, #E8B44F); }
/* THE CUE IS ALWAYS THERE (2026-08-24, Sam: "Hero section image should have an
   icon on it or something that indicates you can click it to change it?").

   It was hover-only, which is the standard trick and the wrong one here: a coach
   has no reason to point at a photograph, so an affordance that only exists once
   they do is an affordance nobody finds. It also failed the first-run case
   completely, where the picture is the AREA backdrop and there is nothing to
   suggest the page has anything to do with it.

   So the glyph sits on the picture all the time, small and quiet, in the corner
   furthest from the name. The WORDS are the part that waits for hover and
   focus: an icon says "this does something", the words say what, and only one
   of those has to be on screen while somebody is reading the page.

   The plate is dark rather than tinted, because the picture underneath it is a
   photograph the coach chose and could be any colour. */
.gg-meta-head__cover-cue {
	position: absolute;
	left: var(--gg-page-column-pad-x, 24px);
	bottom: 14px;
	display: inline-flex;
	align-items: center;
	gap: 0;
	padding: 8px;
	/* Sharp corners, like everything else. */
	border-radius: 0;
	border: 1px solid rgba(255, 255, 255, 0.28);
	background: rgba(0, 0, 0, 0.55);
	color: #fff;
	font-size: 12.5px;
	line-height: 1;
	letter-spacing: 0.01em;
	opacity: 0.9;
	transition: opacity 140ms ease, gap 140ms ease, padding 140ms ease;
}
.gg-meta-head__cover-cue svg { width: 15px; height: 15px; display: block; flex: 0 0 auto; }
/* The words, folded away until somebody points at the picture. max-width rather
   than display, so the plate GROWS into them instead of jumping to a new size. */
.gg-meta-head__cover-cue [data-cover-word] {
	display: block;
	max-width: 0;
	overflow: hidden;
	white-space: nowrap;
	opacity: 0;
	transition: max-width 160ms ease, opacity 140ms ease;
}
.gg-meta-head__cover-hit:hover .gg-meta-head__cover-cue,
.gg-meta-head__cover-hit:focus-visible .gg-meta-head__cover-cue {
	opacity: 1;
	gap: 7px;
	padding: 8px 11px 8px 9px;
}
.gg-meta-head__cover-hit:hover .gg-meta-head__cover-cue [data-cover-word],
.gg-meta-head__cover-hit:focus-visible .gg-meta-head__cover-cue [data-cover-word] {
	max-width: 12em;
	opacity: 1;
}
/* The second control: clearing the picture, and the line that says what the
   upload is doing. Kept out of the hit button — a Remove inside a button that
   opens a file browser is one control that does two things. */
.gg-meta-head__cover-actions {
	position: absolute;
	right: var(--gg-page-column-pad-x, 24px);
	bottom: 14px;
	display: flex;
	align-items: center;
	gap: 10px;
	opacity: 0;
	transition: opacity 140ms ease;
}
.gg-meta-head__cover:hover .gg-meta-head__cover-actions,
.gg-meta-head__cover:focus-within .gg-meta-head__cover-actions { opacity: 1; }
/* SAYING SOMETHING BEATS BEING TIDY. "Uploading…" and "Saved" have to be
   readable whether or not the pointer is still on the band — a coach who
   clicked, chose a file and moved the mouse away is exactly who that line is
   for. */
.gg-meta-head__cover:has(.gg-bv2-field-upload-status:not(:empty)) .gg-meta-head__cover-actions { opacity: 1; }
/* SCOPED, NEVER A SECOND CLASS ON THE ELEMENT. uploadStatus() in gg-field-box.js
   rewrites this element's whole className every time it says something, so any
   class added here would survive exactly until the first "Uploading…" and then
   silently vanish (seen, then fixed 2026-08-24). */
.gg-meta-head__cover .gg-bv2-field-upload-status {
	padding: 6px 10px;
	background: rgba(0, 0, 0, 0.55);
	border: 1px solid rgba(255, 255, 255, 0.24);
	color: #fff;
	font-size: 12px;
	line-height: 1;
}
.gg-meta-head__cover .gg-bv2-field-upload-status:empty { display: none; }
.gg-meta-head__cover-actions .gg-bv2-field-upload-remove {
	background: rgba(0, 0, 0, 0.55);
	border-color: rgba(255, 255, 255, 0.24);
	color: #fff;
}
.gg-meta-head__cover-actions .gg-bv2-field-upload-remove.is-hidden { display: none; }
/* NO HOVER TO DISCOVER IT WITH, so a phone gets the whole thing at rest: the
   glyph, the words, and the Remove. There is no second state to reveal them in.
   See the note above the cue. */
@media (hover: none) {
	.gg-meta-head__cover-cue {
		opacity: 1;
		gap: 7px;
		padding: 8px 11px 8px 9px;
	}
	.gg-meta-head__cover-cue [data-cover-word] { max-width: 12em; opacity: 1; }
	.gg-meta-head__cover-actions { opacity: 1; }
}

/* ── THE NAME, UNDER THE PICTURE (2026-08-20, Sam) ───────────────────────────

   LEFT, AND LARGE. The name is the biggest thing on the page and it starts at
   the same left edge as every heading under it, so the column reads as one
   piece of paper instead of a centred lockup sitting on top of a left-aligned
   document.

   The size is stated on the hero variant only. Everywhere else the masthead is
   used — the setup shell, a client record — is untouched. */
.gg-meta-head--hero .gg-meta-head__title {
	text-align: left;
	margin: 0;
	/* BIGGER THAN THE TEXT, SMALLER THAN A BANNER (2026-08-20, Sam: "make it a
	   bit smaller too ... the title itself should be bigger than normal text,
	   but smaller than it is now").

	   Set against the page's own vocabulary rather than picked: body text is
	   14px, a section heading is 17px, and the first version of this was 46px —
	   a poster headline sitting over a document. 26px clears both the text and
	   the headings it has to out-rank, and stops well short of shouting.

	   ONE NUMBER, NOT A CLAMP. Scaling it down on a phone put the name at 22px
	   under a 24px section heading further down the page — the project's own
	   name, smaller than one of its sections. 26px is comfortable at 390px (a
	   long name wraps, which is fine) and it stays the largest thing on the page
	   at every width, which is the only thing the size has to be. */
	font-size: 26px;
	line-height: 1.2;
	letter-spacing: -0.01em;
}
/* The label above the box. .gg-bv2-field-label carries the weight and colour;
   these two lines are the same sentence-case treatment the project page already
   applies to every other field label on it (.gg-project-qrow--setting), stated
   here so the masthead's label and the ones under it are one size and one case
   rather than two that happen to look alike. */
.gg-meta-head__title-label {
	display: block;
	margin: 0 0 8px;
	font-size: 12.5px;
	text-transform: none;
	letter-spacing: 0;
}
/* The list variant centres its identity text. The hero overrules it for the
   name and for the box the name sits in — nothing else in the block moves. */
.gg-meta-head--hero .gg-meta-head__id,
.gg-meta-head--hero .gg-meta-head__title-box { text-align: left; }

/* THE BOX (Sam: "title should be in a text box, so it's obvious you can edit
   it"). It has to read as a field WITHOUT reading as a form control shouting
   over a 46px heading, so it is the quietest thing that still says "type here":
   a soft inset panel with a hairline, which firms up on hover and takes the
   brand's own focus treatment when the caret is in it. */
.gg-meta-head__title-box { display: block; }
/* THE BOX HAS REAL PADDING (2026-08-24, Sam: "the 'title' field needs padding,
   like other text fields do. A bit of padding to the left of the word, and a
   couple pixels more above and below").

   IT ALWAYS ASKED FOR SOME AND NEVER GOT ANY. The base heading rule a few lines
   up is written as `.gg-meta-head__title, h1.gg-meta-head__title, h2…, h3…` and
   zeroes the padding; the tag-and-class halves of that outrank a single class,
   so this rule's own padding lost every time and the name sat flush against the
   edge of its box, top, bottom and left. The name is drawn as an h1, so the
   selector below says so too - matched, not out-shouted with !important.

   THE NUMBERS ARE THE SHARED FIELD'S. 14px on the sides is what every other box
   on the platform uses (.gg-bv2-field-input), so the title's first letter starts
   on the same line as the answer under it rather than nearly on it. Vertically
   it takes 13px against their 11px, because this is 26px type and the same
   padding round a much bigger letter reads tighter. */
.gg-meta-head__title--edit,
h1.gg-meta-head__title--edit,
h2.gg-meta-head__title--edit,
h3.gg-meta-head__title--edit {
	display: block;
	box-sizing: border-box;
	width: 100%;
	padding: 13px 14px;
	margin: 0;
	border: 1px solid rgba(255, 255, 255, 0.14);
	border-radius: 0;
	background: rgba(255, 255, 255, 0.035);
	cursor: text;
	outline: none;
	/* A long name WRAPS rather than scrolling sideways. Holding it to one line
	   meant a phone showed the first two thirds of a name and hid the rest
	   behind a scroll nobody would think to try. Nothing is lost by wrapping:
	   a pasted paragraph is flattened to one line before it is saved, in the
	   box and again at the route, so the box can never be storing the shape it
	   is showing. */
	white-space: pre-wrap;
	overflow-wrap: anywhere;
	transition: background .12s ease, border-color .12s ease;
}
.gg-meta-head__title--edit:hover {
	border-color: rgba(255, 255, 255, 0.28);
	background: rgba(255, 255, 255, 0.06);
}
.gg-meta-head__title--edit:focus {
	border-color: var(--gg-gold, #E8B44F);
	background: rgba(255, 255, 255, 0.07);
}
/* An unnamed project says what to put there, in the box, quietly — not a
   sentence standing in for a heading. */
.gg-meta-head__title--edit:empty::before {
	content: attr(data-placeholder);
	color: rgba(255, 255, 255, 0.38);
}
.gg-meta-head__title-saving {
	display: block;
	min-height: 1em;
	padding-top: 6px;
	font: 400 12px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
	color: rgba(255, 255, 255, 0.5);
}
/* Everything except the picture is lifted above the picture. The cover control
   is the one child that positions ITSELF — it has to sit exactly on the band,
   which means absolute, so it opts out here rather than fighting this rule for
   specificity further down the sheet (2026-08-24). */
.gg-meta-head--hero > *:not(.gg-meta-head__hero):not(.gg-meta-head__cover) { position: relative; z-index: 1; }
/* The hero IS the top of the page, so the block above it has nothing left to
   space away from — and the accent rule is a panel treatment with no panel. */
.gg-meta-head--hero .gg-meta-head__band { margin-top: 0; }
.gg-meta-head--hero::before { content: none; }
/* Narrow screens read the picture as a band rather than a backdrop: less of it,
   and the name starts sooner, so a phone does not open on 210px of sky. */
@media (max-width: 782px) {
	.gg-meta-head--hero { --gg-hero-h: 190px; }
}

.gg-meta-head--list .gg-meta-head__fact-value { justify-content: center; text-align: center; }
.gg-meta-head--list .gg-meta-head__eyebrow { margin: 0 0 10px; }
.gg-meta-head--list .gg-meta-head__summary { margin: 0 0 22px; }
/* ONE row, not a stack (Sam, 30 Jul). Status, colour and progress are each
   about ten characters; stacked they cost ~200px of empty column between the
   description and the first question. Inline they read as one line of context
   under the name.

   CENTRED (Sam, 4 Aug), on the same axis as the name above it and the grid
   pages either side of it. It was briefly left-aligned, for the one day the
   masthead was a two-column header row; with the identity centred again a
   left-aligned progress bar is the only thing on the page lined up with
   nothing. */
.gg-meta-head--list .gg-meta-head__facts {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: 10px 20px;
	border: 0;
	background: none;
	padding: 0;
	margin: 0;
}
.gg-meta-head--list .gg-meta-head__fact {
	display: flex;
	align-items: center;
	gap: 8px;
	border: 0;
	padding: 0;
	/* Do not grow — the band variant's cells divide the strip evenly, which
	   here pushed three short values out to the far edges of the column. */
	flex: 0 0 auto;
}
/* No separator glyph — it kept landing above the line, and the gap already
   reads as separation. Spacing does the job. */
.gg-meta-head--list .gg-meta-head__fact-label { margin: 0 0 2px; }
.gg-meta-head--list .gg-meta-head__fact-value { display: flex; align-items: center; gap: 8px; }

.gg-meta-head__facts {
	display: flex;
	flex-wrap: wrap;
	gap: var(--gg-border-width);
	margin: 0;
	padding: 0;
	background: var(--gg-surface-border);
	border-top: var(--gg-border-width) solid var(--gg-surface-border);
}
.gg-meta-head__fact {
	flex: 1 1 190px;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: calc(var(--gg-space-base) * .75);
	padding: calc(var(--gg-space-base) * 1.75) calc(var(--gg-space-base) * 3.5);
	background: var(--gg-surface-panel);
}
.gg-meta-head__fact-label {
	margin: 0;
	font-size: 10.5px;
	font-weight: 700;
	letter-spacing: 0.14em;
	line-height: 1.4;
	text-transform: uppercase;
	color: var(--gg-status-warn);
}
.gg-meta-head__fact-value {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: calc(var(--gg-space-base) * 1);
	margin: 0;
	min-width: 0;
	font-size: 13.5px;
	line-height: 1.5;
	color: var(--gg-on-surface);
	overflow-wrap: anywhere;
}
.gg-meta-head__summary.is-empty,
.gg-meta-head__fact-value.is-empty { color: var(--gg-on-surface-muted); }
/* A fact whose value is a RELATIONSHIP — "Linked", "Owner", "Part of" — is a
   link, not a string. The `value_html` slot already allows it; without this
   the anchor arrives wearing wp-admin's #2271b1 blue, which is the one colour
   this block never uses. Links read as the surrounding value with a hairline
   underline, and pick up the accent on hover like .gg-back does. Nothing
   changes for the plain-text facts every existing caller passes. */
.gg-meta-head__fact-value a {
	color: inherit;
	text-decoration: underline;
	text-decoration-color: var(--gg-surface-border);
	text-underline-offset: 3px;
	transition: color var(--gg-transition-speed, 150ms) var(--gg-transition-easing, ease);
}
.gg-meta-head__fact-value a:hover,
.gg-meta-head__fact-value a:focus-visible {
	color: var(--gg-colour-accent, #74C299);
	text-decoration-color: currentColor;
}
.gg-meta-head__swatch {
	flex: none;
	display: inline-block;
	width: 16px;
	height: 16px;
	border: var(--gg-border-width) solid var(--gg-surface-border);
}