/* Huntington Beach Chiropractic - main stylesheet.
 *
 * Hand-written CSS only. No framework, no build step, no preprocessor.
 * Written append-only in numbered sections so later work never overrides
 * earlier work by accident.
 *
 * SECTIONS
 *   01  Design tokens
 *   02  Base
 *   03  Shared utilities
 *   04  Site header
 *   05  Mobile menu
 *   06  Site footer
 *   07  Responsive
 *   08  Article feed (component)
 *   09  Homepage sections
 *
 * STAGE 1 - SKELETON ONLY. There are no page sections in this file and
 * there should not be until copy lands. Sections 08 onward are reserved.
 */


/* ==================================================================
 * 01  DESIGN TOKENS
 *
 * SOURCE OF TRUTH
 *   Figma file  wwL8CzZ1v3k04rJS9kaRnq  (HB Chiropractic)
 *   Frame       513:2393
 *               "(unverified) Homepage - Synthesis [ACTIVE - future
 *                edits go here]"
 *   Pulled      2026-08-19
 *
 * Only that frame was read. The file holds nine other page frames and
 * an icon reference board; all were ignored on instruction.
 *
 * Every COLOUR below is an exact value - either a published Figma
 * variable (marked "Figma var") or a literal read out of the frame
 * (marked "read from"). Nothing here was eyeballed, sampled from an
 * image, or approximated.
 *
 * This block remains the ONLY place a colour or size value may live.
 * No hex code and no pixel number appears anywhere else in this
 * stylesheet or in any template.
 * ================================================================== */

:root {

	/* ---- 1.1  Colour - PUBLISHED FIGMA VARIABLES ----
	 * The file publishes eleven variables. These are them, one to one.
	 * Figma's names are kept in the comments so the two sets can be
	 * read against each other.
	 */
	--ink:            #141413;   /* Figma var: color/ink            */
	--ink-muted:      #5c5b57;   /* Figma var: color/ink-secondary  */
	--surface:        #ffffff;   /* Figma var: color/paper          */
	--surface-alt:    #f5f4f1;   /* Figma var: color/paper-alt      */
	--surface-invert: #141413;   /* = color/ink, used as a ground   */
	--on-invert:      #ffffff;   /* Figma var: color/ink-inverse    */
	--rule:           #dddbd6;   /* Figma var: color/line           */

	/* ---- 1.2  Colour - THE ACCENT ----
	 *
	 * AMBER, not the bronze placeholder this file used to carry. The
	 * design leans on it hard: it is the fill and border of every
	 * primary CTA, the "live" state in the process diagrams, and the
	 * one warm note against an otherwise neutral page.
	 *
	 * !! --accent-contrast IS INK, NOT WHITE. Every amber button in the
	 * frame sets its label to color/ink. White on this amber measures
	 * about 1.9:1 and would fail contrast outright; the design never
	 * does it. Do not "fix" this to white.
	 *
	 * !! AMBER IS A SURFACE COLOUR, NOT A TEXT COLOUR. In the frame it
	 * is never used for text on a light ground - only as a fill, a
	 * border, or a rule. See the note in section 02 about link and
	 * hover states, which currently spend it as text and need a
	 * decision.
	 */
	--accent:          #fbb03c;   /* Figma var: var(--color-amber)  */
	--accent-contrast: #141413;   /* read from: every amber CTA label */

	/* Glow beneath the primary CTA. Read from the canonical button
	 * component (350:81): DROP_SHADOW rgba(255,139,0,0.35), offset
	 * (0,4), blur 7.5. Its own colour, not a tint of --accent. */
	--accent-glow: 0 4px 7.5px rgba(255, 139, 0, 0.35);

	/* ---- 1.2b  THE GLASS-AMBER PILL ----
	 *
	 * Figma describes the button component (362:2 / 350:81) as a
	 * "glass-amber pill: shader gradient fill, glossy multi-stop
	 * gradient border, GLASS blur effect". A shader fill has no CSS
	 * equivalent and Figma exports only the flat amber, which is why
	 * this shipped as a plain amber lozenge and read as one.
	 *
	 * These four values are the buildable reading of that description.
	 * Each does one job:
	 *
	 *   --accent-fill    the body of the pill. Lighter at the top than
	 *                    the bottom, which is what makes it look lit
	 *                    from above rather than filled in.
	 *   --accent-gloss   the highlight ACROSS THE TOP HALF only. This is
	 *                    the "glossy" part - a second layer, not a
	 *                    darker stop on the first, because a gradient
	 *                    that both lightens and darkens reads as muddy.
	 *   --accent-rim     the multi-stop border. NOT a simple top-down
	 *                    fade: Figma calls it a "glossy multi-stop
	 *                    gradient border", and what makes a ring read as
	 *                    METAL rather than as a highlight is TWO
	 *                    speculars with a darker band between them - a
	 *                    bright top edge, a warm shadow through the
	 *                    middle, and a second catch along the bottom.
	 *                    One fade alone just looks like a lit edge.
	 *   --accent-lift    the drop shadow plus the halo. The halo is what
	 *                    the "GLASS blur" leaves behind once the blur
	 *                    itself is gone. */
	--accent-fill:  linear-gradient(180deg, #fdc262 0%, #fbb03c 46%, #f0a02a 100%);
	--accent-gloss: linear-gradient(180deg, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 62%);
	--accent-rim: linear-gradient(
		180deg,
		rgba(255, 255, 255, 0.95)  0%,    /* top specular              */
		rgba(255, 243, 214, 0.70) 20%,
		rgba(166, 108, 16,  0.40) 54%,    /* the darker band           */
		rgba(255, 238, 196, 0.60) 84%,
		rgba(255, 255, 255, 0.90) 100%    /* the second catch, beneath */
	);
	--accent-lift:  0 4px 7.5px rgba(255, 139, 0, 0.35), 0 0 18px rgba(251, 176, 60, 0.45);

	/* ---- 1.3  Colour - NAVY ----
	 * The second brand colour. Carries the full-width "Button Card"
	 * panels and the outline of the "How We Do It Differently" panel.
	 *
	 * The design paints those panels as a vertical gradient, so the
	 * deep end is a real value the palette needs even though Figma
	 * publishes only the light end as a variable.
	 */
	--navy:      #205e96;   /* Figma var: color/navy                    */
	--navy-deep: #102c46;   /* read from: Button Card gradient end 513:2700 */

	--gradient-navy: linear-gradient(180deg, var(--navy), var(--navy-deep));

	/* The STATEMENT PANEL's ground - the navy block on the Chiropractic
	 * Adjustments page (553:18). Its own token because it is not the bar
	 * gradient above and not the diagonal the Proof and Credentials
	 * panels use: sampled at the eight edge points of the frame, it runs
	 * LIGHTEST AT THE TOP RIGHT (#1f5c92, which is --navy almost
	 * exactly) into near-black at the bottom right (#010204), with a
	 * separate lighter blue lifting the BOTTOM LEFT corner (#243e7e).
	 *
	 * A top-right-to-bottom-left linear gradient cannot do that - the
	 * corner it would make darkest is the one the design makes light -
	 * so it is a radial lift over a linear base. Both layers are built
	 * from --navy and --navy-deep; no third blue is introduced, which is
	 * why the bottom-left corner is a shade cooler than the frame's. */
	--gradient-navy-panel:
		radial-gradient(80% 110% at 0% 100%, rgba(32, 94, 150, 0.5) 0%, rgba(32, 94, 150, 0) 60%),
		linear-gradient(200deg, var(--navy) 0%, var(--navy-deep) 65%);

	/* Halo around the bordered navy panel. Read from 513:2535:
	 * DROP_SHADOW rgba(96,171,238,0.33), offset (0,0), blur 50. The
	 * colour is a lighter blue than --navy and is not derived from it. */
	--navy-glow: 0 0 50px rgba(96, 171, 238, 0.33);

	/* The lit edge ON a navy ground. Read off Figma 876:93, the open-days
	 * card on the contact page, which draws a 2px #69b8ff border against
	 * --navy so the card reads as raised rather than as a flat block.
	 *
	 * !! IT IS NOT A TINT OF --navy AND IT IS NOT --navy-glow's COLOUR.
	 * The glow is rgb(96,171,238) at 33% in a shadow; this is a solid
	 * stroke three points brighter. Close enough to look like a mistake
	 * if the two are ever collapsed, far enough apart that collapsing
	 * them changes the card. Tokenised here because section 01 is the
	 * only place a colour may live - the same call section 20 made about
	 * the field fill rather than typing #f2f2f2 into a rule. */
	--navy-lit: #69b8ff;

	/* ---- 1.4  Colour - DIAGRAM AND STATUS ----
	 * The process diagrams use a green "done" state and a red "problem"
	 * eyebrow. Tokenised because they are real design values that would
	 * otherwise be typed into a section by hand later.
	 */
	--success:      #2e965f;   /* Figma var: diagram/step-green            */
	--success-glow: 0 0 13.75px rgba(46, 150, 95, 0.55);  /* read from 513:2572 */
	--alert:        #ff1a1a;   /* read from: "THE PROBLEM" eyebrow 513:2497 */

	/* ---- CARD NETWORK COLOURS ----
	 *
	 * !! THESE ARE THE ONLY VALUES IN THIS BLOCK THAT DID NOT COME FROM
	 * THE FIGMA FILE, AND THEY ARE NOT DESIGN DECISIONS. They are the
	 * four card networks' own published brand colours, used by the
	 * trust row on the checkout (section 21.4b) and nowhere else. They
	 * live here rather than in the row's own rules for the reason
	 * stated at the top of this section: this block is the only place
	 * in the theme a colour is allowed to exist, and an exception for
	 * "colours that are somebody else's" would be the end of that rule.
	 *
	 * Nothing else in the theme may use them. If a fifth network is
	 * ever accepted, it gets a line here and a rule in 21.4b - not a
	 * hex code next to the drawing. */
	--brand-visa:          #1434cb;  /* Visa brand blue           */
	--brand-mastercard-a:  #eb001b;  /* Mastercard, left disc     */
	--brand-mastercard-b:  #f79e1b;  /* Mastercard, right disc    */
	--brand-mastercard-ab: #ff5f00;  /* Mastercard, the overlap   */
	--brand-amex:          #006fcf;  /* American Express blue     */
	--brand-discover:      #ff6000;  /* Discover orange           */

	/* Neutral chip fill for inactive diagram steps.
	 * Figma var "Fills/Primary", #78787833 - a 20% alpha grey. */
	--fill-muted: rgba(120, 120, 120, 0.2);

	/* THE BRAND'S LIGHT TINT. A NAVY TINT, NOT A GREY.
	 *
	 * --surface-alt was tried first and is only about 2% off white -
	 * technically a fill, visually nothing. This is dark enough to
	 * register at a glance and tinted so it belongs to the brand rather
	 * than looking like a disabled state.
	 *
	 * RENAMED from --fill-hover, and the rename is the point: it started
	 * as the dropdown row's hover ground and is now also the RESTING fill
	 * of the condition chips, which never hover at all. A name that says
	 * "hover" on a static element is the small kind of lie that gets a
	 * second, identical value typed in beside it six months later. */
	--fill-tint: rgba(32, 94, 150, 0.10);

	/* The same tint, one step deeper, for a HOVER on something already
	 * wearing --fill-tint at rest. Both are the same navy at two
	 * alphas, so they can never drift into two different blues. */
	--fill-tint-strong: rgba(32, 94, 150, 0.18);

	/* THE REVIEW CARD'S FILL. Sampled off the Reviews frame's own card
	 * nodes (717:3 and its four siblings): a soft diagonal ramp from
	 * near-white at the top left to a pale navy tint at the bottom
	 * right. It is the one surface on this site that is neither
	 * --surface nor --surface-alt, and it is what makes a review card
	 * read as a quoted object rather than as another content tile.
	 *
	 * !! THE FRAME ALSO EXPORTS THIS AS A RASTER (media 167,
	 * reviews-Review-Card-Background, 388x249 RGBA at max alpha 161)
	 * AND THAT EXPORT IS DELIBERATELY NOT USED. The cards are three
	 * different heights in the frame alone and a fourth on a phone, so
	 * a fixed-size translucent PNG stretched to fit would band and
	 * shift its ramp on every card. Two stops in CSS cost nothing and
	 * scale exactly. Declining the export is the decision, not an
	 * oversight - see 16.3. */
	--gradient-review-card: linear-gradient(135deg, #f9f9f9 0%, #d5e4f1 100%);

	/* ---- 1.5  Colour - NOT DEFINED BY THE DESIGN ----
	 * Retained from the placeholder set. The frame contains no
	 * equivalent, so these are the theme's own and are flagged as such.
	 * If the designer supplies real values, they replace these.
	 */
	--ink-subtle:  #8A8A8A;                    /* third ink step        */
	--rule-invert: rgba(255, 255, 255, 0.15);  /* hairline on dark      */

	/* Secondary text on a dark ground. Unlike the two above this IS a
	 * design value - the photo-strip subtext is drawn at exactly this
	 * alpha (513:2480) - but the frame publishes no variable for it, so
	 * it lives here with the theme's own. */
	--on-invert-muted: rgba(255, 255, 255, 0.85);

	/* ---- 1.6  Elevation ----
	 * Figma effect variable "Shadow/Card": DROP_SHADOW #00000014,
	 * offset (0,2), radius 12, spread 0. The 14 suffix is 8% alpha.
	 *
	 * Figma's blur radius maps 1:1 onto a CSS box-shadow blur, which is
	 * how this is consumed. The frame's own CSS export confirms it -
	 * the Button Cards read
	 * shadow-[0px_2px_12px_0px_rgba(0,0,0,0.08)], exactly this.
	 *
	 * (A Tailwind `drop-shadow-` filter on the same node shows 6px
	 * instead, because the CSS filter takes a standard deviation rather
	 * than a blur radius. That is the filter's scale, not a second
	 * design value - do not copy the 6.)
	 */
	--shadow-card: 0 2px 12px rgba(0, 0, 0, 0.08);

	/* The three process tiles sit on --surface-alt, not on white, and
	 * Shadow/Card at 8% barely registers against a tinted ground - the
	 * tiles read as flat cutouts. This is the same shadow given a
	 * second, wider pass so the card lifts off the cream the way the
	 * design draws it. Only the tiles use it. */
	--shadow-tile: 0 1px 2px rgba(20, 20, 19, 0.06), 0 8px 24px rgba(20, 20, 19, 0.10);

	/* The photo cards in the strip carry a heavier lift than the panels
	 * do. Read from 513:2476: DROP_SHADOW rgba(20,20,19,0.12), offset
	 * (0,10), blur 15. Its own value, not a scale of --shadow-card. */
	--shadow-photo: 0 10px 15px rgba(20, 20, 19, 0.12);

	/* The dark wash under the photo-strip captions. Read from 513:2478:
	 * a linear gradient, transparent at the top to solid black at the
	 * bottom. It is an elevation device rather than a colour - the
	 * caption is legible because of it - so it lives with the shadows. */
	--scrim-photo: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000000 100%);


	/* ---- 1.7  Typography - FAMILY ----
	 *
	 * TWO FAMILIES, not the frame's one. Inter was the design's choice
	 * and it is a fine workhorse, but it carried none of this practice
	 * in it - the page read as competent and anonymous.
	 *
	 *   LEXEND  body. Drawn for reading proficiency, and the face Gerek
	 *           already uses on his own sites.
	 *   SORA    headings. Geometric like Lexend so the two agree, but
	 *           tighter and more angular, which is what gives a heading
	 *           its authority. It also sets narrower than Lexend, worth
	 *           real lines on an 83-character hero headline.
	 *
	 * Both are SIL Open Font License 1.1: free commercially, free to
	 * self-host, no paid licence. Loaded from Google Fonts in
	 * functions.php - and BOTH must be requested there or the heading
	 * face silently falls back to the body face and nothing looks
	 * broken enough to notice.
	 *
	 * The fallbacks are metric-adjacent grotesques so a failed webfont
	 * load does not reflow the page into a different shape.
	 */
	--font-heading: "Sora", "Lexend", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
	--font-body:    "Lexend", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;

	/* ---- 1.8  Typography - SIZE SCALE ----
	 *
	 * ANCHORED ON A 20px BODY, NOT ON THE FIGMA FRAME.
	 *
	 * The frame is a guide, not a specification - the same call already
	 * written into Peninsula's stylesheet, which labels its own frame
	 * width "reference only". The frame's running copy sits between 16
	 * and 20 depending on the block; this file used to take the bottom
	 * of that range for everything, which is too small to read
	 * comfortably on a modern desktop.
	 *
	 * THE STANDING RULE, and it is a rule rather than a reading:
	 *   22px  running copy on desktop, laptop and tablet
	 *   18px  running copy on mobile (the <=767 step in 1.8b)
	 *
	 * 22 rather than 20 because that is what Peninsula already uses -
	 * --type-body: var(--font-size-22) - and the two sites should not
	 * disagree about what a paragraph looks like.
	 *
	 * The headings are then sized to hold their hierarchy AGAINST that
	 * 20, not copied off the frame - a 36px H1 over 20px body is only
	 * 1.8x and reads as a bold paragraph rather than a headline. The
	 * scale below steps at roughly 1.27, so each level is clearly its
	 * own rank at a glance.
	 *
	 *   50  H1 - hero headline, and the Button Card titles
	 *   40  H2 - every section heading
	 *   30  H3 - diagram panel titles
	 *   26  H4 - sub-section headings, process step numbers
	 *   23  H5 - process card titles
	 *   24  body-lg - the Proof section's running copy
	 *   22  body - default running copy            <- THE RULE
	 *   19  ui / small - CTA labels, card body
	 *   17  detail - header CTA, footer address and links
	 *   15  xs - Proof Bar stat labels
	 *   13  eyebrow - the pill eyebrows
	 *
	 * H1 is 50 rather than the 52-58 the ratio would suggest: this
	 * site's headline is 83 characters long, far longer than the pages
	 * this scale came from, and anything bigger turns the hero into six
	 * lines of type with no room for the photograph beside it.
	 *
	 * The ladder in 1.8b steps the headings at four breakpoints and
	 * drops the body to 18 at the mobile one.
	 */
	--type-h1:      50px;
	/* The HERO headline runs smaller than every other H1 on the site.
	 * It is 83 characters long - "Huntington Beach Chiropractic:
	 * Chiropractic Adjustments Built On Data, Not Guesswork" - and at 50
	 * it needed five lines, which pushed the hero panel tall enough that
	 * the photograph behind it had to scale up ~25% to keep covering.
	 * The result was a close-up of Dr. Michael's shoulder. Sizing the
	 * headline is what fixes the photograph.
	 *
	 * 42 -> 52 -> 46 -> 57, all on 2026-08-25, and the last jump was
	 * bought by the COPY, not by CSS. The headline used to read
	 * "Huntington Beach Chiropractic: Chiropractic Adjustments..." - the
	 * word twice, and at any size that held four lines it started two of
	 * them. Cutting the second "Chiropractic" took the line from 84
	 * characters to 71 and paid for eleven pixels of headline.
	 *
	 * !! 57 IS A MEASURED CEILING, NOT A PREFERENCE. Against the 740px
	 * copy column it is the LARGEST size that still sets in four lines -
	 * 58 goes to five. Found by stepping this property on the rendered
	 * page and counting line boxes, not estimated.
	 *
	 * So it is tied to the H1's exact wording. CHANGE THE HEADLINE COPY
	 * AND THIS NUMBER HAS TO BE RE-MEASURED - one longer word spills it.
	 * Widening the column would buy more, but the copy already ends
	 * around half the panel and any further runs into the subject of the
	 * photograph. */
	--type-h1-hero: 57px;

	/* The hero's opening line, one step above body. It is the only
	 * sentence above the fold and at plain body size it read as a
	 * caption under the headline rather than a statement beside it. */
	--type-hero-lede: 26px;
	--type-h2:      40px;
	--type-h3:      30px;
	--type-h4:      26px;
	--type-h5:      23px;
	--type-brand:   24px;   /* footer brand lockup (513:2748)         */

	--type-body-lg: 24px;   /* Proof section running copy             */
	--type-body:    22px;
	--type-ui:      19px;
	--type-small:   19px;
	/* RENAMED from --type-eyebrow-lg. When the header and footer were
	 * pulled, 14px turned out to carry the header CTA label, the footer
	 * address, the footer phone and the footer links as well as the hero
	 * eyebrow - a general small-text size, not an eyebrow variant. The
	 * old name would have been wrong at four of its five uses. */
	--type-detail:  17px;
	--type-xs:      15px;
	--type-eyebrow: 13px;

	/* NOT FOUND IN THE DESIGN. The frame has nothing smaller than 12px.
	 * Retained because the footer legal line and the hamburger label
	 * use it, and dropping it would push those onto a size the design
	 * does use. Flagged for the designer. */
	--type-micro:   11px;

	/* Header navigation size. DELIBERATELY ITS OWN TOKEN - the header
	 * is chrome, not content, and should be re-scalable without moving
	 * page type. Nothing outside section 04 may use it.
	 *
	 * The design's Nav Links row (513:2428) sets 15px Regular, and that
	 * is what this was. 17 as of 2026-08-25: 15 was drawn for Inter and
	 * the header now sets in Lexend, which runs optically smaller at the
	 * same nominal size - the bar read undersized against a 220px logo
	 * and a 17px phone number sitting either side of it.
	 *
	 * Checked for room before raising it: at 1500 the nav still clears
	 * the phone by a wide margin. */
	--type-header-nav: 17px;

	/* ---- 1.9  Typography - WEIGHT ----
	 * All four confirmed in use in the frame. Unchanged.
	 */
	--weight-regular:  400;
	--weight-medium:   500;
	--weight-semibold: 600;
	--weight-bold:     700;

	/* ---- 1.10  Typography - LEADING ----
	 *
	 * The design sets line-height in ABSOLUTE PIXELS. These are those
	 * values converted to unitless ratios, because the heading ladder
	 * in 1.8b changes the font size at four breakpoints and a fixed
	 * pixel leading would not move with it - headings would collide at
	 * small sizes.
	 *
	 * The exact source figures, so the conversion can be checked:
	 *   H1        36 / 40  = 1.111
	 *   H2        32 / 38  = 1.1875
	 *   H5 card   19 / 24  = 1.263
	 *   body      16 / 22  = 1.375
	 *   body-lg   17 / 27  = 1.588
	 *   small     15 / 23  = 1.533
	 *
	 * H1 keeps its own token because 1.11 is visibly tighter than the
	 * 1.19 the other headings use, and that tightness is the headline's
	 * character.
	 *
	 * Card titles and small text land close enough to --leading-body
	 * and --leading-body-lg that they are not given tokens of their
	 * own; if the built page disagrees, add them rather than typing a
	 * number into a component.
	 */
	--leading-h1:      1.11;
	--leading-heading: 1.19;
	--leading-body:    1.375;
	--leading-body-lg: 1.6;

	/* ---- 1.11  Typography - TRACKING ----
	 *
	 * Kept in PIXELS because that is what the design specifies, and
	 * both values are applied at a single size each.
	 *
	 *   H1        -0.5px  (36px hero headline; H2-H4 carry none)
	 *   eyebrow   +1px    (both the 12px pill and the 14px hero line)
	 */
	--tracking-h1:      -0.5px;
	--tracking-heading:  0;
	--tracking-eyebrow:  1px;


	/* ---- 1.12  Radius ----
	 *
	 * The design is far rounder than the placeholder set assumed. There
	 * is no small radius in the frame at all - the smallest is 16px.
	 *
	 *   16   cards, media, Button Cards - the system radius
	 *   24   large panels and the hero image
	 *   999  pills: every primary CTA and every eyebrow chip
	 *
	 * 26px also appears once, on the hero's secondary "Call" button. On
	 * a 53px-tall control that is a pill in all but name, so it is not
	 * given a token; it resolves to --radius-pill when that button is
	 * built. Recorded here rather than lost.
	 */
	--radius:      16px;
	--radius-md:   24px;
	--radius-pill: 999px;

	/* NOT FOUND IN THE DESIGN. Used only by the focus ring and the skip
	 * link - browser affordances rather than design elements - so it is
	 * retained at its placeholder value. */
	--radius-sm:    2px;

	/* ---- 1.12a  THE NESTED RADIUS ----
	 *
	 * ALSO NOT IN THE DESIGN, AND THE HOLE IS THE POINT. The frame has
	 * nothing below 16, because it never draws a rounded box INSIDE
	 * another rounded box. The built site does - the dropdown rows sit
	 * inside the dropdown panel - and that case has a rule of its own:
	 *
	 *   NESTED CORNERS MUST GET LESS ROUND GOING INWARDS, NEVER MORE.
	 *   inner radius = outer inner-radius - the gap between them
	 *
	 * The panel is --radius (16) with a 1px border, so its inner edge
	 * curves at 15; the rows are inset 8 from it; 15 - 8 = 7, and the
	 * tie rule rounds up to 8.
	 *
	 * What it replaces: the rows were --radius-pill. A pill's end caps
	 * are drawn at HALF ITS HEIGHT, so a 39px row curved at 19.5 and a
	 * two-line 63px row at 31.5 - both ROUNDER than the 15px panel
	 * holding them, with 5px of clearance, and the two rows a different
	 * shape from each other. That is what read as off.
	 *
	 * Any future box nested in a --radius panel uses this, and if the
	 * inset changes the value has to be re-derived rather than kept. */
	--radius-nested: 8px;


	/* ---- 1.13  Layout ----
	 *
	 * The frame is 1440 wide with 150px of padding on each side, so its
	 * content column measures 1140. THE SITE DOES NOT USE THAT NUMBER.
	 *
	 * --container is 1400, matching Peninsula's --container-max and the
	 * house standard. The frame width is reference only, exactly as
	 * Peninsula's own stylesheet labels it. 1140 was never a decision
	 * here - it was a measurement copied off a guide - and it is why
	 * this page reads narrow on any modern desktop.
	 *
	 * !! --measure IS THE SECOND DIAL AND IT IS NOT OPTIONAL.
	 *
	 * A container and a reading width are different things. At the full
	 * 1400 a paragraph runs about 150 characters a line, roughly twice
	 * any comfortable maximum, so running text is capped separately
	 * while photographs, cards and panels take the whole width.
	 * Peninsula does the same thing, per component.
	 *
	 * !! THE RULE IS A CHARACTER COUNT, NOT A PIXEL WIDTH.
	 *
	 * --measure is 90 CHARACTERS PER LINE, expressed in px because this
	 * block is where px values live. It is NOT tuned to any one
	 * paragraph, and it must never be: every section carries a different
	 * amount of copy, so a width chosen to make one block land on four
	 * lines is wrong for the next block and for every page after this
	 * one.
	 *
	 * Deriving it: Lexend at 22px measures 10.67px per character across
	 * a representative English sample, so 90 characters is about 960px,
	 * which is 69% of the 1400 container.
	 *
	 * IF THE BODY SIZE EVER CHANGES, RE-DERIVE FROM 90 CHARACTERS. Do
	 * not scale this number, and do not re-measure it against a
	 * paragraph.
	 *
	 * Why 90. The classic advice is 45-85 for sustained reading. These
	 * are two-to-four sentence blocks that get scanned rather than read
	 * through, so a modest overshoot is defensible; past roughly 90 the
	 * eye starts losing its place on the return sweep, and that is a
	 * real effect rather than a style preference.
	 *
	 * 760 (54%, 69 characters) was the previous value. Textbook, and too
	 * cautious in practice - it made every section a narrow ribbon down
	 * the middle of a wide page.
	 *
	 * --container is the CONTENT width. .container in section 03 adds
	 * --container-pad on each side and widens its own max-width to
	 * match, so the content box is a true 1400 at desktop rather than
	 * 1400 minus padding.
	 */
	--frame:         1440px;   /* design frame width, reference only     */
	--container:     1400px;   /* content column - the house standard    */
	--measure:        960px;   /* 90 characters at the 22px body         */

	/* Framed blocks - the step diagrams, the bordered panel - are not
	 * prose and must not borrow --measure, but they are not full-width
	 * either. The design draws them at roughly half to two-thirds of the
	 * frame; at the full 1400 a four-step diagram stretches until the
	 * steps stop reading as a sequence and start reading as four
	 * separate things. 1040 is 74%: wider than the reading measure,
	 * because a diagram is scanned across rather than read, and well
	 * inside the container. */
	--container-panel: 1040px;

	/* THE STATEMENT PANEL, WHERE A PAGE INSETS IT.
	 *
	 * .itv-panel takes the full content column by default, because that
	 * is what 553:18 draws on Chiropractic Adjustments. The Magic Hug
	 * frame insets its equivalent: 569:104 is x260 w920 inside a content
	 * column that runs x150 to x1290. So the panel is 920 of 1140, or
	 * 80.7%, inset 110 a side from the column's own edges.
	 *
	 * 1400 x 0.807 = 1129.8. The site's standing mapping is that the
	 * design's 1140 content column IS our 1400 container (see 10.2), so
	 * the proportion scales and the inset comes out at 135 a side rather
	 * than the frame's 110.
	 *
	 * Gerek asked for "maybe indented by 100px on each side" from a
	 * screenshot. 135 is the same instinct read off the frame instead of
	 * off the eye - his 100 is almost exactly the frame's own 110, and
	 * the extra 25 is only the container being wider than the column it
	 * stands in for. */
	--container-statement: 1130px;

	--container-pad:   24px;   /* gutter inside .container               */
	--gutter:         150px;   /* the frame's own inset, reference only  */

	/* NEW - THE HEADER AND FOOTER ARE WIDER THAN THE PAGE CONTENT.
	 *
	 * Sections pad 150px a side; the header pads 96px (513:2395). That
	 * is not an inconsistency to normalise away - the chrome is meant to
	 * reach further out than the copy, and the header needs the extra
	 * room to fit six nav items plus a phone number and a CTA that the
	 * design's five-item bar never had to carry.
	 *
	 * WAS 1248, the frame's own 1440 - (96 * 2). That made sense while
	 * the content column was 1140. Now that --container is 1400 the old
	 * number is INVERTED - the chrome ended up narrower than the content
	 * it sits above, so the logo hung inside the hero panel's left edge
	 * instead of lining up with it.
	 *
	 * Matched to --container so the header's logo, the hero panel and
	 * every section below share one left edge and one right edge. The
	 * footer panel uses this token too and gains the same alignment. */
	--container-wide: 1400px;


	/* ---- 1.14  Spacing - THE APPROVED SCALE ----
	 *
	 * THESE EIGHT VALUES ARE THE ONLY SPACING ALLOWED ON THIS SITE.
	 *
	 *     8  12  16  20  24  32  40  48
	 *
	 * UNCHANGED BY THE DESIGN PULL, AND CONFIRMED BY IT: every one of
	 * the eight appears in the frame - 8 and 16 on the eyebrow pills,
	 * 12 inside the process cards, 20 and 24 on the section stacks, 32
	 * between the cards and inside the Button Cards, 40 on the Proof
	 * Bar, 48 under the intro.
	 *
	 * !! WHAT THE DESIGN DOES THAT THIS SCALE DOES NOT.
	 * Section VERTICAL padding in the frame runs 8, 40, 48, 57, 86 and
	 * 120px, with inter-block gaps of 60, 64 and 70. Those are past the
	 * 48px ceiling and follow no consistent step - they read as
	 * hand-tuned per section rather than as a system.
	 *
	 * No tokens were invented for them. Section rhythm is a decision to
	 * take when the sections are actually built, with the real page in
	 * front of you; guessing a scale now would bake in numbers nobody
	 * has agreed to.
	 *
	 * TIE RULE: a value landing exactly between two steps ROUNDS UP.
	 *
	 * !! NOT EVERYTHING THAT LOOKS LIKE SPACING IS SPACING. This scale
	 * governs padding, margin and gap. Corner radii, animation offsets,
	 * hairline widths and fixed component dimensions are their own
	 * thing and must not be bent onto it.
	 */
	--space-8:   8px;
	--space-12: 12px;
	--space-16: 16px;
	--space-20: 20px;
	--space-24: 24px;
	--space-32: 32px;
	--space-40: 40px;
	--space-48: 48px;
	--space-64: 64px;
	--space-72: 72px;

	/* ---- 1.14a  SECTION RHYTHM ----
	 * The standing rule for every section on every page: 100px of air
	 * above and below on desktop, 64 on mobile where 100 eats most of a
	 * phone screen before any content appears. Deliberately NOT part of
	 * the --space- scale above: that scale governs gaps inside a
	 * component, this governs the distance between them, and bending
	 * one onto the other is how sections end up drifting apart.
	 *
	 * Matches Peninsula's --section-space exactly, including its ladder:
	 * 100 / 80 / 72 / 64 / 56 down the breakpoints in 1.8b. The step
	 * lives on the token so no section rule has to know about it. */
	--section-pad: 100px;


	/* ---- 1.15  Header metrics ----
	 * Not a fixed height - it is the padding that is fixed, and the logo
	 * makes up the rest. The header scrolls away with the page as of
	 * 2026-08-25, so nothing needs to offset against it.
	 */
	/* The design's Header/Nav is py-24 (513:2395).
	 *
	 * !! THIS TRADES AGAINST THE LOGO SIZE. The header's height is
	 * padding + logo, and the brief is a bigger mark inside the SAME
	 * bar. 190px of logo is 52 tall and took 24 of padding for a 100px
	 * header; 220 is 60 tall and takes 20, for the same 100. Change one
	 * of these two numbers and the other has to move with it. */
	--header-pad-y: var(--space-20);
}


/* ---- 1.8b  RESPONSIVE HEADING LADDER ----
 * The four heading steps, and only those. Everything else is FLAT at
 * every width and deliberately absent from every block here.
 *
 * THE DESIGN IS DESKTOP ONLY - the frame is 1440px wide and there are
 * no other breakpoints in the file. This ladder is therefore the
 * theme's own work, not a design value: it takes the design's real
 * desktop sizes as the top step and walks them down by a fixed amount
 * so headings stay readable on a phone.
 *
 * Fixed decrement per breakpoint: H1 -3, H2 -3, H3 -2, H4 -1, H5 -1.
 *
 *            1440+   1199    991     767     599
 *   H1        36      33      30      27      24
 *   H2        32      29      26      23      20
 *   H3        24      22      20      18      17
 *   H4        22      21      20      19      18
 *   H5        19      18      17      17      16
 *
 * H1 steps by 3 rather than the 4 the placeholder ladder used, because
 * it starts 10px lower than the placeholder did and a -4 step would
 * have taken it under H2 by the last breakpoint.
 *
 * !! FLAGGED FOR THE DESIGNER. If mobile frames land later, this whole
 * block is replaced by their real values.
 * ================================================================== */
@media (max-width: 1199px) {
	:root { --type-h1: 46px; --type-h1-hero: 46px; --type-hero-lede: 24px;
	        --type-h2: 37px; --type-h3: 28px; --type-h4: 25px; --type-h5: 22px;
	        --section-pad: 80px; }
}
@media (max-width: 991px) {
	:root { --type-h1: 42px; --type-h1-hero: 41px; --type-hero-lede: 23px;
	        --type-h2: 34px; --type-h3: 26px; --type-h4: 24px; --type-h5: 21px;
	        --section-pad: 72px; }
}
@media (max-width: 767px) {
	/* !! THE BODY SIZE DOES NOT DROP HERE ANY MORE - SEE <=599.
	 *
	 * The rule is 22 on desktop, laptop AND tablet, 18 on mobile. This
	 * breakpoint is where the LAYOUT stacks, and 767 is the right place
	 * for that. It is the wrong place for the type: an iPad Mini in
	 * portrait is 744 CSS pixels, so it was landing in this block and
	 * being served 18px - phone type on a tablet, which is exactly what
	 * Gerek saw and why it looked small. Measured at 744: body 18px.
	 *
	 * Layout stacks at 767, type shrinks at 599. They are different
	 * questions and they now have different answers. */
	:root {
		--type-h1: 35px; --type-h1-hero: 34px; --type-hero-lede: 20px;
		--type-h2: 29px; --type-h3: 24px; --type-h4: 22px; --type-h5: 20px;
		--section-pad: 64px;
	}
}
@media (max-width: 599px) {
	/* THE MOBILE BODY RULE LIVES HERE NOW. Everything that reads as
	 * running copy is pegged to these tokens, so one step moves the lot,
	 * and it only fires on genuine phone widths. */
	:root { --type-h1: 31px; --type-h1-hero: 30px; --type-hero-lede: 19px;
	        --type-h2: 26px; --type-h3: 22px; --type-h4: 21px; --type-h5: 19px;
	        --type-body: 18px; --type-body-lg: 19px;
	        --type-ui: 17px; --type-small: 17px; --type-detail: 16px;
	        --section-pad: 56px; }
}


/* ==================================================================
 * 02  BASE
 *
 * Bare element fallbacks exist deliberately: every heading carries a
 * class OR the stylesheet provides element fallbacks. This build chose
 * fallbacks, so an unclassed heading can never silently render at body
 * size once copy arrives.
 * ================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
	margin: 0;
	background: var(--surface);
	color: var(--ink);
	font-family: var(--font-body);
	font-size: var(--type-body);
	font-weight: var(--weight-regular);
	line-height: var(--leading-body);
	-webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
	margin: 0 0 var(--space-16);
	font-family: var(--font-heading);
	font-weight: var(--weight-bold);
	line-height: var(--leading-heading);
	letter-spacing: var(--tracking-heading);
	color: var(--ink);
}

/* H1 carries its own leading and tracking. The design sets it tighter
 * than the other headings (1.11 against 1.19) and gives it the only
 * negative tracking in the frame; both are part of the headline's
 * character rather than a general heading rule. */
h1 {
	font-size: var(--type-h1);
	line-height: var(--leading-h1);
	letter-spacing: var(--tracking-h1);
}
h2 { font-size: var(--type-h2); }
h3 { font-size: var(--type-h3); }
h4 { font-size: var(--type-h4); }
/* h5 has a real step in the design now - 19px, the process card title -
 * so it no longer borrows H4's. h6 has no design equivalent and shares
 * h5 rather than being given an invented size. */
h5, h6 { font-size: var(--type-h5); }

p { margin: 0 0 var(--space-16); }

/* Links are ink, not accent. The accent is "barely used" by direction,
 * and body links are the single highest-volume link surface on a site -
 * colouring them would spend the accent everywhere at once. Underline
 * carries the affordance instead. */
/* NAVY ON HOVER, NOT AMBER.
 *
 * Amber is a SURFACE colour in this design - a fill, a border, a rule.
 * It is never text on a light ground, and for good reason: #fbb03c on
 * white measures about 1.9:1, which is unreadable. Navy measures about
 * 5.7:1 and passes AA.
 *
 * This applies to every interactive text state on the site: base links,
 * the nav, the footer, the article cards, and the focus ring. */
a {
	color: var(--ink);
	text-underline-offset: 0.15em;
}
/* !! :not(.cta-button) IS LOAD-BEARING - THE THIRD TIME THIS EXACT
 * SPECIFICITY TRAP HAS BITTEN THIS THEME.
 *
 * "a:hover" scores one class-equivalent plus one element, which BEATS
 * the bare ".cta-button" rule in section 04. Without the exclusion the
 * amber pill's label turned NAVY on hover - 2.4:1 against its own
 * background, and not the design's behaviour either. Caught on a real
 * render, not in review.
 *
 * The button's hover affordance is the brightness filter in section 04,
 * not a colour change. Its label is always --accent-contrast.
 *
 * The same guard already exists on ".mobile-menu a" in section 05, for
 * the same reason. Any future rule that colours a bare "a" must exclude
 * this class too. */
a:not(.cta-button):hover { color: var(--navy); }

/* Belt and braces: if a later rule does forget, this one is scoped
 * tightly enough to win the label back. */
.cta-button:hover { color: var(--accent-contrast); }

img, svg { max-width: 100%; height: auto; }

button { font: inherit; }

address { font-style: normal; }

/* Visible focus on everything interactive. Built in the first time,
 * never retrofitted. This is one of only two places the accent appears
 * in Stage 1. */
:focus-visible {
	outline: 3px solid var(--navy);
	outline-offset: 2px;
	/* --radius-sm, not a typed-in 2px. This is a corner radius, so the
	 * radius scale governs it - the SPACING scale is the one that does
	 * not, which is what the old note here was getting at. Using the
	 * token removes the last duplicated value outside section 01. */
	border-radius: var(--radius-sm);
}


/* ==================================================================
 * 03  SHARED UTILITIES
 * ================================================================== */

/* --container is the CONTENT width the design draws (1140px). The
 * padding is added ON TOP of it rather than eaten out of it, so the
 * content box is a true 1140 at desktop instead of 1140 minus 48. Below
 * that width the padding becomes the screen-edge gutter as usual. */
.container {
	width: 100%;
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	margin-inline: auto;
	padding-inline: var(--container-pad);
}

.visually-hidden:not(:focus):not(:active) {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.skip-link {
	position: absolute;
	top: var(--space-8);
	left: var(--space-8);
	z-index: 100;
	padding: var(--space-8) var(--space-16);
	background: var(--surface);
	color: var(--ink);
	border: 1px solid var(--ink);
	border-radius: var(--radius-sm);
	text-decoration: none;
}
.skip-link:not(:focus):not(:active) {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* Page content needs air under the sticky header.
 *
 * Without this the H1 sat flush against the header's bottom hairline -
 * the Stage 1 shells had no content, so the gap never showed up until
 * the 3.6 copy landed.
 *
 * padding-top only. The footer already carries its own margin-top, so a
 * symmetrical padding-block would stack the two into a 96px gap at the
 * bottom and 48 at the top.
 *
 * min-height stays until every page has copy: a page that is still an
 * empty shell would otherwise collapse and put the footer directly under
 * the header. It costs nothing on a page that has content. */
.site-main {
	min-height: 40vh;
	padding-top: var(--space-48);
}


/* ==================================================================
 * 04  SITE HEADER
 *
 * VISUAL SOURCE: Figma 513:2395 "Header/Nav" - 96px side padding,
 * 24px vertical padding, 15px Regular nav at a 32px gap, and the amber
 * pill CTA on the right.
 *
 * !! STRUCTURE IS THE LOCKED SITE MAP, NOT THE DESIGN.
 * The design's bar shows five flat links including "Health Resources",
 * a deleted page. It was not built. The real menu is six Tier-1 items,
 * three of which are unlinked labels opening dropdowns, and it comes
 * from the WordPress 'primary' menu location - nothing here is
 * hardcoded. The design contributed the LOOK; the database owns the
 * STRUCTURE.
 *
 * TWO THINGS THE DESIGN DOES NOT HAVE, added deliberately:
 *   1. The phone number. Click-to-call is required at every breakpoint.
 *   2. A bottom hairline. The header is sticky, which a static 1440px
 *      frame never had to account for; without it, content scrolls
 *      under the bar with no edge.
 * ================================================================== */

/* NAVY, THE SAME GRADIENT AS THE STATS BAND.
 *
 * White-on-white was the wrong call and did not survive contact - a
 * scroll shadow separates the header from the page but does nothing for
 * the flatness of the thing, and above the fold the site read as white
 * on white on white. This uses --gradient-navy, the identical fill the
 * stats band in section 9.4 carries, so the two dark bands bracket the
 * hero and the page has somewhere to start.
 *
 * The HEIGHT is unchanged - it clones the band's colour, not its
 * proportions.
 *
 * Everything in here inverts with it: the lockup swaps to the white
 * horizontal one in header.php, the nav and phone go to --on-invert,
 * and hover becomes amber rather than navy, since navy on navy is
 * nothing. */
/* NOT STICKY. It scrolls away with the page, by decision on
 * 2026-08-25.
 *
 * The scroll-elevation shadow that used to live here went with it, and
 * so did the .is-stuck handler in main.js - both existed only to give a
 * pinned header an edge against the content sliding under it. With
 * nothing pinned there is nothing to separate, and leaving either
 * behind would have been a listener and a rule doing no work.
 *
 * z-index is kept: the dropdown panels are children of this element and
 * still have to open over the page below. */
.site-header {
	position: relative;
	z-index: 50;
	background: var(--gradient-navy);
	color: var(--on-invert);
	border-bottom: 0;
}

/* LOGO HARD LEFT, CTA HARD RIGHT, NAV IN THE SPACE BETWEEN.
 *
 * space-between does the pinning: with three children the first sits at
 * the start edge, the last at the end edge, and the nav takes what is
 * left. The alignment only reads as deliberate because the header now
 * shares its width with the content below it - see --container-wide. */
.site-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-24);
	max-width: calc(var(--container-wide) + (var(--container-pad) * 2));
	margin-inline: auto;
	padding-inline: var(--container-pad);
	padding-block: var(--header-pad-y);
}
/* Neither end may be pushed off its edge by the nav growing. */
.site-header__inner > .site-header__logo,
.site-header__inner > .site-header__actions { flex: 0 0 auto; }

/* THE NAV SITS RIGHT, NOT CENTRED - but it does not touch the phone.
 *
 * margin-left:auto pushes the whole menu toward the actions group; the
 * margin-right is the guaranteed gap, so the last menu item and the
 * phone number can never end up reading as one run of text. Centred,
 * the bar had a lake of empty navy on the right and the three right-
 * hand elements looked unrelated to each other. */
.site-header__inner > .primary-nav {
	flex: 0 1 auto;
	margin-left: auto;
	margin-right: var(--space-40);
}

.site-header__logo {
	display: flex;
	flex-shrink: 0;
	text-decoration: none;
	color: inherit;
}

/* The header lockup. Sized on the IMG rather than a max-width so the
 * intrinsic width/height on the tag and the CSS agree and nothing
 * reflows while it loads.
 *
 * 220, up from 190 and originally 124. At 124 the word CHIROPRACTIC was
 * under 6px tall - the mark was present without being readable.
 *
 * The BAR IS THE SAME HEIGHT it was at 190: --header-pad-y dropped from
 * 24 to 20 to pay for the extra 8px of logo. 220 x (104/380) = 60.2,
 * plus 20 top and 20 bottom, is the same 100px the header has always
 * been. See --header-pad-y in 1.15. */
.site-header__logo img {
	display: block;
	width: 220px;
	height: auto;
}
/* The MOBILE MENU reuses this class on a white panel with the standard
 * dark lockup, so it must not inherit the header's larger size. */
/* 150 -> 260, about 75% up, which is what Gerek asked for. It only fits
 * because the Close pill was lifted out of this row - see
 * .mobile-menu__top. Shared with Close it overflowed by 22px at 375. */
.mobile-menu .site-header__logo img { width: 260px; }

.site-header__actions {
	display: flex;
	align-items: center;
	gap: var(--space-20);
	flex-shrink: 0;
}

/* Click-to-call. Present at EVERY breakpoint - never hidden, never
 * reduced to an icon. Not in the design; required by the brief. */
.header-phone {
	display: inline-flex;
	align-items: center;
	gap: var(--space-8);
	font-size: var(--type-detail);
	font-weight: var(--weight-semibold);
	color: var(--on-invert);
	text-decoration: none;
	white-space: nowrap;
}
/* Optical, not mechanical: the handset glyph reads heavier than the
 * digits beside it at the same box size, so it is nudged down a step. */
.header-phone__icon { width: 17px; height: 17px; flex-shrink: 0; }
.header-phone:hover { color: var(--accent); }

/* The mobile menu no longer carries a phone link - see header.php - and
 * its panel is navy anyway, so the white-ground override that used to
 * live here is gone. */

/* The header CTA is a SMALLER pill than the ones in the page body.
 * Chrome, not a call to action in its own right: it sits on one line
 * with a logo, five nav items and a phone number, and at body size it
 * crowds all of them. Figma draws it at 33px tall against the 48px of
 * the in-page buttons. */
.site-header__actions .cta-button {
	padding: var(--space-8) var(--space-16);
	font-size: var(--type-detail);
}

/* THE AMBER PILL CTA.  Figma 362:2 / 350:81.
 *
 * Label is "Book New Patient Appointment" - the locked wording. The
 * design's own label reads "Book Appointment" and the MENU item for the
 * same page reads "Book Now". Three different strings; this is the one
 * that ships. See hbchiropractic_cta_label().
 *
 * !! THE DESIGN'S FILL CANNOT BE EXPORTED. Figma describes this
 * component as a "glass-amber pill (shader gradient fill, glossy
 * multi-stop gradient border, GLASS blur effect)". A shader fill has no
 * CSS equivalent, and Figma exports only the amber border and the drop
 * shadow. This is the faithful, buildable reading: a solid amber pill
 * carrying the design's real drop shadow. If the glass treatment
 * matters, it needs a decision and probably an exported asset.
 *
 * Text is INK on amber, per --accent-contrast. Never white.
 *
 * The HEADER's instance is deliberately smaller than the in-page ones -
 * see .site-header__actions .cta-button below. */
/* !! THE AMBER PILL'S LABEL IS INK, AND THIS RULE ENFORCES IT.
 *
 * Four separate rules in this file colour a bare "a" inside a dark
 * region - .site-footer, .hp-proof, .hp-final, .mobile-menu - and every
 * one of them outscores the plain .cta-button/.hp-btn-line declarations
 * because a class-plus-element beats a class. Each has been patched
 * with :not(.cta-button) as it was found, one at a time, and the audit
 * that followed the footer fix turned up yet another: .hp-btn-line a
 * inside .hp-proof, white on amber.
 *
 * Measured, not judged: white on #fbb03c is 1.85:1 against a 4.5:1
 * minimum. Ink is 9.99:1. The note on --accent-contrast in 1.2 says
 * never to "fix" this to white.
 *
 * So rather than guard the next one after it ships, this states the
 * rule once at a specificity nothing casual will beat. Adding a new
 * dark region no longer means remembering the exclusion. */
.cta-button,
.hp-cta-row a:first-of-type,
.hp-btn-line a {
	color: var(--accent-contrast);
}

/* Three layers stacked in one element: the rim is painted on the
 * border-box, the fill on the padding-box, and the gloss sits over the
 * top half as a pseudo-element. isolate keeps that pseudo-element's
 * stacking to itself so it can never slide over a neighbouring pill. */
.cta-button {
	position: relative;
	isolation: isolate;
	display: inline-block;
	padding: var(--space-8) var(--space-20);
	background:
		var(--accent-fill) padding-box,
		var(--accent-rim) border-box;
	/* 2px, not 1: at a hairline the multi-stop ring averages out to a
	 * pale outline and the metal is lost. */
	border: 2px solid transparent;
	color: var(--accent-contrast);
	font-size: var(--type-detail);
	font-weight: var(--weight-semibold);
	line-height: 1.4;
	text-decoration: none;
	border-radius: var(--radius-pill);
	box-shadow: var(--accent-lift);
	white-space: nowrap;
	transition: box-shadow .18s ease, transform .18s ease;
}
/* The gloss. inset-block-end:50% is the whole trick - it covers the top
 * half and stops, so the highlight has an edge to it rather than fading
 * the entire pill out. */
.cta-button::before {
	content: "";
	position: absolute;
	inset: 2px 2px 50%;
	z-index: -1;
	border-radius: var(--radius-pill) var(--radius-pill) 0 0;
	background: var(--accent-gloss);
	pointer-events: none;
}
.cta-button:hover {
	box-shadow: 0 6px 12px rgba(255, 139, 0, 0.42), 0 0 26px rgba(251, 176, 60, 0.55);
	transform: translateY(-1px);
}
/* !! RETURNING TO THE FLOOR IS NOT A PRESS STATE. This used to be only
 * `transform: translateY(0)` - so pressing a button undid the hover lift
 * and did nothing else, and on a button already sitting flat under a
 * finger on a touchscreen it did nothing at all.
 *
 * Gerek's report on Momma's, 2026-09-02, was "I barely could tell that
 * the button's even working". HB's hover is in better shape than
 * Momma's was - it has a real glow and a lift - but the press was the
 * same near-silence, and on checkout that invites a second click on a
 * payment form.
 *
 * A press now darkens and collapses the glow as well as dropping: three
 * changes at once, so it reads as a physical press rather than as the
 * hover merely stopping. */
.cta-button:active {
	transform: translateY(0);
	filter: brightness(.92);
	box-shadow: 0 1px 3px rgba(255, 139, 0, .35);
}

/* Keyboard users got no indication at all. :focus-visible so it appears
 * for tabbing without firing on a mouse click. */
.cta-button:focus-visible {
	outline: 3px solid var(--navy);
	outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
	.cta-button { transition: none; }

	/* !! ONLY THE MOVEMENT GOES. Somebody who asks for reduced motion
	 * still needs to know their click landed, so the colour and shadow
	 * changes stay - dropping the whole :active rule here would restore
	 * exactly the silence this fix exists to end. */
	.cta-button:hover,
	.cta-button:active {
		transform: none;
	}
}
.cta-button:hover { filter: brightness(1.06); }

/* A CTA button standing alone as a list item drops its bullet.
 *
 * The booking form's field list ends with its submit control, so the
 * 3.6 document has "[Button: Continue to Next Step]" as the last item
 * of that list. Rendered as a real button it kept the disc marker, and
 * a bullet in front of a pill reads as a mistake.
 *
 * :only-child is deliberate - a list item that mixes a button with text
 * is still a list item and keeps its marker. */
li:has(> .cta-button:only-child) {
	list-style: none;
	margin-left: calc(var(--space-8) * -1);
}

/* ---- primary navigation ---- */
/* GAP PAYS FOR THE FONT SIZE, so the two move together.
 *
 * Raising the nav from 15 to 17 added about 56px across six items -
 * type scales, gaps do not. Dropping the gutter from 32 to 24 gives 40
 * of that back, which keeps the bar close to the width it has always
 * been without setting the words at a size that reads small.
 *
 * The recovered width comes off the LEFT, not the right: the nav is
 * pinned by a fixed 40px margin against the actions group and floated
 * over by an auto margin on the other side, so narrowing it opens space
 * between the logo and the first menu item and leaves the 64px gap to
 * the phone number untouched. Verified at 1500. */
.primary-nav > ul {
	display: flex;
	align-items: center;
	gap: var(--space-24);
	margin: 0;
	padding: 0;
	list-style: none;
}
.primary-nav li { position: relative; }

/* Tier 1 sits at full strength on the navy ground - not a muted grey.
 * The muted step is used inside the dropdowns instead, which fall on
 * white and keep their ink colours. */
.primary-nav > ul > li > a,
.primary-nav > ul > li > span {
	display: block;
	padding-block: var(--space-8);
	color: var(--on-invert);
	font-size: var(--type-header-nav);
	font-weight: var(--weight-regular);
	text-decoration: none;
	white-space: nowrap;
}

/* AMBER ON HOVER, NOT NAVY. The old hover was --navy, which on a navy
 * header is an invisible state change. Amber is the accent the header
 * already carries in the lockup and the CTA, so the bar stays a
 * two-colour object.
 *
 * !! SCOPED TO TIER 1. The dropdown panels are white; amber text on
 * white measures about 1.9:1 and would fail outright. Their hover is
 * set separately below. */
.primary-nav > ul > li > a:hover,
.primary-nav > ul > li.current-menu-item > a,
.primary-nav > ul > li.current-menu-ancestor > a { color: var(--accent); }

/* THE THREE UNLINKED LABELS.
 *
 * Services, New Patients and Resources have no page behind them and
 * render as <span>, never <a> - see Hbchiropractic_Nav_Walker. They are
 * styled to look identical to their linked siblings so the bar reads as
 * one row, but they are not links and never enter the tab order. */
.primary-nav > ul > li > span { cursor: default; }

/* A caret marks the four items that open something, so a label-only
 * parent does not read as a dead link. Drawn in CSS rather than shipped
 * as an icon; the 5px box is a component dimension. */
.primary-nav .menu-item-has-children > a::after,
.primary-nav .menu-item-has-children > span::after {
	content: "";
	display: inline-block;
	width: 5px;
	height: 5px;
	margin-left: var(--space-8);
	border-right: 1.5px solid currentColor;
	border-bottom: 1.5px solid currentColor;
	transform: translateY(-2px) rotate(45deg);
}

/* The gutter is tighter than it looks because each row now carries its
 * own padding - see the hover row below. 4 here plus 8 inside each row
 * is the 12 the rows used to sit apart, so nothing moved. */
.primary-nav .sub-menu {
	position: absolute;
	top: 100%;
	left: 0;
	z-index: 60;
	display: grid;
	gap: 4px;
	min-width: 240px;
	margin: 0;
	padding: var(--space-16);
	background: var(--surface);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	box-shadow: var(--shadow-card);
	list-style: none;
	opacity: 0;
	visibility: hidden;
	/* A literal: how far the panel slides while it fades in, not a gap
	 * between elements. Animation distance is its own concern. */
	transform: translateY(4px);
	transition: opacity .15s ease, transform .15s ease, visibility .15s;
}
.primary-nav li:hover > .sub-menu,
.primary-nav li:focus-within > .sub-menu {
	opacity: 1;
	visibility: visible;
	transform: none;
}
/* A FILLED ROW ON HOVER, NOT JUST A COLOUR CHANGE.
 *
 * These rows used to go --ink-muted to --navy on hover and nothing
 * else. Both are dark colours on white, so the change measured as a
 * shift in hue rather than in brightness - you could tell something had
 * happened but not easily which row it was, and on a trackpad moving
 * fast that is no feedback at all.
 *
 * The row now takes a filled pill AND the colour change. The fill is
 * what the eye catches; the colour is what confirms it. Padding lives
 * on the anchor rather than the list item so the whole row is the hit
 * target, not just the words.
 *
 * NOT AMBER. Amber on white measures about 1.9:1 - it is a surface
 * colour in this palette, never a text colour on a light ground. The
 * tier-1 items in the navy bar hover amber because their ground is
 * navy; these do not, and that difference is deliberate. */
/* !! THE ROW IS NOT A PILL, AND --radius-nested EXPLAINS WHY.
 *
 * Short version: a pill inside a 16px panel curves harder than the
 * panel does, and a two-line row curves harder still than a one-line
 * one, so the highlighted row was a different shape from its
 * neighbours. Measured on the real menu: 19.5px caps on the 39px rows,
 * 31.5 on the 63px "Chiropractic Adjustments", inside a 15px panel with
 * 5px of clearance.
 *
 * The inset goes 5 -> 8 at the same time. 5px between a filled row and
 * the panel's own border is not clearance, it is a near-miss, and it is
 * half of why the row looked like it was bursting out of the box.
 *
 * !! THE TEXT DOES NOT MOVE. padding-inline and the negative margin are
 * the same number, so the label still starts exactly on the panel's
 * 16px padding edge and lines up with the tier-1 item above it. Change
 * one of the two and the label shifts; they are a pair. */
.primary-nav .sub-menu a {
	display: block;
	padding: var(--space-8);
	margin-inline: calc(var(--space-8) * -1);
	border-radius: var(--radius-nested);
	color: var(--ink-muted);
	font-size: var(--type-detail);
	text-decoration: none;
	white-space: normal;
	transition: background-color .12s ease, color .12s ease;
}
.primary-nav .sub-menu a:hover,
.primary-nav .sub-menu a:focus-visible {
	background: var(--fill-tint);
	color: var(--navy);
}
/* The page you are already on is shown in the same row, held down.
 * Without it the current row is the only one that never lights up, and
 * that reads as broken rather than as "you are here". */
.primary-nav .sub-menu .current-menu-item > a {
	background: var(--fill-tint);
	color: var(--navy);
	font-weight: var(--weight-semibold);
}
@media (prefers-reduced-motion: reduce) {
	.primary-nav .sub-menu a { transition: none; }
}

/* ---- hamburger ---- */
/* !! THIS BUTTON SITS ON THE NAVY HEADER, NOT ON WHITE.
 *
 * It was built when the header was white: --rule for the border,
 * --ink for the label. On navy that rendered as a pale pill with dark
 * lettering - the one light-on-dark object in a bar of white-on-dark,
 * and the thing the eye went to first.
 *
 * Now it matches the nav beside it: transparent, white hairline, white
 * bars. The label is visually hidden - see the note in header.php - so
 * the button is square and the glyph is the whole content. */
.nav-toggle {
	display: none;
	align-items: center;
	justify-content: center;
	width: 46px;
	height: 46px;
	padding: 0;
	background: none;
	border: 1px solid rgba(255, 255, 255, 0.45);
	border-radius: var(--radius-pill);
	color: var(--on-invert);
	cursor: pointer;
	transition: background-color .15s ease, border-color .15s ease;
}
.nav-toggle:hover { background: rgba(255, 255, 255, 0.12); border-color: var(--on-invert); }
.nav-toggle__bars {
	display: grid;
	/* 5px is the bar gap inside a fixed 20px glyph - a component
	 * dimension, not layout spacing. */
	gap: 5px;
	width: 20px;
}
.nav-toggle__bars span {
	display: block;
	height: 2px;
	background: currentColor;
}


/* ==================================================================
 * 05  MOBILE MENU
 *
 * Carries the six Tier-1 items with Tier 2 as expandable sub-menus.
 * Hidden from assistive tech and from tab order while closed.
 *
 * No design exists for this - the Figma file is a single 1440px frame.
 * See section 07 for what was decided and why.
 * ================================================================== */

/* !! width:100% IS NOT REDUNDANT NEXT TO inset:0.
 *
 * A fixed element with left:0, right:0 and width:auto is shrink-to-fit
 * clamped at its own min-content, so content that cannot shrink makes
 * the box WIDER than the viewport rather than overflowing inside it.
 * This drawer's nav min-content is about 480px; at a 22px body it grew
 * to 528 on a 390px phone and gave the whole site a horizontal
 * scrollbar - the page itself was fine, the closed menu was dragging it
 * sideways. width:100% resolves the over-constraint to the viewport and
 * lets the nav wrap inside it instead.
 *
 * Found by measuring scrollWidth against clientWidth at 390px. Worth
 * re-running that check after any type-scale change, because this is
 * exactly the kind of failure a bigger font reintroduces. */
.mobile-menu {
	position: fixed;
	inset: 0;
	width: 100%;
	z-index: 90;
	display: grid;
	grid-template-rows: auto 1fr;
	/* NAVY, like the bar it opens from. It was --surface, which meant
	 * tapping the menu turned the whole header white - the site changed
	 * colour to show you a list. Everything inside inverts with it. */
	background: linear-gradient(180deg, #1e3f70 0%, #16345a 60%, #102c46 100%);
	color: var(--on-invert);
	overflow-x: hidden;
	overflow-y: auto;
	visibility: hidden;
	opacity: 0;
	transition: opacity .2s ease, visibility .2s;
}
/* Grid children default to min-width:auto, which would hand the
 * min-content problem straight back. */
.mobile-menu > * { min-width: 0; }
.mobile-menu[data-open="true"] { visibility: visible; opacity: 1; }

/* !! CLOSE IS OUT OF FLOW. THE LOGO OWNS THIS ROW. DO NOT PUT THEM BACK
 * SIDE BY SIDE.
 *
 * This was space-between, and it worked while the logo was 150. At 260
 * it does not: 260 logo + 16 gap + 97 pill + 48 of gutter is 421 against
 * a 375 panel, so Close was pushed 22px off the right edge - measured,
 * close.right 397 on a 375 viewport. The pill is the smaller element
 * but flex kept the img at its declared width and overflowed instead.
 *
 * So Close is absolute in the corner and the logo gets the full 327.
 * The logo stays LEFT rather than centred so its edge lines up with
 * "Home" and the rest of the nav directly beneath it - that shared edge
 * reads better here than centring the mark on its own would. */
.mobile-menu__top {
	position: relative;
	/* 84 not 72: the pill and the logo still overlap horizontally (the
	 * pill starts at 254, the logo runs to 284), so they need real
	 * vertical air between them. 72 left 9px and read as a near-miss. */
	padding: 84px var(--container-pad) var(--space-16);
}
.mobile-menu__close {
	position: absolute;
	top: var(--space-16);
	right: var(--container-pad);
	padding: var(--space-12) var(--space-20);
	background: none;
	border: 1px solid rgba(255, 255, 255, 0.45);
	border-radius: var(--radius-pill);
	color: var(--on-invert);
	font-size: var(--type-xs);
	font-weight: var(--weight-semibold);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	cursor: pointer;
}

.mobile-menu__body { padding: var(--container-pad); }

/* NO DIVIDERS. Six rules between six items turned a short menu into a
 * table; the spacing already separates them. */
.mobile-menu ul { margin: 0; padding: 0; list-style: none; }

/* !! :not(.cta-button) IS LOAD-BEARING - DO NOT SIMPLIFY IT AWAY.
 *
 * ".mobile-menu a" scores one class plus one element, which BEATS the
 * bare ".cta-button" rule in section 04. Without this exclusion the
 * booking button was repainted to --ink text on its own background and
 * rendered invisible but still clickable. That bug shipped once and was
 * caught on a real phone-width render, not in review.
 *
 * The CTA is now amber rather than ink, so the failure would look
 * different - ink-on-amber is legible - but the rule would still be
 * stealing the button's font-size and padding. The exclusion stays. */
.mobile-menu a:not(.cta-button),
.mobile-menu .menu-item > span {
	display: block;
	padding: var(--space-12) 0;
	color: var(--on-invert);
	font-size: var(--type-h4);
	text-decoration: none;
}

/* !! THE THREE UNLINKED PARENTS LOOK LIKE EVERY OTHER ITEM NOW.
 *
 * Services, New Patients and Resources have no page behind them and
 * render as <span> rather than <a>. They used to be styled as section
 * headings - smaller, uppercase, muted - to signal "not a link". In
 * practice that read as three different fonts in one list, and the
 * distinction it was drawing does not matter here: every one of them
 * opens a sub-menu on tap, so they all behave the same way.
 *
 * They keep cursor:default and stay out of the tab order, which is
 * where the real difference belongs. */
.mobile-menu .menu-item > span { cursor: default; }

.mobile-menu .sub-menu {
	padding-left: var(--space-20);
	padding-bottom: var(--space-8);
	border-left: 1px solid rgba(255, 255, 255, 0.22);
}
.mobile-menu .sub-menu a {
	font-size: var(--type-ui);
	color: rgba(255, 255, 255, 0.82);
}

/* ---- expandable sub-menus ----
 *
 * !! THE COLLAPSED STATE IS GATED BEHIND .has-js-submenus, which the
 * script adds. Without JavaScript no toggle is injected, so a rule that
 * hid sub-menus by default would hide fifteen links behind a control
 * that does not exist. Failing this way makes the menu longer; failing
 * the other way makes it unusable. */
.mobile-menu .has-submenu > a,
.mobile-menu .has-submenu > span { grid-area: label; }

.has-js-submenus .has-submenu {
	display: grid;
	grid-template-columns: 1fr auto;
	grid-template-areas: "label toggle" "sub sub";
	align-items: center;
}
.has-js-submenus .has-submenu > .sub-menu { grid-area: sub; }
.has-js-submenus .has-submenu > .submenu-toggle { grid-area: toggle; }

.has-js-submenus .has-submenu > .sub-menu { display: none; }
.has-js-submenus .has-submenu.is-open > .sub-menu { display: block; }

.submenu-toggle {
	display: flex;
	align-items: center;
	justify-content: center;
	/* 44px is the minimum comfortable touch target. A component
	 * dimension, not layout spacing. */
	width: 44px;
	height: 44px;
	padding: 0;
	background: none;
	border: 0;
	color: var(--on-invert);
	cursor: pointer;
}
.submenu-toggle__icon {
	display: block;
	width: 8px;
	height: 8px;
	border-right: 2px solid currentColor;
	border-bottom: 2px solid currentColor;
	transform: translateY(-2px) rotate(45deg);
	transition: transform .15s ease;
}
.has-submenu.is-open > .submenu-toggle .submenu-toggle__icon {
	transform: translateY(2px) rotate(-135deg);
}

.mobile-menu__actions {
	display: grid;
	gap: var(--space-16);
	margin-top: var(--space-32);
	justify-items: start;
}
.mobile-menu__actions .cta-button { text-align: center; width: 100%; }

body.is-menu-open { overflow: hidden; }


/* ---- 05b  BACK TO TOP ----
 *
 * The control itself is NOT in this theme. It is a must-use plugin,
 * wp-content/mu-plugins/itv-back-to-top.php, so it survives a theme
 * swap and a core update and cannot be switched off from wp-admin. It
 * is an <a href="#top"> and works with no JavaScript; the script only
 * decides when to show it.
 *
 * !! DO NOT ADD A SECOND ONE TO THIS THEME. If a theme ever ships its
 * own it must declare add_theme_support( 'itv-back-to-top' ) so the
 * plugin stands down - otherwise the page gets two arrows. This theme
 * deliberately does not: it recolours the plugin's instead, which is
 * what the three custom properties below exist for. Editing the plugin
 * file per client is the thing this mechanism is here to avoid.
 *
 * White surface with a navy chevron rather than the plugin's neutral
 * near-black. It has to work on all three of this page's grounds -
 * white, the cream of OUR SYSTEM, and the navy footer - and a white
 * pill is the only one of the three that never disappears into its
 * background. Amber was the other candidate and was rejected: it is the
 * booking button's colour, and a floating amber pill in the corner
 * competes with the thing the page actually wants pressed.
 *
 * !! DOUBLED CLASS, DELIBERATELY. The plugin prints its CSS inline on
 * wp_head, which lands AFTER this stylesheet's <link> - measured at
 * char 2920 against 2716. A plain .to-top here is the same specificity
 * and therefore loses on order, which is exactly what happened on the
 * first attempt: the arrow rendered in the plugin's neutral near-black
 * with nothing in this file looking wrong. .to-top.to-top wins on
 * specificity instead, so it does not matter what order the two land
 * in. */
.to-top.to-top {
	--to-top-ink: var(--navy);
	--to-top-surface: rgba(255, 255, 255, 0.94);
	--to-top-border: rgba(32, 94, 150, 0.22);
	border-radius: var(--radius-pill);
}


/* ==================================================================
 * 06  SITE FOOTER
 *
 * VISUAL SOURCE: Figma 513:2740 - a rounded dark panel inset from the
 * page edge, brand lockup over the address and phone on the left, a
 * compact link block on the right, a hairline, then the legal line.
 *
 * The design paints that panel a translucent pale blue sitting on a
 * dark section. Standing alone on a white page it needs its own ground,
 * so it takes the navy gradient the design already uses for its
 * full-width Button Cards - the same dark navy treatment, from the same
 * file.
 *
 * !! LINKS ARE THE LOCKED FOUR, NOT THE DESIGN'S.
 * The design lists service pages and "Health Resources", a deleted
 * page. The real footer menu is Careers, Privacy Policy, Terms and
 * Accessibility - and those four appear in NO other menu.
 *
 * !! NO EMAIL ADDRESS. That is a decision, not an omission.
 * ================================================================== */

/* THE FOOTER IS THE WHOLE NAVY REGION, CTA INCLUDED.
 *
 * The design (513:2733) runs one dark section from "Ready to Find Out
 * ..." down to the copyright line, with the footer panel inset inside
 * it as a lighter blue-on-blue block. This used to be two things - a
 * homepage-only .hp-final and a global footer that started below it -
 * and the seam between two different navies showed on every page.
 *
 * Now: .site-footer paints the region, .site-footer__cta sits directly
 * on it, and .site-footer__panel is the lighter inset. The CTA is in
 * footer.php, so all 19 pages get it. */
/* !! ITS OWN GRADIENT, DARKER THAN --gradient-navy, AND THAT IS THE
 * WHOLE REASON THE PANEL READS.
 *
 * This used --gradient-navy, which starts at #205e96 - light. Measured
 * on the render: at the panel's top edge the section was rgb(25,73,116)
 * against a panel of rgb(36,70,115). One value apart in blue. The box
 * genuinely was invisible there, and only separated further down as the
 * section's gradient got dark enough to pull away.
 *
 * Both gradients ran the same direction, so they tracked each other.
 * The section is now dark end to end and the panel is uniformly
 * lighter, which is the relationship Figma has: a dark ground with a
 * lighter block sitting on it, not two similar blues crossing.
 *
 * NOT a change to --gradient-navy - the stats band and the link bars
 * use that token and are correct as they are. */
.site-footer {
	margin-top: 0;
	padding: var(--section-pad) var(--container-pad) var(--space-24);
	background: linear-gradient(180deg, #1e3f70 0%, #1a3560 55%, #142845 100%);
	color: var(--on-invert);
}

/* ---- the closing CTA ---- */
.site-footer__cta {
	max-width: calc(var(--measure) + (var(--container-pad) * 2));
	margin-inline: auto;
	/* Half the standing rhythm. The CTA and the panel are two halves of
	 * one closing region, not two sections, so the full 100 read as a
	 * gap between unrelated things. */
	padding-bottom: calc(var(--section-pad) / 2);
	text-align: center;
}
.site-footer__cta-heading {
	margin: 0 0 var(--space-16);
	font-size: var(--type-h2);
	color: var(--on-invert);
}
.site-footer__cta-sub {
	margin: 0 0 var(--space-32);
	font-size: var(--type-body-lg);
	color: var(--on-invert);
}
.site-footer__cta-row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--space-20);
	margin: 0 0 var(--space-24);
}
/* The larger pill. The header's is chrome; this one is the last thing
 * on the page and is meant to be pressed. */
.cta-button--lg {
	padding: var(--space-16) var(--space-32);
	font-size: var(--type-small);
}
/* The outlined twin. Same size, no fill - two amber pills side by side
 * would give the eye nowhere to go. */
.footer-cta-call {
	display: inline-block;
	padding: var(--space-16) var(--space-32);
	font-size: var(--type-small);
	font-weight: var(--weight-semibold);
	color: var(--on-invert);
	border: 1px solid var(--on-invert);
	border-radius: var(--radius-pill);
	white-space: nowrap;
}
.site-footer .footer-cta-call:hover {
	background: var(--on-invert);
	color: var(--navy-deep);
	text-decoration: none;
}
.site-footer__cta-talk {
	margin: 0;
	font-size: var(--type-detail);
	color: var(--rule);
}
.site-footer__cta-talk a { color: var(--on-invert); }

/* ---- the inset panel ----
 *
 * A DIFFERENT BLUE, NOT A WHITE WASH. This was rgba(255,255,255,.06)
 * over the section, which is the obvious way to lift a panel and was
 * far too quiet - Gerek read it as blue on blue and he was right.
 *
 * Sampled off the design render instead of guessed. Outside the panel
 * the section is about rgb(29,56,106); inside it runs rgb(39,76,130) at
 * the top left to rgb(29,51,73) at the bottom right. So it is not the
 * same navy lightened - the blue channel lifts far more than the red -
 * and it carries its own gradient running the opposite way to the
 * section behind it. Both of those are why it separates.
 */
.site-footer__panel {
	max-width: var(--container-wide);
	margin-inline: auto;
	margin-bottom: var(--space-24);
	padding: var(--space-32) var(--space-40);
	/* Lighter than the section at EVERY point - that is the rule, not
	 * the exact shade. The first attempt at this failed because both
	 * gradients ran the same direction and crossed; the second
	 * over-corrected into near-black against bright blue.
	 *
	 * These sit close to what Figma actually measures: the section
	 * around rgb(26,53,96) and the panel around rgb(41,77,128), a step
	 * of roughly +15/+24/+32. Enough to read as a box on a ground,
	 * gentle enough that the panel still belongs to the same footer. */
	background: linear-gradient(135deg, #2d5590 0%, #294d80 60%, #244470 100%);
	/* The hairline is belt and braces on purpose. A gradient can always
	 * be re-tuned into invisibility by a later change; a 1px edge cannot,
	 * so the box-in-box survives whatever happens to the two fills. */
	border: 1px solid rgba(255, 255, 255, 0.16);
	color: var(--on-invert);
	border-radius: var(--radius-md);
}

/* !! :not(.cta-button) IS LOAD-BEARING - THE THIRD TIME IN THIS FILE.
 *
 * ".site-footer a" scores one class plus one element, which BEATS the
 * bare ".cta-button" rule in section 04. The footer's booking pill was
 * therefore being repainted white-on-amber.
 *
 * That is not a taste question. Measured on the rendered page: white on
 * #fbb03c is 1.85:1, against a 4.5:1 minimum. --accent-contrast (ink)
 * on the same amber is 9.99:1. The note on --accent-contrast in 1.2
 * says exactly this and says never to "fix" it to white; the same trap
 * already bit .mobile-menu a and the bare a:hover rule, and both carry
 * the same guard.
 *
 * ANY future rule that colours a bare "a" inside a dark region has to
 * exclude this class. */
.site-footer a:not(.cta-button) { color: var(--on-invert); text-decoration: none; }
/* The outlined Call pill is excluded from the hover underline: it
 * already answers by filling white with ink text, and an underline on
 * top of that reads as a link that happens to be in a box rather than
 * as a button. Underlines stay on the things that ARE links - the
 * footer menu, the directions link, the phone in the sentence. */
.site-footer a:not(.cta-button):not(.footer-cta-call):hover { text-decoration: underline; }

/* THREE COLUMNS, AND A SHORT ONE.
 *
 * The brand column takes the most room, hours the least - hours are
 * label/value pairs and read better narrow. Aligned to the TOP, not
 * stretched: the logo has to start at the panel's top edge, which was
 * the other half of why it looked lost. */
/* HOURS LEFT, BRAND CENTRED, MAP RIGHT.
 *
 * The brand was in column one, which put the practice's name in the
 * corner and left the panel reading left-to-right as three unrelated
 * blocks. Centred, the panel is symmetrical - text one side, map the
 * other, the lockup holding the middle - and the logo lands on the
 * centre line of the page.
 *
 * The ORDER IS CSS, NOT MARKUP. The brand stays first in the document,
 * because that is the sensible reading order and because it is what
 * should come first when these stack on a phone. `order` moves it
 * visually and only at this breakpoint.
 *
 * !! THE OUTER TRACKS MUST BE EQUAL OR THE LOCKUP IS NOT CENTRED.
 *
 * This was 1.05fr / 0.9fr / 1fr, which reads as balanced and is not:
 * the outer tracks differed by 20px, so the middle column - and the
 * logo in it - sat 10px right of the panel's centre line. Measured, not
 * guessed: panel centre 750, logo centre 760. Gerek could see it and
 * could not name it, which is exactly what a 10px offset does.
 *
 * 1fr / fixed / 1fr guarantees it: the outer two are identical at any
 * panel width, so the middle is always dead centre. The centre track is
 * a fixed 380 rather than a fraction because the thing in it is a
 * fixed-width image - a fraction just re-introduces a number somebody
 * has to balance by hand.
 *
 * align-items:center, not start. The hours are the shortest column and
 * were hanging off the top with a gap beneath them; against the map's
 * height they now sit level with the middle of the panel. */
.site-footer__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 380px) minmax(0, 1fr);
	align-items: center;
	gap: var(--space-40);
}
.site-footer__hours { order: 1; }
.site-footer__brand { order: 2; }
.site-footer__visit { order: 3; }

/* ---- brand column ---- */
/* CENTRED, ALL OF IT.
 *
 * The lockup is wide enough to fill this column, so left-aligned it
 * still LOOKED centred - while the phone number and the icon row under
 * it sat hard left. Three elements, two different alignments, and the
 * column read as broken rather than as a choice. Centring the whole
 * thing is the cheaper fix than shrinking the logo to prove it is
 * left-aligned. */
.site-footer__brand {
	display: grid;
	gap: var(--space-12);
	justify-items: center;
	text-align: center;
}

/* The cropped lockup - see the note in footer.php. Bigger than the old
 * 200 AND it starts at the top of the panel now, because the 22% of
 * transparent margin that used to push it down is gone from the file. */
.site-footer__logo { margin: 0; }
.site-footer__logo img {
	display: block;
	width: 260px;
	max-width: 100%;
	height: auto;
}

.site-footer address { font-style: normal; }
.footer-phone {
	font-size: var(--type-h4);
	font-weight: var(--weight-semibold);
	white-space: nowrap;
}

/* ---- social ---- */
/* Bigger, and with room around them. The phone number that used to sit
 * between the lockup and this row is gone - it was the third printing
 * of the same number inside one navy region - so the row takes that
 * space as breathing room rather than closing the gap. */
.footer-social {
	display: flex;
	gap: var(--space-16);
	margin: var(--space-8) 0 0;
	padding: 0;
	list-style: none;
}
/* 40 -> 56 -> 46. 40 read as an afterthought beside a 260px lockup and
 * was under the 44px minimum a thumb wants; 56 overshot and started
 * competing with the lockup for the column. 46 clears the touch target
 * with two pixels to spare and still sits under the mark above it. */
.footer-social a {
	display: grid;
	place-items: center;
	width: 46px;
	height: 46px;
	border-radius: var(--radius-pill);
	background: rgba(255, 255, 255, 0.10);
	color: var(--on-invert);
	transition: background-color .15s ease, color .15s ease;
}
/* The glyph is sized here rather than on the SVG tag, so the row
 * resizes from one number. */
.footer-social a svg { width: 22px; height: 22px; display: block; }
.footer-social a:hover {
	background: var(--accent);
	color: var(--accent-contrast);
	text-decoration: none;
}

/* ---- a column heading ---- */
.footer-heading {
	margin: 0 0 var(--space-16);
	font-family: var(--font-heading);
	font-size: var(--type-detail);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-eyebrow);
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.72);
}

/* ---- office hours ----
 * A description list, so the days and their times stay associated for
 * a screen reader rather than being four unrelated runs of text.
 *
 * TWO COLUMNS, NOT EIGHT ROWS. Day and hours stacked made this the
 * tallest thing in the panel for four short facts. Side by side it is
 * four rows, which is half the height and reads like the opening hours
 * on a door. */
.site-footer__hours dl {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: var(--space-8) var(--space-16);
	margin: 0;
	font-size: var(--type-detail);
}
.site-footer__hours dt {
	font-weight: var(--weight-semibold);
	white-space: nowrap;
}
.site-footer__hours dd {
	margin: 0;
	color: rgba(255, 255, 255, 0.78);
}
/* !! A TIME RANGE NEVER BREAKS ACROSS LINES.
 *
 * "7:00am - 11:00am, 2:00pm - 6:00pm" wrapped wherever it ran out of
 * room, which left "6:00pm" stranded alone on line two - a half-range
 * that reads as a typo. Each range is its own nowrap span, so the only
 * place the line CAN break is at the comma between them. */
.site-footer__hours dd .hours-range { white-space: nowrap; }

/* ---- visit us: map, address, route - one block ---- */
/* No heading on this column. "VISIT US" over a map that already says
 * so was a label for its own sake, and losing it lifts the whole column
 * up to sit level with the logo and the hours. */
.site-footer__visit {
	display: grid;
	gap: var(--space-8);
	justify-items: start;
	/* Capped and pushed to the far edge. Left to fill its column the map
	 * became the widest thing in the panel, and Google answers a wider
	 * frame by zooming OUT - so the practice's own pin got smaller as
	 * the card got bigger. A card, not a panel. */
	width: min(330px, 100%);
	justify-self: end;
}
.map-embed {
	width: 100%;
	aspect-ratio: 3 / 2;
	border-radius: var(--radius);
	overflow: hidden;
	background: rgba(255, 255, 255, 0.08);
}
/* The iframe carries width/height attributes for its own aspect; these
 * override them so it fills whatever the card is. */
.map-embed iframe { display: block; width: 100%; height: 100%; border: 0; }
.site-footer__visit address { font-size: var(--type-detail); }
.site-footer__directions {
	font-size: var(--type-detail);
	font-weight: var(--weight-semibold);
}

/* ---- the base row ----
 * Links in ONE horizontal run, legal on the other side. They used to be
 * a 2x2 block of four legal pages sitting in the middle of the panel,
 * which gave four minor links the visual weight of a nav. */
.footer-nav ul {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-12) var(--space-24);
	margin: 0;
	padding: 0;
	list-style: none;
}
/* AMBER ON HOVER, like the header's tier-1 nav. Same reasoning: these
 * sit on the dark panel, amber is the accent the region already
 * carries, and white-underlining-white is a state change you have to
 * hunt for. The underline stays and takes the colour with it, since it
 * inherits currentColor. */
.footer-nav a {
	font-size: var(--type-detail);
	transition: color .15s ease;
}
.site-footer .footer-nav a:hover,
.site-footer .footer-nav a:focus-visible {
	color: var(--accent);
	text-decoration: underline;
}
@media (prefers-reduced-motion: reduce) {
	.footer-nav a { transition: none; }
}

/* ---- base ----
 * Links on one side, legal on the other, both on one row at desktop.
 * This is the "pancake" half of the brief: the panel should be short
 * and wide, and stacking four legal links above two legal lines was
 * three rows doing the work of one. */
.site-footer__base {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16) var(--space-40);
	margin-top: var(--space-24);
	padding-top: var(--space-20);
	border-top: 1px solid var(--rule-invert);
	font-size: var(--type-xs);
}
.site-footer__base p { margin: 0; }
.site-footer__legal {
	display: grid;
	gap: 2px;
	text-align: right;
	color: rgba(255, 255, 255, 0.66);
}

/* THE LICENCE BLOCK.
 *
 * Kept visually distinct so nobody edits it casually, and kept in its
 * OWN paragraph so the licence number is never on the same line as the
 * business name.
 *
 * The licence belongs to Dr. Michael VanDerschelden, DC personally.
 * Huntington Beach Chiropractic does not hold one and must never appear
 * beside the number. See the note in footer.php. */
.footer-licence {
	margin: 0;
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: var(--space-8);
	color: var(--on-invert);
}
.footer-licence strong { font-weight: var(--weight-semibold); }
/* The name and the number sit side by side, separated by a rule rather
 * than by punctuation - close enough to read as one credential, far
 * enough that nobody mistakes them for a sentence containing the
 * business name. */
.footer-licence span { border-left: 1px solid var(--rule-invert); padding-left: var(--space-8); }

/* !! THE BUILD CREDIT MUST NOT LOOK LIKE AN ADVERT. Gerek's brief was
 * "same font size, same everything else - make it look blended in", so
 * it inherits colour from the copyright line rather than taking a link
 * colour that would make the one commercial word on the page the
 * brightest thing in the footer.
 *
 * `color: inherit` rather than naming --on-invert, so it keeps matching
 * if that line is ever recoloured. It is still a link: it underlines on
 * hover and on keyboard focus, which is the whole of what tells somebody
 * it can be clicked. */
.footer-credit__link {
	color: inherit;
	text-decoration: none;
}

.footer-credit__link:hover,
.footer-credit__link:focus-visible {
	text-decoration: underline;
}


/* !! ON TESTING THIS PAGE BELOW DESKTOP.
 *
 * Headless screenshots of this page at phone width are NOT reliable.
 * They come back with content clipped past the right edge - at both
 * tall and normal window heights - while a real browser at the same
 * width measures scrollWidth exactly equal to clientWidth and renders
 * correctly. Chasing that phantom overflow cost real time, and two
 * genuine faults (the missing cutout, the broken step diagram) were
 * only found afterwards by scrolling an actual viewport.
 *
 * Measure and screenshot in a real browser for tablet and phone. Use
 * headless for desktop stills, where it agrees.
 * ================================================================== */

/* ==================================================================
 * 07  RESPONSIVE
 *
 * !! THERE IS NO DESIGN BELOW 1440px. The Figma file is a single
 * desktop frame and none is coming. Everything in this section is a
 * judgement call, recorded here so it can be argued with rather than
 * discovered later.
 *
 * WHAT WAS DECIDED, AND WHY
 *
 * 1200+  DESKTOP - the design's layout.
 *        logo | nav | phone | CTA, all on one row.
 *
 * 992-1199  TABLET LANDSCAPE - nav collapses, everything else stays.
 *        The six Tier-1 items need roughly 525px of the row once the
 *        32px gaps are counted. Below 1200 the container starts
 *        shrinking and the row cannot hold the logo, the nav, a phone
 *        number and a 28-character button at once. The NAV is what
 *        gives way - it is the only element with a complete
 *        replacement one tap away. The phone and the CTA stay in the
 *        bar, because a visitor on a tablet is as likely to call as
 *        one on a desktop, and burying the number behind a hamburger
 *        would break the click-to-call requirement.
 *
 * 768-991  TABLET PORTRAIT - same single row, tighter.
 *        Still fits: the CTA loses padding rather than words.
 *
 * <768  PHONE - the header wraps to two rows.
 *        Row 1: logo + hamburger.  Row 2: phone + CTA, full width.
 *        Everything stays present. The full "Book New Patient
 *        Appointment" wording is never shortened and the number is
 *        never reduced to an icon - both were explicit requirements,
 *        and both are the reason for the second row rather than a
 *        squeeze.
 *
 * <600  SMALL PHONE - only the boxes tighten, never the strings.
 *
 * THE FOOTER follows the same principle: the design's two-column
 * arrangement holds while it fits, then stacks. The link block keeps
 * its two columns down to 600 because four short items in two columns
 * read better than a four-item ladder; below that it becomes one.
 * ================================================================== */

/* Hamburger takeover.
 *
 * MEASURED, not guessed. At 1200px the header's content box is 1248 -
 * the design's own width - and the row needs roughly 1140 of it: a
 * 190px logo, ~525px of nav, ~120px of phone, ~215px of CTA, and three
 * 24px gaps. Below 1200 the container shrinks with the window and the
 * slack is gone.
 *
 * !! RE-MEASURE THIS IF THE MENU CHANGES. A seventh Tier-1 item, or a
 * longer label than "New Patients", pushes the takeover point up. */
@media (max-width: 1199px) {
	.primary-nav { display: none; }
	.nav-toggle  { display: inline-flex; }
}

@media (max-width: 991px) {
	.site-header__inner { gap: var(--space-16); }
	.site-header__actions { gap: var(--space-16); }
	.site-header__logo img { width: 114px; }
}

/* Two-row header.
 *
 * SCOPED WITH A CHILD COMBINATOR - deliberately, and this matters.
 * .site-header__logo is reused inside .mobile-menu__top for the
 * logo. Unscoped, "order: 1" applied there too, and because the
 * Close button keeps the default order of 0 it jumped ahead of the
 * logo - the panel header rendered Close-then-logo, mirrored from
 * its own markup. That bug shipped once and was caught on a real
 * phone-width render. Do not remove the "> ". */
@media (max-width: 767px) {
	.site-header__inner {
		flex-wrap: wrap;
		row-gap: var(--space-12);
		column-gap: var(--space-12);
	}
	.site-header__inner > .site-header__logo { order: 1; }
	.site-header__inner > .nav-toggle        { order: 2; }
	/* PHONE AND CTA SHARE THE SECOND ROW, HALF EACH.
	 *
	 * They were space-between, which let the CTA size to its label and
	 * left the row looking accidental, and below 600 the CTA took a
	 * whole third row as a full-width slab. Two equal halves reads as
	 * deliberate and gives the logo the top row to itself. */
	.site-header__inner > .site-header__actions {
		order: 3;
		flex-basis: 100%;
		gap: var(--space-12);
	}
	.site-header__actions > * { flex: 1 1 0; }
	.site-header__actions .header-phone { justify-content: center; }
	.site-header__actions .cta-button { text-align: center; }

	/* The logo gets the room the hidden "Menu" label gave back. 170 ->
	 * 195: the top row carries only the mark and a 46px button, so there
	 * is space, and at 170 it still read small against a two-row header
	 * that is 147px tall. 195 -> 215 on the same reasoning. */
	.site-header__logo img { width: 215px; }

	.site-footer__panel { padding: var(--space-32) var(--space-24); }
	.site-footer__grid  { gap: var(--space-32); }
}

@media (max-width: 599px) {
	/* Both strings stay in full. Only the boxes around them tighten.
	 *
	 * The CTA takes its own row here rather than shrinking further. At
	 * a 22px body the header's two items no longer fit side by side on
	 * a 390px phone without cutting the button's label down to
	 * something no thumb wants to aim at - and "Book New Patient
	 * Appointment" is the whole point of the header. Full width reads
	 * as deliberate; 13px type squeezed against the edge does not. */
	/* Still two halves at 390 - the CTA no longer takes its own row.
	 * "Book Appointment" is short enough to fit one now that the header
	 * label is gone and the phone is beside it rather than above it. */
	/* 150 -> 175 -> 200. Measured at 390: the mark and the 46px toggle
	 * come to 246 of 342 available, so the row still is not tight. This
	 * is close to the practical ceiling - past about 210 the toggle
	 * starts crowding the mark's right edge. */
	.site-header__logo img { width: 200px; }
	.header-phone { font-size: var(--type-xs); }
	.site-header__actions .cta-button {
		padding: var(--space-12) var(--space-8);
		font-size: var(--type-xs);
	}

	.footer-nav ul { grid-template-columns: 1fr; }
}


/* ==================================================================
 * 08  ARTICLE CARD + GRID  (component)
 *
 * !! THE ONLY COMPONENT ON THIS SITE THAT IS NOT PER-PAGE. Every other
 * layout here belongs to the page it was drawn for - that is what the
 * .ca-*, .mh-*, .ct-*, .wte-*, .ab-*, .rv-* and .ar-* prefixes are for.
 * Articles are different: one component in three places a reader moves
 * between - /articles/, every category archive, and the [article_feed]
 * shortcode - so it is adjusted once, here, and all three follow.
 *
 * !! REWRITTEN 2026-08-27, FROM A STACKED LIST CARD TO A GRID TILE.
 *
 * This section used to describe a horizontal preview - a 180px thumbnail
 * beside the text, cards separated by a bottom rule - with a stretched
 * ::after overlay around the title only. It was replaced, not amended,
 * when /articles/ became the posts page and gained a real index.
 *
 * NOTHING A READER HAS SEEN CHANGED. The markup that produced the old
 * card came from the [article_feed] shortcode, and that shortcode is on
 * ZERO pages and ZERO posts - checked across all 19 pages, 2026-08-27,
 * before this was touched. It was dead code.
 *
 * !! THE WHOLE TILE IS ONE ANCHOR, so there is no ::after overlay and no
 * :has() focus rule any more. A screen reader announces one link per
 * card instead of three, hover and focus move the whole object, and the
 * accessible name is just the title. See inc/article-card.php.
 *
 * WHITE WITH A HAIRLINE, NOT CREAM, AND THAT IS A DELIBERATE ECHO. The
 * Articles frame draws an empty-state box (577:160) as a white box with
 * a 1px --rule border on a 16px radius, sitting in the exact place these
 * cards appear. A reader watching the page fill up sees the same slot
 * gain contents. The six CATEGORY cards above it are --surface-alt with
 * a shadow, so the two card types on that white ground never blur.
 * ================================================================== */

/* THREE ACROSS, which is also HBCHIROPRACTIC_ARTICLES_PER_CATEGORY - so a
 * category feed always fills exactly one row and can never put a row
 * with a hole in it on the page. --4 exists for a wider full-width feed
 * and is not used today; it is one declaration and it keeps the grid
 * from being re-derived the first time somebody wants four. */
.article-grid {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--space-24);
	margin: var(--space-32) 0 0;
	padding: 0;
	list-style: none;
}
.article-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

/* The <li> is only a grid cell. Everything painted is on the anchor, so
 * the whole painted surface is the click target rather than a box with a
 * link inside it. */
.article-card { display: grid; }

.article-card__link {
	display: flex;
	flex-direction: column;
	height: 100%;
	overflow: hidden;
	background: var(--surface);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	color: var(--ink);
	text-decoration: none;
	/* Not verifiable in this environment - the pane's tab is backgrounded
	 * so the compositor never ticks and getComputedStyle returns t=0
	 * forever. Both the resting and the hover state are real declarations
	 * rather than animation, so the transition only softens a change that
	 * is correct with or without it. */
	transition: border-color 150ms ease, box-shadow 150ms ease;
}
/* !! THE HOVER IS A BORDER AND A LIFT, NOT A COLOUR CHANGE ON THE TEXT.
 * Section 02's `a:not(.cta-button):hover { color: var(--navy) }` already
 * reaches this anchor and would repaint every word in the tile - the
 * date and the excerpt included, which are deliberately --ink-subtle and
 * --ink-muted. Naming the colour back at the card keeps the hierarchy
 * inside the card and lets the title alone carry the state. */
.article-card__link:hover {
	color: var(--ink);
	border-color: var(--navy);
	box-shadow: var(--shadow-card);
}
.article-card__link:hover .article-card__title { color: var(--navy); }
.article-card__link:focus-visible {
	outline: 3px solid var(--navy);
	outline-offset: 4px;
}

/* A post with no featured image renders with NO media block at all - the
 * body simply takes the whole tile. That matters on this site rather
 * than being defensive: the first articles arrive from the AEO Article
 * Creation Tool and may land before anyone has chosen a picture. */
.article-card__media {
	display: block;
	overflow: hidden;
	background: var(--surface-alt);
}
.article-card__img {
	display: block;
	width: 100%;
	/* A fixed ratio keeps a row of cards aligned regardless of what shape
	 * the uploaded image happens to be. A component dimension, not
	 * layout spacing. */
	aspect-ratio: 16 / 9;
	object-fit: cover;
}

/* flex rather than grid, and `margin-top: auto` on the last child, so
 * "Read more" sits on the tile's bottom edge in every card in a row no
 * matter how long its excerpt is. Same problem the review cards solved
 * with grid-template-rows in 16.3; here the body is the flexible box
 * rather than the card, because the media above it is a fixed ratio. */
.article-card__body {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: var(--space-8);
	padding: var(--space-24);
}
.article-card__title {
	margin: 0;
	font-family: var(--font-heading);
	font-size: var(--type-h5);
	font-weight: var(--weight-semibold);
	transition: color 150ms ease;
}
.article-card__meta {
	font-size: var(--type-xs);
	color: var(--ink-subtle);
}
.article-card__excerpt {
	font-size: var(--type-small);
	color: var(--ink-muted);
}
.article-card__more {
	margin-top: auto;
	padding-top: var(--space-8);
	font-size: var(--type-detail);
	font-weight: var(--weight-semibold);
	color: var(--navy);
}

/* The shortcode's wrapper. It keeps its own class so a caller can target
 * "a feed placed in page copy" separately from the ones the templates
 * build - the one thing the shortcode can do that home.php cannot. The
 * cards inside it are the same cards. */
.article-feed { display: block; }

/* ---- 08.1  PAGINATION ----
 *
 * the_posts_pagination() on /articles/ and on every archive. It is the
 * ONE paginator on either page - see inc/articles.php for why several
 * feeds on one page cannot each have their own.
 *
 * WordPress emits the current page as <span class="page-numbers current">
 * and the rest as anchors, so the two are styled together and the
 * current one is inverted. `.nav-links` is core's wrapper and is the
 * only thing that can be made the flex row. */
.pagination { margin-top: var(--space-48); }
.pagination .nav-links {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8);
	align-items: center;
}
.pagination .page-numbers {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-width: var(--space-48);
	min-height: var(--space-48);
	padding: 0 var(--space-12);
	background: var(--surface);
	border: 1px solid var(--rule);
	border-radius: var(--radius-nested);
	color: var(--ink);
	font-size: var(--type-small);
	text-decoration: none;
}
/* 48px minimums above are a touch target, not a look: a paginator is the
 * one control on these pages a reader uses repeatedly on a phone. */
.pagination a.page-numbers:hover {
	color: var(--ink);
	border-color: var(--navy);
}
.pagination .page-numbers.current {
	background: var(--navy);
	border-color: var(--navy);
	color: var(--on-invert);
	font-weight: var(--weight-semibold);
}
/* The ellipsis core prints between distant page numbers is not a
 * control and should not wear a box. */
.pagination .page-numbers.dots {
	min-width: 0;
	padding: 0 var(--space-8);
	background: none;
	border: 0;
	color: var(--ink-muted);
}

@media (max-width: 991px) {
	.article-grid,
	.article-grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 599px) {
	.article-grid,
	.article-grid--4 { grid-template-columns: 1fr; }
	/* The tile keeps its own left edge. It is a heading, a date and a
	 * running sentence in a box of its own - the same call 10b makes for
	 * .ca-workup .hp-card and 16b makes for .rv-card. */
	.article-card { text-align: left; }
}


/* ==================================================================
 * 09  HOMEPAGE SECTIONS
 *
 * VISUAL SOURCE: Figma 513:2393. Every colour, size and space below
 * comes from a token in section 01 - there is not a raw value in here.
 *
 * !! THE SECTION CLASSES ARE THE HOMEPAGE'S. THE COMPONENT CLASSES ARE
 * NOT, ANY MORE.
 *
 * This block used to say the .hp-* classes "only the homepage emits, so
 * nothing here can reach the other eighteen pages". That was true while
 * the homepage was the only built page and it is now half wrong, which
 * is worse than being wrong - it invites the next page to duplicate a
 * component rather than reuse it.
 *
 * As built:
 *   .hp-hero .hp-intro .hp-stats .hp-photostrip .hp-problem
 *   .hp-different .hp-system .hp-bars .hp-proof .hp-not-for-everyone
 *     SECTIONS. Homepage only. They carry its rhythm and its grounds.
 *     Do not reuse one on another page - give that page its own.
 *
 *   .hp-media / .hp-frame   .hp-eyebrow and its pill variants
 *   .hp-cards / .hp-card    .hp-stats__row / .hp-stat   .hp-btn-line
 *     COMPONENTS. Shared, and already used by section 10. A page that
 *     needs one to look different states the difference ITSELF -
 *     `.ca-workup .hp-card { ... }` - rather than adding a modifier
 *     here. The component keeps one definition.
 *
 * !! THE COPY IS NOT STYLED INTO EXISTENCE. Headings and paragraphs
 * are the loaded 3.6 blocks; this section only arranges them.
 *
 * NO DESIGN EXISTS BELOW 1440px. The breakpoint behaviour at the end
 * of this section is the theme's own - see the note there.
 * ================================================================== */

/* ---- 9.0  image slots ----
 *
 * Every photograph on this page sits in a slot whose SIZE comes from
 * the design. A slot is in one of two states:
 *
 *   .hp-media   FILLED  - wraps a real <img>
 *   .hp-frame   EMPTY   - a dashed box naming what belongs there
 *
 * !! THE SIZE MODIFIERS BELONG TO BOTH. Every --hero-bg, --portrait,
 * --video, --icon and --cluster rule in this section is written against
 * `.hp-frame--x, .hp-media--x` so the two states occupy identical
 * space. Filling a slot is then a markup change and never a CSS one,
 * and an empty slot cannot quietly collapse the layout around it.
 *
 * Find the ones still empty with: grep "EMPTY FRAME"
 */
.hp-frame, .hp-media {
	border-radius: var(--radius-md);
	overflow: hidden;
}
.hp-frame {
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--surface-alt);
	border: 1px dashed var(--rule);
	color: var(--ink-subtle);
}
.hp-frame__label {
	font-size: var(--type-eyebrow);
	letter-spacing: var(--tracking-eyebrow);
	text-transform: uppercase;
	padding: var(--space-8);
	text-align: center;
}

/* The filled state. The wrapper owns the slot's dimensions and the
 * <img> is told to fill it outright - width AND height at 100% with
 * object-fit. `height: auto` here would let the image's own intrinsic
 * size decide, which is how a 1265px-wide photograph ends up bursting
 * a 508px column. */
/* !! MARGIN ZERO IS LOAD-BEARING. Every filled slot is a <figure>,
 * and the user-agent stylesheet gives that a 1em inline margin - which
 * would inset each photograph from its own column and push the hero
 * image out of alignment with the copy beside it. */
.hp-media { display: block; margin: 0; }
/* !! THE <picture> BRANCH IS NOT OPTIONAL ONCE A SLOT ART-DIRECTS.
 *
 * A <picture> puts an element between the figure and the <img>, so a
 * `.hp-media > img` selector silently stops matching and the photograph
 * reverts to its intrinsic size inside the slot - which is how a
 * 1265px-wide file bursts a 508px column, the exact failure the rule
 * below exists to prevent. Both paths, one rule. */
.hp-media > picture { display: block; }
.hp-media > img,
.hp-media > picture > img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}
/* Cut-outs and line art are drawn to fit, not cropped to fill. */
.hp-media--contain > img { object-fit: contain; }

/* ---- 9.1  shared section furniture ----
 *
 * !! display:contents ON THE INNER CONTAINER IS LOAD-BEARING.
 *
 * A wp:group block does not render as one element. WordPress wraps its
 * children in a .wp-block-group__inner-container, so .hp-hero had
 * exactly ONE child and every grid-template-columns in this section was
 * laying out a single item - the hero's photograph stacked above the
 * copy instead of sitting beside it. Caught on the real page.
 *
 * display:contents dissolves that wrapper for layout purposes, so the
 * real children become the grid items these rules expect. Scoped to
 * .hp-* so no other page is affected. */
[class*="hp-"] > .wp-block-group__inner-container {
	display: contents;
}
.hp-intro, .hp-stats, .hp-problem, .hp-different,
.hp-system, .hp-bars, .hp-proof, .hp-not-for-everyone,
.hp-photostrip {
	padding-inline: var(--container-pad);
}
/* .hp-hero is absent above too: the panel sets its own width and
 * margin, because inline padding would sit INSIDE it and push the
 * photograph away from its own edges. */

/* !! GRID ITEMS MUST BE ALLOWED TO SHRINK.
 *
 * min-width:auto is the default on a grid item, which means any fixed
 * width inside one becomes a floor the whole column cannot go below.
 * The homepage's grids all hold photographs at their design widths, so
 * without this every one of them is a potential horizontal scrollbar at
 * phone size. Set once here rather than hunted down per section.
 *
 * Targets the grandchildren: 9.1 gives WordPress's inner-container
 * display:contents, so the real grid items are one level further in. */
.hp-hero > .wp-block-group__inner-container > *,
.hp-proof > .wp-block-group__inner-container > *,
.hp-hero > *, .hp-proof > * {
	min-width: 0;
}

/* EVERY SECTION NOW CENTRES ITSELF.
 *
 * front-page.php hands the content straight to <main> with no container
 * around it, because a container is what stopped the full-width
 * sections being full width. The consequence is that a section which
 * sets a max-width must also centre itself - inside the old wrapper
 * that was free, and out here it is not. Anything missing this would
 * silently pin itself to the left edge of a 1600px screen. */
.hp-hero, .hp-intro, .hp-problem, .hp-different, .hp-system,
.hp-bars, .hp-not-for-everyone, .hp-photostrip {
	margin-inline: auto;
}
/* The sections that are SUPPOSED to reach both edges - the navy band,
 * the review ground and the final CTA - set no max-width at all, so
 * they fill whatever <main> gives them. That is the whole point. */

/* ONE RHYTHM, NOT SIX. Every section gets the same 100px above and
 * below. This used to be four different values applied to four
 * different subsets, which is why the page read as a stack of unrelated
 * blocks rather than a document. The stats bar is the single exception
 * below - it is a thin band, not a section. */
/* .hp-hero is absent: it is a panel that sits close under the header
 * and carries its own, much smaller, top pad. Everything else takes the
 * standing 100. */
.hp-intro, .hp-problem, .hp-different, .hp-system,
.hp-bars, .hp-proof, .hp-not-for-everyone, .hp-photostrip {
	padding-block: var(--section-pad);
}

/* !! TWO SECTIONS ON THE SAME GROUND SHARE ONE GAP.
 *
 * 100 above and 100 below is the rule, and at a colour change it is
 * exactly right - the eye reads the edge, not the distance. Where two
 * sections on the SAME white ground meet, the two pads stack into 200px
 * of nothing and the page looks like it has come apart.
 *
 * Halving both sides of that one boundary gives the standing 100
 * between them without special-casing either section's own rhythm. The
 * navy band above the photo strip is untouched: it has an edge of its
 * own and needs the full pad. */
.hp-photostrip { padding-bottom: calc(var(--section-pad) / 2); }
.hp-problem    { padding-top:    calc(var(--section-pad) / 2); }

/* THE PROBLEM and HOW WE DO IT DIFFERENTLY are the same argument in two
 * halves - the same four steps, done two ways - and they sit on the
 * same white. Same treatment, for the same reason.
 *
 * .hp-different keeps its FULL bottom pad: that boundary is a real
 * colour change into the beige of OUR SYSTEM, and the bordered panel
 * needs the whole 100 under it. */
.hp-problem   { padding-bottom: calc(var(--section-pad) / 2); }
.hp-different { padding-top:    calc(var(--section-pad) / 2); }

/* A section heading is not a paragraph heading: it introduces several
 * blocks, not the next sentence, and the global 16px margin made the
 * first paragraph look like a subtitle attached to it. */
.hp-intro h2, .hp-problem h2, .hp-system h2,
.hp-not-for-everyone h2, .hp-photostrip h2 {
	margin-bottom: var(--space-32);
}

/* RUNNING COPY IS CAPPED, THE CONTAINER IS NOT. See --measure in 1.13.
 * Scoped to p.wp-block-paragraph on purpose: that is the class
 * WordPress puts on a plain paragraph, so the CTA rows, button lines
 * and eyebrows - which carry their own classes and are not running
 * copy - keep the full width they need. */
.hp-intro p.wp-block-paragraph,
.hp-problem p.wp-block-paragraph,
.hp-system p.wp-block-paragraph,
.hp-not-for-everyone p.wp-block-paragraph {
	max-width: var(--measure);
	margin-inline: auto;
}

/* The eyebrow, in its three forms: plain (hero), an outlined pill on
 * white, and an outlined pill on a dark ground. */
/* The pill and the heading it introduces are a pair, but they are not
 * the same line - 16px read as a stacked label rather than an
 * introduction. 32 gives the H2 room to land. */
.hp-eyebrow {
	margin: 0 0 var(--space-32);
	/* fall through to the sizes below */
	font-size: var(--type-detail);
	font-weight: var(--weight-medium);
	letter-spacing: var(--tracking-eyebrow);
	color: var(--ink-muted);
}
/* The default pill is RED - this is the one THE PROBLEM uses, and the
 * design draws it in --alert (#ff1a1a) for both the outline and the
 * text. The other two eyebrows override the colour below. */
/* Bigger and heavier than the design draws it. At 13px Regular inside a
 * 1px hairline it was a label; these pills are the only thing marking
 * where one argument ends and the next begins, and on a page this wide
 * they have to carry that from across the room. 2px of border, a
 * semibold weight and wider tracking do it without adding a colour. */
.hp-eyebrow--pill {
	display: inline-block;
	align-self: center;
	padding: var(--space-12) var(--space-24);
	font-size: var(--type-small);
	font-weight: var(--weight-semibold);
	letter-spacing: 0.08em;
	text-transform: uppercase;
	border: 2px solid var(--alert);
	border-radius: var(--radius-pill);
	color: var(--alert);
}
.hp-eyebrow--ink    { border-color: var(--ink);       color: var(--ink); }
.hp-eyebrow--invert { border-color: var(--on-invert); color: var(--on-invert); }

/* ---- 9.2  HERO ----
 * Copy column left, photograph right. The design's column is 584px
 * inside a 1140 container; the frame takes what is left.
 */
/* ONE PANEL WITH THE PHOTOGRAPH BEHIND EVERYTHING.
 *
 * The photograph is the GROUND for the whole row, not a column beside
 * the copy. It was built as a two-column split, which is why the shot
 * was cropped to a slot too narrow to hold its own subject and why the
 * hero read as a text block with a picture next to it rather than as
 * one image.
 *
 * The composition only works because the photograph is already lit for
 * it: the room's white wall fills the left half and Dr. Michael and the
 * patient sit in the right. The copy goes on the wall. The wash in
 * .hp-hero__bg::after is insurance for the crops where less wall is
 * showing, not the main event.
 *
 * SITS CLOSE UNDER THE HEADER on purpose - this section takes a much
 * smaller top pad than the 100px every other section gets, because the
 * panel's own top edge is the separation. */
.hp-hero {
	position: relative;
	isolation: isolate;
	display: grid;
	align-items: center;
	width: calc(100% - (var(--container-pad) * 2));
	max-width: var(--container);
	min-height: 620px;
	/* 40 above, not 100: the gap to the header. The panel's own edge is
	 * the separation, so the full rhythm would leave it stranded. */
	margin: var(--space-40) auto 0;
	padding-block: var(--space-48);
	border-radius: var(--radius-md);
	overflow: hidden;
}

.hp-hero__col {
	position: relative;
	z-index: 1;
	/* WIDTH AND INDENT MOVE WITH --type-h1-hero, NOT INDEPENDENTLY.
	 *
	 * 740 wide with a 72px indent gives the headline about 620px to set
	 * in, which is four lines at 52px. Narrow it without dropping the
	 * headline size and it goes to five, the panel grows, and the
	 * photograph behind it has to scale up to keep covering - the same
	 * loop that produced a close-up of a shoulder at 660/42.
	 *
	 * The indent is ASYMMETRIC on purpose: 72 on the left so the copy
	 * sits in from the panel edge rather than against it, 48 on the
	 * right because that edge is a soft gradient into the photograph and
	 * needs no matching margin. The block ends around 53% of the panel,
	 * which is still inside the navy wash. */
	width: min(740px, 100%);
	justify-self: start;
	padding-left: var(--space-72);
	padding-right: var(--space-48);
}
/* !! :not(.hp-cta-row) IS LOAD-BEARING.
 *
 * WordPress leaves wp-block-paragraph on a paragraph that also carries
 * its own class, so the CTA row matches p.wp-block-paragraph too. With-
 * out the exclusion the button row inherited the lede's 26px and both
 * pills grew. Same trap as the --measure rule in 9.1. */
.hp-hero__col > p.wp-block-paragraph:not(.hp-cta-row) {
	font-size: var(--type-hero-lede);
}
.hp-hero h1 { font-size: var(--type-h1-hero); }

/* Everything in the copy column inverts onto the navy wash. The amber
 * pill is untouched - ink on amber stays ink, per --accent-contrast. */
.hp-hero__col,
.hp-hero__col h1,
.hp-hero__col p { color: var(--on-invert); }
.hp-hero__col .hp-eyebrow { color: rgba(255, 255, 255, 0.78); }
.hp-hero .hp-cta-row { color: rgba(255, 255, 255, 0.55); }
.hp-hero .hp-cta-row a:last-of-type { color: var(--on-invert); }
.hp-hero .hp-cta-row a:last-of-type:hover {
	background: var(--on-invert);
	color: var(--navy-deep);
}

/* The panel lifts off the page rather than sitting flush on it - the
 * other thing that was missing above the fold. */
.hp-hero { box-shadow: var(--shadow-photo); }

/* The hero's two buttons sit SIDE BY SIDE, as the design draws them.
 * .hp-cta-row is already flex; the pair simply measured a few pixels
 * wider than the copy column and wrapped, costing the panel ~70px of
 * height - and the panel's height is what decides how hard the
 * photograph behind it gets scaled. Chrome sizing, so the 22px body
 * rule is untouched.
 *
 * !! MATCH :first-of-type / :last-of-type EXPLICITLY.
 *
 * The base rules in 9.11 are .hp-cta-row a:first-of-type, which scores
 * (0,2,1) - the same as a plain .hp-hero .hp-cta-row a, and they sit
 * later in the file, so the obvious override silently loses the tie.
 * Third time this file has caught me on specificity today; naming the
 * pseudo-class is what actually wins it. */
.hp-hero .hp-cta-row a:first-of-type,
.hp-hero .hp-cta-row a:last-of-type {
	font-size: var(--type-detail);
	padding: var(--space-12) var(--space-20);
}
.hp-hero__col > *:last-child { margin-bottom: 0; }

.hp-hero__bg {
	position: absolute;
	inset: 0;
	z-index: 0;
	display: flex;
}
/* NAVY, NOT WHITE.
 *
 * The first version washed the left of the photograph out to near-white
 * and set dark type on it. It was legible and completely flat: a white
 * header over a white wash over a white page, with a photograph of a
 * white room in the middle. Nothing above the fold had any weight.
 *
 * The wash is now the brand navy, the type on it is white, and the
 * amber CTA is the only warm thing in the frame - which is what makes
 * it read as the thing to press. It also ties the hero to the header
 * and the stats band, so the top of the page is one object rather than
 * three unrelated pale ones.
 *
 * The stops are deliberately uneven: solid to about a third, then a
 * fast fall to nothing by two-thirds. A linear fade would grey the
 * subject on the right, which is the half that has to stay a
 * photograph. */
.hp-hero__bg::after {
	content: "";
	position: absolute;
	inset: 0;
	background: linear-gradient(
		100deg,
		rgba(16, 44, 70, 0.95) 0%,
		rgba(16, 44, 70, 0.88) 32%,
		rgba(16, 44, 70, 0.55) 52%,
		rgba(16, 44, 70, 0.10) 68%,
		rgba(16, 44, 70, 0) 80%
	);
}
/* Fills the panel outright. The photograph is 1265x652 against a panel
 * about 1400x620, so it is scaled up slightly and cropped top and
 * bottom - which is where there is nothing but wall and floor. */
.hp-frame--hero-bg, .hp-media--hero-bg { flex: 1; }
.hp-media--hero-bg, .hp-media--hero-bg > img { height: 100%; }
/* min(), NOT a fixed 406 with max-width:100%.
 *
 * A grid item defaults to min-width:auto, so a 406px child sets the
 * column's minimum to 406 and max-width:100% then resolves against a
 * column the child itself is inflating - the two agree on 406 and the
 * hero refuses to go narrower. On a 390px phone that pushed the whole
 * document to 528 wide and gave the site a horizontal scrollbar; the
 * mobile menu looked like the culprit only because a fixed element
 * sizes off the overflowed page. min() takes the smaller outright and
 * has no circularity to resolve. */
.hp-frame--hero-video, .hp-media--hero-video {
	width: min(406px, 100%);
	max-width: 100%;
	aspect-ratio: 406 / 228;
	margin-block: var(--space-24);
}
/* THE CTA LINES.
 *
 * The document writes each CTA row as ONE line - "Book New Patient
 * Appointment →   |   Call (714) 794-2171" - so the page has one
 * paragraph carrying two links and a literal pipe between them. The
 * first link is painted as the amber pill and the second as the outlined
 * pill.
 *
 * The pipe between them is DOCUMENT NOTATION, not copy - it means "these
 * are two separate CTAs" the way the arrow meant "this is a link" - and
 * the builder strips it on the same rule. See cta_row(). */
.hp-cta-row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-16);
	align-items: center;
	color: var(--ink-subtle);
}
/* The in-page primary CTAs are the same component as .cta-button, at a
 * larger size. Same three layers - see the note on .cta-button in 04 -
 * so the two never drift apart visually. */
.hp-cta-row a:first-of-type {
	position: relative;
	isolation: isolate;
	display: inline-block;
	padding: var(--space-12) var(--space-24);
	background:
		var(--accent-fill) padding-box,
		var(--accent-rim) border-box;
	/* 2px, not 1: at a hairline the multi-stop ring averages out to a
	 * pale outline and the metal is lost. */
	border: 2px solid transparent;
	color: var(--accent-contrast);
	font-size: var(--type-small);
	font-weight: var(--weight-semibold);
	text-decoration: none;
	border-radius: var(--radius-pill);
	box-shadow: var(--accent-lift);
	transition: box-shadow .18s ease, transform .18s ease;
}
.hp-cta-row a:first-of-type::before {
	content: "";
	position: absolute;
	inset: 2px 2px 50%;
	z-index: -1;
	border-radius: var(--radius-pill) var(--radius-pill) 0 0;
	background: var(--accent-gloss);
	pointer-events: none;
}
.hp-cta-row a:first-of-type:hover {
	color: var(--accent-contrast);
	box-shadow: 0 6px 12px rgba(255, 139, 0, 0.42), 0 0 26px rgba(251, 176, 60, 0.55);
	transform: translateY(-1px);
}
.hp-cta-row a:last-of-type {
	display: inline-block;
	padding: var(--space-12) var(--space-24);
	font-size: var(--type-ui);
	font-weight: var(--weight-semibold);
	text-decoration: none;
	border: 1px solid currentColor;
	border-radius: var(--radius-pill);
}
/* On the navy panels the outlined pill and the pipe invert. */
/* The two rules that inverted .hp-final's CTA row on a dark ground
 * went with the rest of 9.11 - that block is footer.php's now. */

/* ---- 9.3  INTRO ---- */
.hp-intro {
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	text-align: center;
}
.hp-intro p { margin: 0; }

/* ---- 9.4  STATS BAR ---- */
.hp-stats {
	background: var(--gradient-navy);
	color: var(--on-invert);
	padding-block: var(--space-40);
	padding-inline: 0;
}
/* The one section that does NOT take --section-pad: it is a band, and
 * 100px of air inside it would turn it into a section. */
/* The gutter drops to 16 because each stat now carries 16 of its own
 * padding for the hover ground - 16 + 16 + 16 is the 32 the columns
 * always sat apart, so nothing moved. */
.hp-stats__row {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: var(--space-16);
	max-width: var(--container);
	margin: 0 auto;
	padding-inline: var(--container-pad);
	list-style: none;
}
/* Icon left, figure over label right. The icon spans both text rows so
 * it centres against the pair rather than sitting on the figure alone -
 * which is how 513:2787 draws it. A stat with no icon still lays out
 * correctly: with nothing in the first column it collapses to zero. */
.hp-stat {
	display: grid;
	grid-template-columns: auto 1fr;
	align-items: center;
	gap: var(--space-8) var(--space-16);
	padding: var(--space-16);
	border-radius: var(--radius);
	transition: background-color .2s ease, transform .2s ease;
}

/* MOVEMENT ON HOVER, AND ONLY ON HOVER.
 *
 * These four are the page's proof, so they should answer when someone
 * points at them - but they are not links and nothing happens when you
 * click, so the movement has to read as "noticed" rather than as
 * "press me". A small lift and a barely-there ground do that; a scale,
 * a shadow or a colour change would all overpromise.
 *
 * The icon leads the lift slightly - 4px against the row's 3px - which
 * is what makes the pair feel like it is responding rather than
 * sliding. Nothing else in the row moves independently.
 *
 * :focus-within is included so a keyboard reaching the Google or Yelp
 * mark gets the same feedback a mouse does. */
.hp-stat:hover,
.hp-stat:focus-within {
	background: rgba(255, 255, 255, 0.07);
	transform: translateY(-3px);
}
.hp-stat__icon { transition: transform .2s ease; }
.hp-stat:hover .hp-stat__icon,
.hp-stat:focus-within .hp-stat__icon { transform: translateY(-1px) scale(1.06); }

@media (prefers-reduced-motion: reduce) {
	.hp-stat, .hp-stat__icon { transition: none; }
	.hp-stat:hover, .hp-stat:focus-within { transform: none; }
	.hp-stat:hover .hp-stat__icon, .hp-stat:focus-within .hp-stat__icon { transform: none; }
}
.hp-stat__icon {
	grid-row: span 2;
	display: block;
	width: 44px;
	height: 44px;
}
/* CONTAIN, NOT COVER. These are badges of different shapes - the
 * laurels are 44x37, the Yelp burst 34x44 - and cropping any of them to
 * a square would cut the mark. */
.hp-stat__icon > img { display: block; width: 100%; height: 100%; object-fit: contain; }
.hp-stat__figure { font-family: var(--font-heading); font-size: var(--type-h2); font-weight: var(--weight-bold); line-height: 1.1; }
.hp-stat__label  { font-size: var(--type-xs); color: var(--rule); }

/* ---- 9.4a  INSIDE THE PRACTICE - the photo strip ----
 *
 * VISUAL SOURCE: Figma 513:2473 "Section - Proof Photo Strip".
 *
 * Numbered 9.4a rather than 9.5 on purpose. The section sits between
 * the stats bar and THE PROBLEM in the design, and inserting it as a
 * whole number would have renumbered nine downstream blocks and every
 * comment that points at them - churn with no reading benefit.
 *
 * !! THE CARD IS THE CROP. Each photograph is larger than its 360x330
 * card and the design slides it inside the frame - three of the four
 * are pulled UP so the subject sits above the caption. That is
 * reproduced with object-position rather than by shipping four
 * pre-cropped files, so re-cropping later is a one-line change and the
 * originals stay intact in the media library.
 *
 * !! THE CAPTION BOX *IS* THE SCRIM. Figma draws a separate 260px
 * gradient rectangle behind the text; here the caption carries the
 * gradient itself and is sized to the same 79% of the card. One
 * element instead of two, identical result, and the text can never
 * drift off its own backing.
 */
.hp-photostrip {
	max-width: calc(var(--container) + (var(--container-pad) * 2));
}
.hp-photostrip h2 { margin: 0 0 var(--space-24); }
.hp-photostrip__row {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: var(--space-24);
	margin: 0;
	padding: 0;
	list-style: none;
}
.hp-photo {
	position: relative;
	aspect-ratio: 360 / 330;
	border-radius: var(--radius-md);
	overflow: hidden;
	box-shadow: var(--shadow-photo);
}
.hp-photo > img {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center top;
}
/* The one card the design does NOT pull up - its subject is already
 * centred in the frame (513:2487). */
.hp-photo--centred > img { object-position: center; }

/* A card whose photograph does not exist yet. It keeps the caption -
 * that copy is real and comes from the design - but the picture area
 * becomes the same dashed box every other unbuilt slot on this page
 * uses, so it reads as unfinished rather than as a broken image. The
 * scrim is untouched: it is transparent at the top, where the label
 * sits, and solid at the bottom, where the caption does. */
.hp-photo--empty {
	background: var(--surface-alt);
	border: 1px dashed var(--rule);
	color: var(--ink-subtle);
}
.hp-photo--empty .hp-frame__label {
	position: absolute;
	inset-inline: 0;
	top: var(--space-24);
	text-align: center;
}

.hp-photo__caption {
	position: absolute;
	inset-inline: 0;
	bottom: 0;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	gap: var(--space-8);
	height: 79%;                 /* a 260px scrim in a 330px card */
	padding: var(--space-24);
	background: var(--scrim-photo);
}
.hp-photo__title {
	margin: 0;
	font-family: var(--font-heading);
	font-size: var(--type-h5);
	font-weight: var(--weight-semibold);
	color: var(--on-invert);
}
.hp-photo__body {
	margin: 0;
	font-size: var(--type-detail);
	color: var(--on-invert-muted);
}

/* ---- 9.5  THE PROBLEM + 9.6 HOW WE DO IT DIFFERENTLY ---- */
.hp-problem, .hp-system, .hp-not-for-everyone {
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
}
/* .hp-not-for-everyone is deliberately absent here: its ground runs
 * edge to edge, so the SECTION is full width and only the copy inside
 * it is held in. Same for .hp-system, whose monogram bleeds right. */
.hp-problem, .hp-different {
	max-width: calc(var(--container) + (var(--container-pad) * 2));
}
/* !! THE CAP GOES ON THE GRANDCHILDREN, NOT THE CHILDREN.
 *
 * The only direct child of these sections is WordPress's
 * .wp-block-group__inner-container, and 9.1 gives that display:contents
 * - which means it generates no box at all, so a max-width on it is
 * silently ignored and the copy runs the full 1600. The real items are
 * one level further down.
 *
 * The absolutely-positioned art is excluded by name: it is not in the
 * flow and must keep the size its own rule gives it. */
/* !! EVERY p IS EXCLUDED, AND THAT IS THE POINT.
 *
 * This rule caps the things that are allowed to use the full 1400 - the
 * card grids, the lists, the diagram panels. Paragraphs are NOT among
 * them: they answer to --measure in 9.1 instead, and they can only do
 * that if this rule stays out of their way.
 *
 * The trap here cost a round trip. Each :not() adds a class to the
 * specificity, so an earlier version of this selector scored (0,4,0)
 * and quietly outranked the (0,2,1) --measure rule - the copy went back
 * to full width with nothing in the stylesheet looking wrong. Excluding
 * p outright is the fix; matching the specificity would have been a
 * race that the next :not() re-loses.
 *
 * .hp-eyebrow is excluded for a different reason: it is sized by its
 * own text, and width:100% turned it into a 1400px lozenge with two
 * words marooned in the middle. */
.hp-system > .wp-block-group__inner-container > *:not(.hp-media):not(.hp-eyebrow):not(p),
.hp-not-for-everyone > .wp-block-group__inner-container > *:not(.hp-media):not(.hp-eyebrow):not(p),
.hp-not-for-everyone .wp-block-list {
	width: 100%;
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	margin-inline: auto;
}
.hp-problem > *, .hp-system > *, .hp-not-for-everyone > * { max-width: 100%; }

/* the four-step diagrams */
/* Capped, not full width. See --container-panel. The panel below uses
 * the same token so the two boxes line up as a pair - which is the
 * whole point of the section: the same four steps, done two ways. */
.hp-diagram {
	width: 100%;
	max-width: var(--container-panel);
	margin-block: var(--space-24);
	padding: var(--space-32);
	background: var(--surface-alt);
	border-radius: var(--radius-md);
}
.hp-diagram__title {
	margin: 0 0 var(--space-24);
	font-family: var(--font-heading);
	font-size: var(--type-h3);
	font-weight: var(--weight-bold);
}
.hp-steps {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	margin: 0;
	padding: 0;
	list-style: none;
}
.hp-step {
	--dot: 33px;
	position: relative;
	display: grid;
	justify-items: center;
	gap: var(--space-8);
	font-size: var(--type-xs);
}
/* The connector is drawn as a rule behind each step but the first, so
 * the row needs no extra elements between the circles.
 *
 * !! ITS top IS DERIVED FROM --dot. DO NOT HARD-CODE IT AGAIN.
 *
 * It used to be a literal 16px here and 34px on the green variant, each
 * hand-matched to a dot size declared somewhere else. That held until
 * the green dots dropped to 48 at <=599 and the line stayed at 34 -
 * 11.5px BELOW the circles' centres, measured, which is the off-centre
 * Gerek reported. Two numbers describing one thing will always drift
 * apart at the third breakpoint. Now --dot is the only place a circle's
 * size is written and both the dot and the rule read it. */
.hp-step + .hp-step::before {
	content: "";
	position: absolute;
	top: calc((var(--dot) - 3px) / 2);
	right: 50%;
	left: -50%;
	height: 3px;
	background: var(--rule);
}
.hp-step__dot {
	position: relative;
	z-index: 1;
	display: grid;
	place-items: center;
	width: var(--dot);
	height: var(--dot);
	border-radius: var(--radius-pill);
	background: var(--fill-muted);
	color: var(--on-invert);
	font-weight: var(--weight-bold);
	font-size: var(--type-eyebrow);
}
.hp-step__x {
	position: absolute;
	top: -18px;
	color: var(--alert);
	font-size: var(--type-eyebrow);
}
.hp-step.is-active  .hp-step__dot { background: var(--accent); }
.hp-step.is-active + .hp-step.is-active::before,
.hp-step.is-active::before { background: var(--accent); }
.hp-steps--ours .hp-step__dot {
	background: var(--success);
	font-size: var(--type-h3);
	box-shadow: var(--success-glow);
}
/* No top here - the base rule derives it from --dot. Colour only. */
.hp-steps--ours .hp-step + .hp-step::before { background: var(--success); }
.hp-steps--ours .hp-step { --dot: 70px; font-size: var(--type-ui); }

/* the bordered "how we do it differently" panel */
/* !! padding-block IS DELIBERATELY ABSENT.
 *
 * It used to set --space-48 here, which quietly overrode the standing
 * 100px rhythm from 9.1 - this rule is later in the file and scores the
 * same - and left the bordered panel sitting 48px off the beige section
 * below it. It looked cramped because it was. The section now takes the
 * same rhythm as every other one. */
.hp-different {
	display: flex;
	flex-direction: column;
	align-items: center;
	max-width: calc(var(--container) + (var(--container-pad) * 2));
}
.hp-different__title {
	margin: 0 0 var(--space-32);
	font-family: var(--font-heading);
	font-size: var(--type-h4);
	font-weight: var(--weight-bold);
	text-align: center;
}
.hp-panel {
	width: 100%;
	max-width: var(--container-panel);
	padding: var(--space-40);
	background: var(--surface);
	border: 2px solid var(--navy);
	border-radius: var(--radius-md);
	box-shadow: var(--navy-glow);
	text-align: center;
}
.hp-panel > p:last-child { margin-bottom: 0; }
/* The lockup as a background rather than an <img>: post content cannot
 * call the theme for a URL, and an absolute one would bake the staging
 * domain into the database. The stylesheet reaches it relatively. */
/* 396 x 74, which is what Figma draws (513:2537). It was 248 - small
 * enough that the panel read as a diagram with a logo stuck on top
 * rather than as the practice's own answer to the row above it. The
 * lockup is the point of this panel. */
.hp-panel__logo {
	width: min(396px, 100%);
	height: 74px;
	margin: 0 auto var(--space-32);
	background: url("../img/logo.png") no-repeat center / contain;
}
.hp-panel .hp-steps { margin-bottom: var(--space-32); }

/* The monogram behind this section. It is brand furniture rather than
 * content - it carries no information and is marked aria-hidden - so it
 * is allowed to run off the right edge the way the design draws it.
 * Pinned to the right edge of the VIEWPORT, not the container, so it
 * bleeds at every width instead of only at 1400. */
.hp-system {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	background: var(--surface-alt);
}
.hp-media--watermark {
	position: absolute;
	z-index: -1;
	top: 50%;
	right: 0;
	translate: 28% -50%;
	width: min(1080px, 78vw);
	margin: 0;
	border-radius: 0;
	pointer-events: none;
}
.hp-media--watermark > img { width: 100%; height: auto; object-fit: contain; }

/* ---- 9.7  OUR SYSTEM cards ---- */
/* The tiles are a change of gear from the running copy above them, so
 * they get more room than a paragraph break. Uneven on purpose: more
 * above, where the reader is arriving at them, than below. */
.hp-cards {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: var(--space-32);
	width: 100%;
	margin-block: var(--space-64) var(--space-48);
	padding: 0;
	list-style: none;
	text-align: left;
}
.hp-card {
	display: grid;
	gap: var(--space-12);
	align-content: start;
	padding: var(--space-32);
	background: var(--surface);
	border-radius: var(--radius);
	box-shadow: var(--shadow-tile);
}
/* The EMPTY slot is a fixed box, because a dashed rectangle needs a
 * width to be one. The FILLED slot is not: the three icons are drawn
 * 79, 72 and 46 wide at a common height of 100, so height is the
 * binding dimension and the width follows the artwork - same reasoning
 * as the header logo. A fixed 78px box would centre the narrow icon in
 * whitespace and break the left edge the card's text sets. */
.hp-frame--icon { width: 78px; height: 100px; }
.hp-media--icon { width: auto; height: 100px; justify-self: start; }
.hp-media--icon > img { width: auto; }
.hp-card__num   { margin: 0; font-size: var(--type-h4); font-weight: var(--weight-semibold); color: var(--ink-muted); }
.hp-card__title { margin: 0; font-family: var(--font-heading); font-size: var(--type-h5); font-weight: var(--weight-semibold); }
.hp-card__body  { margin: 0; font-size: var(--type-small); color: var(--ink-muted); }

/* ---- 9.8  THE THREE LINK BARS ---- */

/* !! THE CREAM DOES NOT STOP AT OUR SYSTEM.
 *
 * In the design, --surface-alt runs from the top of OUR SYSTEM all the
 * way down past these three bars and only gives way to white at PROOF,
 * NOT PROMISES. The bars are part of that region, not a separate white
 * strip after it - they are the section's own "and here is where to go
 * next", and cutting the ground out from under them broke the run into
 * two unrelated blocks.
 *
 * !! AND IT FADES RATHER THAN STOPPING.
 *
 * Sampled from the design render rather than guessed: the page is solid
 * --surface-alt from y2700 to y3650, then steps cream -> white over the
 * next 450px - 246, 247, 248, 249, 251, 252, 254, 255 - and is pure
 * white from y4100 on. The Button Cards sit at y3682-4026, which is
 * almost exactly that fade. The bars ARE the transition.
 *
 * So this is a gradient, not a flat fill. A hard edge here was the
 * thing that made the region look like it ended abruptly, and a flat
 * cream would only move the hard edge further down.
 *
 * PROOF, NOT PROMISES IS ON WHITE. It looks like the cream continues
 * because the fade ends just above it. */
.hp-bars {
	max-width: none;
	background: linear-gradient(180deg, var(--surface-alt) 0%, var(--surface) 100%);
}
.hp-bars__list {
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	margin-inline: auto;
}
.hp-system { padding-bottom: calc(var(--section-pad) / 2); }
.hp-bars   { padding-top:    calc(var(--section-pad) / 2); }
.hp-bars__list { display: grid; gap: var(--space-16); padding: 0; list-style: none; }
.hp-bar {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--space-16);
	padding: var(--space-24) var(--space-32);
	background: var(--gradient-navy);
	border-radius: var(--radius);
	box-shadow: var(--shadow-card);
}
/* h2, not h4. Measured off the design render: the label's cap height is
 * 27px, which puts the type around 37 - and at 40 the bar comes out at
 * the 96px height Figma draws the card, so the two agree. At h4 the
 * bars read as list rows; at h2 they read as the three doors they are. */
.hp-bar__label {
	font-family: var(--font-heading);
	font-size: var(--type-h2);
	font-weight: var(--weight-semibold);
	color: var(--on-invert);
}

/* A paragraph that is nothing but one CTA link becomes the design's
 * amber button. The copy is untouched - only the anchor is painted. */
.hp-btn-line { margin-block: var(--space-24); }
.hp-btn-line a {
	position: relative;
	isolation: isolate;
	display: inline-block;
	padding: var(--space-12) var(--space-24);
	background:
		var(--accent-fill) padding-box,
		var(--accent-rim) border-box;
	/* 2px, not 1: at a hairline the multi-stop ring averages out to a
	 * pale outline and the metal is lost. */
	border: 2px solid transparent;
	color: var(--accent-contrast);
	font-size: var(--type-small);
	font-weight: var(--weight-semibold);
	text-decoration: none;
	border-radius: var(--radius-pill);
	box-shadow: var(--accent-lift);
	transition: box-shadow .18s ease, transform .18s ease;
}
.hp-btn-line a::before {
	content: "";
	position: absolute;
	inset: 2px 2px 50%;
	z-index: -1;
	border-radius: var(--radius-pill) var(--radius-pill) 0 0;
	background: var(--accent-gloss);
	pointer-events: none;
}
.hp-btn-line a:hover {
	color: var(--accent-contrast);
	box-shadow: 0 6px 12px rgba(255, 139, 0, 0.42), 0 0 26px rgba(251, 176, 60, 0.55);
	transform: translateY(-1px);
}

/* ---- 9.9  PROOF, NOT PROMISES ---- */
/* A ROUNDED PANEL, NOT A FULL-WIDTH SLAB. The design insets this one
 * and rounds it, which is what separates it from the stats band and the
 * final CTA above and below - all three are navy, and without the
 * inset they read as one enormous blue region. The gradient runs
 * diagonally for the same reason: it keeps the three apart. */
.hp-proof {
	display: grid;
	grid-template-columns: minmax(0, 440px) 1fr;
	gap: var(--space-48);
	align-items: center;
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	background: linear-gradient(135deg, var(--navy-deep), var(--navy));
	color: var(--on-invert);
	border-radius: var(--radius-md);
	/* The panel's own inset. This overrides BOTH the shared
	 * padding-inline and the shared 100px rhythm from 9.1 - the air
	 * around this section is margin, not padding, because padding would
	 * sit inside the navy and push the panel's contents away from its
	 * own edges. */
	padding: var(--space-48);
	/* !! WIDTH, NOT JUST max-width.
	 *
	 * With only a max-width the panel filled the viewport edge to edge
	 * at anything between 992 and 1448 - it kept its rounded corners and
	 * lost the inset that makes it a panel at all, so on a 1024 tablet
	 * it read as a full-bleed navy slab. The <=991 rule added a margin
	 * back, which is why the fault sat in the gap between the two.
	 *
	 * A percentage width always leaves the gutter, at every size. */
	width: calc(100% - (var(--container-pad) * 2));
	margin: var(--section-pad) auto;
}
/* THE PORTRAIT IS BIGGER THAN THE PANEL, ON PURPOSE.
 *
 * Figma puts the cutout at x-62, y-48 against a 1140x739 panel and
 * makes it 784 tall - taller than the panel it sits in. His head breaks
 * the top edge and his shoulder runs past the left, and the cutout
 * stands ON the panel's bottom rather than inside its padding. That
 * overhang is the whole reason the section has any depth; boxed inside
 * the padding, as it was, he reads as a photograph in a slot.
 *
 * Absolutely positioned so the grid column still reserves its 440 and
 * the copy beside it is unaffected by the overflow. .hp-proof sets no
 * overflow, so the bleed is allowed.
 *
 * The three 48s in the height: one cancels the panel's bottom padding,
 * one cancels the top, and the third is the overhang above the panel's
 * own edge - which lands at the design's 48. */
.hp-proof__media { position: relative; align-self: stretch; }
.hp-media--portrait {
	position: absolute;
	/* !! THE CUTOUT CARRIES 27px OF TRANSPARENT MARGIN ON ITS LEFT.
	 *
	 * Measured on the file: the PNG is 544 wide and the opaque pixels
	 * start at x27. Rendered at about 490 that is ~24px of nothing down
	 * the left side, which is why a 24px overhang looked like no
	 * overhang at all - the box hung over the edge and the man did not.
	 *
	 * -96 is 48 to cancel the panel's padding and 48 of overhang, of
	 * which the first ~24 is the file's own margin. So he actually
	 * clears the panel edge by about 24, and at a 1500 viewport - 26px
	 * of page gutter - his arm still lands on the page rather than
	 * against the window. Both halves of that sum matter; changing the
	 * image means re-measuring the margin. */
	left: calc(var(--space-48) * -2);
	bottom: calc(var(--space-48) * -1);
	height: calc(100% + (var(--space-48) * 3));
	width: auto;
	min-height: 0;
	flex: none;
}
.hp-media--portrait > img {
	width: auto;
	height: 100%;
	object-fit: contain;
	object-position: bottom left;
}
/* The empty state keeps the old in-flow behaviour - a dashed box cannot
 * meaningfully overhang anything. */
.hp-proof__media:has(.hp-frame--portrait) { display: flex; }
.hp-frame--portrait { flex: 1; min-height: 460px; }
.hp-proof__col > *:last-child { margin-bottom: 0; }
.hp-proof h2, .hp-proof p { color: var(--on-invert); }
/* THE WHITE-LINK RULE THAT USED TO LIVE HERE IS DELETED, NOT GUARDED.
 *
 * It read .hp-proof a:not(.cta-button) { color: var(--on-invert) } and
 * existed from when this panel had links in its running copy. It has
 * none now - the panel contains exactly ONE anchor, the amber button -
 * so the rule's entire remaining effect was to paint that button white
 * on amber at 1.85:1.
 *
 * Guarding it with another :not() would have been the fourth patch of
 * that shape in this file. Deleting it removes the competition instead
 * of out-ranking it, which is why the button is now simply ink. If
 * running-copy links ever return to this panel they need a rule that
 * names them, not one that catches every anchor. */
.hp-videos { display: flex; flex-wrap: wrap; gap: var(--space-20); margin-top: var(--space-24); }
.hp-frame--video, .hp-media--video { width: 239px; aspect-ratio: 239 / 134; }

/* ---- 9.10  THIS PRACTICE ISN'T FOR EVERYONE ---- */
/* THE GRAPHIC RUNS EDGE TO EDGE, BELOW THE COPY - NOT BEHIND IT.
 *
 * The design floats the copy ON the artwork, and that works there
 * because the design's version of this section is three short lines.
 * The built page carries three bullet panels as well, and laid over the
 * phone and the review cards they are unreadable - text on top of text.
 *
 * So the artwork takes the full width, which was the actual problem
 * with it, and sits under the copy rather than behind it. If the copy
 * is ever cut back to the three lines the design assumes, this becomes
 * a background again by moving it back into position:absolute - the
 * markup does not have to change for that.
 *
 * The image stays in the page content rather than becoming a CSS
 * background-image: an upload path hardcoded in the stylesheet would
 * break the moment the media is replaced, and it would be invisible to
 * anyone editing the page. */
/* WHITE, matching the section above it. The cream ends with the fade
 * across the link bars - see 9.8 - and everything from PROOF onwards is
 * white until the final CTA turns navy. */
.hp-not-for-everyone { background: var(--surface); }
/* CAPPED AT THE DESIGN'S OWN FRAME, NOT THE VIEWPORT.
 *
 * The artwork is 1439x994 and the design draws it at exactly that, in a
 * 1440 frame. Left to fill the viewport it scaled past its own size on
 * any screen wider than 1440 - at 1900 it was a third bigger than
 * anything in Figma, which is why the phone and the review cards looked
 * oversized and loose rather than composed.
 *
 * --frame is 1440 and exists for precisely this: the design's width,
 * reference only, used where something must not grow past what was
 * drawn. Centred, and no crop - the composition is the point, so
 * contain rather than cover. */
/* THE ASSET IS TRIMMED, SO THE CSS NO LONGER CROPS ANYTHING.
 *
 * This used to display a sub-rectangle of a 1439x994 export whose real
 * composition was only ~950x726 - the rest was margin baked into the
 * file. Doing that in CSS took four interdependent percentages, and the
 * first attempt got them slightly wrong and clipped the outer review
 * cards on both sides.
 *
 * home-Review-Cluster.webp (ID 190) is that same artwork cleaned and
 * trimmed, 973x625, so the box is simply the image. Three things were
 * fixed making it:
 *
 *   - the export's ground was #fefefe, not white, which showed as a
 *     faint rectangle against the page
 *   - the cards' drop shadows trailed as wide diffuse haze far below
 *     them, reading as smudges on white. Everything within 9 of white
 *     is lifted to pure white; the real contact shadows are darker than
 *     that and are untouched
 *   - one stray patch of ink sat 130px below the composition entirely,
 *     with nothing above it. Cleared, then re-trimmed - which is what
 *     took the height from 738 to 625
 *
 * STILL NOT TRANSPARENT. The review cards are themselves near-white on
 * a near-white ground, so alpha cannot be keyed out after the fact
 * without eating them - that needs a re-export with alpha from the
 * source file. Flagged to Gerek for the designer. Until then this reads
 * correctly on white and would NOT survive being moved onto a tinted
 * section. */
/* 1200 against a 973px-native file - a 23% upscale. Visible only if you
 * go looking, on a composite this soft, and it is the practical
 * ceiling: past here the card text starts to go mushy. If it needs to
 * be larger again the answer is a higher-resolution export, not a
 * bigger max-width.
 *
 * The 100 above it is the gap Gerek asked for, and it is --section-pad
 * rather than a one-off: the artwork is the section's closing image and
 * deserves the same distance the page gives a section boundary. */
.hp-media--cluster {
	width: 100%;
	max-width: 1200px;
	margin: var(--section-pad) auto 0;
	border-radius: 0;
	aspect-ratio: 973 / 625;
}
.hp-media--cluster > img { width: 100%; height: 100%; object-fit: contain; }
.hp-not-for-everyone .wp-block-list {
	display: grid;
	gap: var(--space-16);
	max-width: 760px;
	margin: var(--space-24) auto;
	padding: 0;
	list-style: none;
	text-align: left;
}
/* These were white cards on a cream ground, which is where their shape
 * came from. On white the fill does nothing and the 8% shadow is not
 * enough on its own - they flattened into three floating sentences. A
 * hairline gives them their edge back and the tile shadow lifts them;
 * the fill stays white so they still read as cards rather than as a
 * tinted list. */
/* CREAM, NOT WHITE-ON-WHITE.
 *
 * Three white cards on a white section had nothing to sit against - a
 * hairline gave them an edge but they still read as three floating
 * sentences. Inverting them onto --surface-alt is the answer the page
 * already contains: it is the ground OUR SYSTEM uses, so the section
 * reads as a deliberate echo rather than as a new colour, and it costs
 * the palette nothing.
 *
 * It also suits what these three actually are. They are the
 * disqualifiers - the "this is not for you if" list - so they should
 * read as set apart from the white they sit on rather than as more
 * cards like the ones above.
 *
 * No border and no shadow with it: on a tinted fill both would be
 * belt-and-braces, and the flat shape is calmer next to the artwork
 * below. */
.hp-not-for-everyone .wp-block-list li {
	padding: var(--space-24) var(--space-32);
	background: var(--surface-alt);
	border: 0;
	border-radius: var(--radius);
	box-shadow: none;
	font-size: var(--type-body);
}
.hp-frame--cluster, .hp-media--cluster { width: 100%; margin-top: var(--space-24); }
.hp-frame--cluster { min-height: 320px; }
/* A composed graphic, not a photograph to crop: it holds its own
 * proportions at every width so no review card is ever cut in half. */
.hp-media--cluster { aspect-ratio: 1439 / 994; }

/* ---- 9.11  FINAL CTA - REMOVED, NOT MISSING ----
 *
 * The homepage's closing CTA used to be a content section called
 * .hp-final. It is global furniture now: the block lives in footer.php
 * and is styled by .site-footer__cta in section 06, so every page gets
 * it rather than only the homepage.
 *
 * Every rule that styled it is deleted, including its entries in the
 * shared selector lists in 9.1 and its line in the <=767 block. Checked
 * before deleting: zero occurrences of "hp-final" in the rendered HTML
 * of any page on the site.
 *
 * The numbering keeps its gap on purpose - 9.10 to 9.12 with this note
 * between them is clearer than renumbering six comments that point at
 * each other. */

/* .hp-final's white-link rule is deleted for the same reason plus a
 * simpler one: .hp-final no longer exists. The closing CTA moved into
 * footer.php and the homepage no longer emits that block at all. The
 * rest of the 9.11 rules below are dead in the same way - left in place
 * for now rather than swept in a contrast fix, but they are dead. */


/* ==================================================================
 * 09b  HOMEPAGE RESPONSIVE
 *
 * !! NO DESIGN EXISTS BELOW 1440px AND NONE IS COMING. Everything here
 * is a judgement call, written down so it can be argued with.
 *
 * WHAT WAS DECIDED
 *
 * 992-1199  The two side-by-side sections - the hero and Proof - keep
 *           their split but the copy column stops being a fixed 584px
 *           and starts sharing the row. The four-across stats bar and
 *           the three system cards still fit.
 *
 * 768-991   The hero and Proof STACK: copy first, photograph after.
 *           A photograph beside a paragraph stops being a layout and
 *           starts being two cramped columns somewhere around here.
 *           The stats bar goes 4 -> 2 across rather than shrinking the
 *           figures. The system cards go 3 -> 2.
 *
 * <768      One column everywhere. The step diagrams turn VERTICAL -
 *           four labelled circles side by side at phone width would put
 *           "PostureRay Analysis" on three lines inside 80px. Turning
 *           the row into a stack keeps every label on one line and the
 *           connector becomes a vertical rule, which still reads as a
 *           sequence.
 *
 * <600      The stats bar goes to a single column and the link bars
 *           stack their label above the button.
 * ================================================================== */

@media (max-width: 1199px) {
	.hp-hero  { grid-template-columns: 1fr 1fr; }
	.hp-proof { grid-template-columns: minmax(0, 360px) 1fr; }
}

@media (max-width: 991px) {
	/* Two columns: brand across the top, then hours and the map beneath
	 * it. Back to document order, so the reordering above cannot leave
	 * the lockup stranded in the middle of a stack. */
	/* The monogram is decoration at desktop, where it bleeds off the
	 * right of a 1400 column. At tablet it scales down onto the content
	 * and runs a pale band straight through the OUR SYSTEM pill and its
	 * heading. Hidden rather than faded: it carries no information, it
	 * is aria-hidden already, and there is no width here where it reads
	 * as background rather than as a smudge. */
	.hp-media--watermark { display: none; }

	.site-footer__grid { grid-template-columns: 1fr 1fr; align-items: start; }
	.site-footer__brand { order: 0; grid-column: 1 / -1; }
	.site-footer__hours { order: 1; }
	.site-footer__visit { order: 2; }
	.site-footer__base { justify-content: flex-start; }
	.site-footer__legal { text-align: left; }
	.footer-licence { justify-content: flex-start; }

	.hp-hero, .hp-proof { grid-template-columns: 1fr; }
	/* margin-inline is not needed here any more: .hp-proof carries a
	 * percentage width, so the gutter exists at every size. */

	/* !! THE CUTOUT COMES BACK INTO THE FLOW.
	 *
	 * Its desktop position is absolute - bottom-left of the panel,
	 * overhanging two edges - and that only means anything in the
	 * two-column layout. At one column its containing row collapses to
	 * nothing at the top of the panel, so Dr. Michael was not small or
	 * clipped, he was ENTIRELY ABSENT from every tablet and phone
	 * render. Caught by scrolling a real 390px viewport, not by reading
	 * the CSS.
	 *
	 * In flow, centred, above the copy - which is how the hero handles
	 * the same problem one section up. */
	.hp-proof__media { position: static; display: flex; justify-content: center; }
	.hp-media--portrait {
		position: static;
		width: min(260px, 70%);
		height: auto;
		margin: 0;
		flex: none;
	}
	.hp-media--portrait > img { width: 100%; height: auto; }

	/* The pill, the copy and the videos all centre with everything else
	 * on this breakpoint. .hp-proof__col is not a flex column, so the
	 * align-self that centres pills elsewhere had nothing to act on
	 * here - margin-inline is what actually moves it. */
	.hp-proof__col { text-align: center; }
	.hp-proof__col .hp-eyebrow--pill { margin-inline: auto; }
	.hp-proof .hp-btn-line { text-align: center; }
	.hp-videos { justify-content: center; }

	/* !! THE WASH GOES FULL-COVERAGE ONCE THE HERO IS ONE COLUMN.
	 *
	 * The desktop wash fades out by two-thirds across, because the copy
	 * only occupies the left half. At one column the copy spans the
	 * whole panel, so its right-hand end - the second CTA - was sitting
	 * on the bright part of the photograph and could not be read.
	 *
	 * A flat veil instead of a horizontal fade. The photograph still
	 * reads through it; nothing on top of it does not. */
	.hp-hero__bg::after {
		background: linear-gradient(
			180deg,
			rgba(16, 44, 70, 0.82) 0%,
			rgba(16, 44, 70, 0.90) 100%
		);
	}
	.hp-hero__bg { order: 0; }
	.hp-frame--hero-bg,  .hp-media--hero-bg  { min-height: 260px; }
	.hp-frame--portrait, .hp-media--portrait { min-height: 320px; }
	.hp-stats__row { grid-template-columns: repeat(2, 1fr); gap: var(--space-24); }
	.hp-cards      { grid-template-columns: repeat(2, 1fr); }

	/* !! THE LABEL DROPS TWO STEPS HERE, NOT ONE.
	 *
	 * At h2 the longest of the three - "What Your First Visit Looks
	 * Like" - measured 495px against 632 of usable bar, which left no
	 * room for a 142px button and wrapped it onto its own line. The
	 * other two fitted, so only one bar of three looked broken.
	 *
	 * Gerek's read was 4px. Measured, 4 is not enough: at 768 the label
	 * still comes to 512 and the button still wraps. h3 is what actually
	 * clears it with room to spare. */
	.hp-bar__label { font-size: var(--type-h3); }
	/* Four across survives to here on a 1400 container - each card is
	 * still ~330 wide. Below it they would drop under the caption's
	 * comfortable width, so they go two-up. */
	.hp-photostrip__row { grid-template-columns: repeat(2, 1fr); }
	.hp-panel      { padding: var(--space-24); }
}

@media (max-width: 767px) {
	/* !! THE HOURS LIST WAS DRAGGING THE WHOLE PANEL OFF THE SCREEN.
	 *
	 * dl is auto/1fr with white-space:nowrap on both the day and the
	 * time range - which is right on desktop and fatal here. Its
	 * min-content came to 336px ("Monday - Thursday" + "By appointment
	 * only", neither allowed to wrap) against a panel content box of
	 * 294. A grid item cannot go below its min-content, so the single
	 * column stretched to 336 and took the map, the address and the
	 * hours 42px past the panel's right edge with it.
	 *
	 * That one measurement is items 6, 7 and 8 in Gerek's list: nothing
	 * was individually mis-centred, the whole grid was too wide.
	 *
	 * Stacked and centred here. min-width:0 on the children stops any
	 * future item doing the same thing silently. */
	.site-footer__grid {
		grid-template-columns: 1fr;
		gap: var(--space-32);
		justify-items: center;
	}
	.site-footer__grid > * { min-width: 0; max-width: 100%; }
	.site-footer__brand { grid-column: auto; }
	.site-footer__panel { padding: var(--space-32) var(--space-24); }

	/* !! THE DAY/VALUE PAIRS DO NOT STACK HERE - SEE <=599.
	 *
	 * They used to. Stacking is the phone fix for the min-content
	 * problem described above, and it was written at this breakpoint
	 * because this is where the footer goes single-column. But the
	 * overflow it solves is a 294px panel problem: at 600 the panel
	 * gives the list 504 and the two-column form needs 324, so from 600
	 * up there is nothing to fix and stacking only costs height.
	 *
	 * On a 744 iPad that put eight lines on screen where four would do,
	 * with a day alone on one line and its hours alone on the next -
	 * which is what Gerek was looking at. */
	.site-footer__hours { justify-items: center; text-align: center; }

	.site-footer__visit {
		justify-items: center;
		text-align: center;
		width: min(300px, 100%);
		justify-self: center;
	}
	/* MORE ROOM, AND A COLOUR OF THEIR OWN.
	 *
	 * Four legal links in the same white as the four lines of legal text
	 * directly beneath them read as one eight-line block of white on
	 * blue. Amber separates them - it is the accent this footer already
	 * uses on the button and in the lockup, so it costs the palette
	 * nothing, and it matches what these links already do on hover at
	 * desktop. The legal text stays muted white, which widens the gap
	 * between the two further. */
	.site-footer__base {
		justify-content: center;
		text-align: center;
		gap: var(--space-32);
		padding-top: var(--space-32);
	}
	/* !! SCOPED WITH .site-footer, OR IT LOSES.
	 *
	 * ".site-footer a:not(.cta-button)" is two classes plus an element
	 * and beats a bare ".footer-nav a" - the fourth time that exact
	 * shape has caught me in this file, and the reason the first version
	 * of this rule rendered white with nothing looking wrong. */
	.site-footer .footer-nav a { color: var(--accent); }
	.footer-nav ul { gap: var(--space-12) var(--space-24); }

	/* Stacked and centred, the rule that separates the name from the
	 * licence number reads as a stray tick above the line rather than a
	 * divider between two things. */
	.footer-licence span { border-left: 0; padding-left: 0; }
	.site-footer__legal { text-align: center; }
	.footer-licence { justify-content: center; }
	.footer-nav ul { justify-content: center; }

	/* !! HE BREAKS THE TOP EDGE HERE TOO.
	 *
	 * In flow he was a polite 260px portrait sitting inside the padding
	 * - correct, and completely without the presence the desktop version
	 * has. Bigger, and pulled up by more than the panel's own top
	 * padding so his head clears the edge, which is the whole point of
	 * the cutout. overflow stays visible on .hp-proof, so nothing clips
	 * him.
	 *
	 * The negative margin is on the MEDIA WRAPPER, not the image, so the
	 * copy below simply starts where the wrapper ends and nothing
	 * overlaps. */
	.hp-proof { padding: var(--space-24); overflow: visible; }
	.hp-proof__media { margin-top: calc((var(--space-24) + var(--space-40)) * -1); }
	.hp-media--portrait { width: min(300px, 88%); }

	/* Slightly larger, and centred as a pair. At 239 wide in a 342
	 * column they read as thumbnails of thumbnails. */
	.hp-frame--video, .hp-media--video { width: min(280px, 100%); }

	/* THE HERO STOPS BEING AN OVERLAY AT PHONE WIDTH.
	 *
	 * The desktop composition only works because the photograph has a
	 * wall on the left to set type on. At 390 the panel is narrower than
	 * that wall, so the copy would land on the subject - and a
	 * horizontal wash cannot rescue it, because there is no horizontal
	 * room left to fade across. Photograph on top, copy underneath, on a
	 * solid ground. Same markup, no overlay to get wrong. */
	/* !! NAVY, NOT --surface-alt.
	 *
	 * The copy inverts to white for the desktop overlay, and when this
	 * breakpoint pulled it off the photograph onto a pale ground the
	 * white type went with it - a whole headline rendered invisible.
	 * Caught on a real 390px render, not in review.
	 *
	 * Giving the stacked panel the navy ground is the fix and the better
	 * design: photograph on top, navy copy block under it, and the hero
	 * reads the same way it does on desktop instead of becoming a
	 * different component at 767px. */
	.hp-hero {
		display: flex;
		flex-direction: column;
		min-height: 0;
		padding-block: 0;
		background: var(--gradient-navy);
	}
	/* !! THE COPY COMES FIRST, THE PHOTOGRAPH SECOND.
	 *
	 * The figure is first in the markup because at desktop it is the
	 * panel's background and has to paint underneath. Stacked, that
	 * order put a photograph between the header and the H1 - so the
	 * first thing on the page was an image with no context, and the
	 * headline was below the fold on a phone. `order` flips them for the
	 * stacked layout only; the markup is unchanged, so the desktop
	 * painting order is untouched. */
	/* THE GAP IS TWO NUMBERS, WHICH IS WHY HALVING ONE DID NOT HALVE IT.
	 *
	 * .site-main carries padding-top: 48 for every page, and the hero
	 * adds its own margin on top of that. Measured at 390: header bottom
	 * 149, hero top 217 - a 68px gap, of which the hero only owned 20.
	 * Trimming the hero alone could never get there.
	 *
	 * 28 + 20 = 48 total, which is the 20px reduction asked for. The
	 * hero keeps its margin so the two numbers stay meaningful
	 * separately: main's padding is what every page gets, the hero's
	 * margin is this section's own. */
	.site-main { padding-top: var(--space-32); }
	.hp-hero { margin-top: var(--space-16); }

	/* THE SUBJECT, NOT THE MIDDLE OF THE FILE.
	 *
	 * The photograph is 1265x652 with Dr. Michael and the patient in its
	 * right-hand third. Cropped to 3:2 from the centre, the phone showed
	 * a wall and half a plant. 76% puts the pair in the middle of what
	 * is actually visible - the same reasoning as the desktop hero's
	 * right-alignment, at a different crop. */
	.hp-media--hero-bg > img { object-position: 76% center; }

	.hp-hero__col { order: 1; }
	/* INSET, NOT FLUSH. Stacked, the photograph filled the panel edge to
	 * edge, so the navy stopped short of it and the two read as separate
	 * blocks stuck together. A margin on all four sides lets the panel's
	 * colour run round the picture, which is what makes it a card with a
	 * photo in it rather than a colour block above a photo. */
	.hp-hero__bg {
		order: 2;
		position: relative;
		inset: auto;
		width: auto;
		margin: 0 var(--space-12) var(--space-12);
		border-radius: var(--radius);
		overflow: hidden;
		aspect-ratio: 3 / 2;
	}
	.hp-hero__bg::after { display: none; }
	.hp-hero__col {
		width: 100%;
		padding: var(--space-32) var(--space-24);
	}
	/* CENTRED, AND THE LABELS CENTRED INSIDE THEM.
	 *
	 * Both pills were left-aligned in a left-aligned row, and the long
	 * one wrapped to two lines with its text jammed against the left
	 * edge. On a phone a full-width pill with a centred label is the
	 * expected shape and the easiest thing to aim at. */
	.hp-hero .hp-cta-row { justify-content: center; }
	.hp-hero .hp-cta-row a:first-of-type,
	.hp-hero .hp-cta-row a:last-of-type {
		flex: 1 1 100%;
		text-align: center;
	}

	.hp-cards { grid-template-columns: 1fr; }

	/* The step diagrams become vertical. See the note above. */
	.hp-steps { grid-template-columns: 1fr; gap: var(--space-20); justify-items: start; }
	.hp-step  { grid-template-columns: auto 1fr; align-items: center; justify-items: start; gap: var(--space-16); }

	/* !! ONE CONTINUOUS LINE, NOT A SEGMENT PER STEP.
	 *
	 * The horizontal version draws a connector on each step but the
	 * first, sized to the gap between them. Rotated vertical that
	 * became a FIXED 20px stub - but a step box is taller than its dot
	 * (the label can wrap, and the dot is centred in whatever height
	 * results), so the real distance between two dot centres is much
	 * more than the gap. The stubs hung below each circle reaching
	 * nothing, and step 4 had one trailing off the bottom into empty
	 * space.
	 *
	 * A single line behind the whole column solves it without needing to
	 * know any step's height: it starts at the first dot's centre and
	 * ends at the last one's, because top and bottom are each inset by
	 * half a dot. The dots sit on top of it - they carry z-index 1
	 * already.
	 *
	 * The per-step segments are switched off rather than retuned; there
	 * is no fixed height that is correct for a box that grows with its
	 * label. */
	.hp-step + .hp-step::before { content: none; }

	.hp-steps { position: relative; }
	.hp-steps::before {
		content: "";
		position: absolute;
		top: 16px;
		bottom: 16px;
		left: 15px;
		width: 3px;
		background: var(--rule);
	}
	.hp-steps--ours::before {
		top: 24px;
		bottom: 24px;
		left: 23px;
		background: var(--success);
	}

	/* !! THE SECOND COLUMN HAS TO STOP BEING 1fr, OR CENTRING DOES
	 * NOTHING.
	 *
	 * .hp-stat is auto/1fr, and 1fr eats every spare pixel - so the
	 * track group already filled the row and justify-content:center had
	 * no free space to distribute. The first attempt at this looked like
	 * it had been ignored because, in effect, it had.
	 *
	 * auto/auto sizes both columns to their content, which leaves slack
	 * for the centring to act on. The icon keeps its place beside the
	 * text; the pair sits in the middle of the band. */
	/* CENTRED AS A BLOCK, NOT FOUR TIMES INDEPENDENTLY.
	 *
	 * auto/auto centred each stat on its own, so four pairs of different
	 * widths each found their own middle and the icons ended up at four
	 * different x positions - centred, and ragged.
	 *
	 * A shared width centred in the row fixes both: every stat is the
	 * same box, so the icons line up in a column and the text starts on
	 * one edge, and the block as a whole sits in the middle of the band.
	 * 280 is the longest label - "Google rating - 437 reviews" - plus
	 * the icon and its gutter. */
	.hp-stat {
		grid-template-columns: 44px 1fr;
		width: min(280px, 100%);
		justify-self: center;
	}
	/* !! THE CROSS STAYS ABSOLUTE. position:static made it a THIRD item
	 * in a two-column grid, so it took column one, pushed the numbered
	 * dot into column two and knocked the label onto a second row - the
	 * two crossed-out steps rendered completely differently from the two
	 * that are not crossed out. Caught on a real 390px render.
	 *
	 * Re-anchored rather than un-positioned: at desktop it sits above
	 * the dot, here it rides the dot's top-right shoulder like a badge,
	 * which is the same meaning in a vertical list. */
	.hp-step__x {
		position: absolute;
		top: -4px;
		left: 22px;
	}
	.hp-steps--ours .hp-step__x { left: 56px; }
	/* The section copy reads better ranged left on a narrow screen, but
	 * the diagram's own title is a heading over a centred object, not a
	 * line of running copy - it stays centred with it. */
	.hp-diagram, .hp-problem, .hp-system, .hp-not-for-everyone { text-align: left; }
	.hp-diagram__title, .hp-different__title { text-align: center; }
	/* The pills stay centred. They were pushed left with the rest of the
	 * section's text, but a pill is an object rather than a line of
	 * copy - left-aligned under centred headings it read as a stray
	 * button. */
	.hp-eyebrow--pill { align-self: center; }
	.hp-intro { text-align: left; }
}

@media (max-width: 599px) {
	.hp-stats__row { grid-template-columns: 1fr; }
	.hp-photostrip__row { grid-template-columns: 1fr; }
	/* Stacked, the label and its button were both ranged left in a
	 * full-width navy bar, which left a long ragged edge down the middle
	 * of three of them. Centred, each bar reads as one object. */
	.hp-bar { flex-direction: column; align-items: center; text-align: center; }

	/* THE PHONE FIX, AT PHONE WIDTHS. dl is auto/1fr with nowrap on
	 * both the day and the time range - right everywhere it fits, and
	 * fatal at 390, where its min-content came to 336 against a 294
	 * panel and dragged the map and the address off the right edge with
	 * it. A grid item cannot go below min-content, so the only cure is
	 * to let the pairs break. */
	.site-footer__hours dl {
		grid-template-columns: 1fr;
		gap: var(--space-4, 4px) 0;
		justify-items: center;
	}
	.site-footer__hours dt { white-space: normal; }
	.site-footer__hours dd { margin-bottom: var(--space-12); }
	.site-footer__hours .hours-range { white-space: normal; }
	/* --dot, not width/height: the connector's top is calculated from
	 * it. Setting the size directly here is what broke the line. */
	.hp-steps--ours .hp-step { --dot: 48px; }
	.hp-steps--ours .hp-step__dot { font-size: var(--type-h4); }
}


/* ==================================================================
 * 10  CHIROPRACTIC ADJUSTMENTS
 *
 * VISUAL SOURCE: Figma 551:15 "(unverified) Chiropractic Adjustments",
 * eleven section frames, 552:18 down to 551:56. Every colour, size and
 * space below comes from a token in section 01 - there is not a raw
 * value in here.
 *
 * !! SECTION 09'S COMPONENT CLASSES ARE SHARED, ITS SECTION CLASSES
 * ARE NOT.
 *
 * 09 was written when the homepage was the only built page and its
 * header says the .hp-* classes "only the homepage emits". Half of that
 * is still true and half of it is not, and the distinction matters:
 *
 *   SHARED, and used by this page as-is -
 *     .hp-media / .hp-frame  the two-state image slot
 *     .hp-eyebrow--pill      and its --ink / --invert variants
 *     .hp-cards / .hp-card   the numbered process tile
 *     .hp-stats__row / .hp-stat   the icon-figure-label stat
 *     .hp-btn-line           a paragraph that is one link becomes the
 *                            amber pill
 *
 *   HOMEPAGE ONLY - .hp-hero, .hp-intro, .hp-problem, .hp-different,
 *     .hp-system, .hp-bars, .hp-proof, .hp-photostrip and
 *     .hp-not-for-everyone. These are SECTIONS. They carry the
 *     homepage's own rhythm and grounds and must not be reused.
 *
 * Where this page needs a shared component to look different, the
 * change is made BY THE SECTION - `.ca-workup .hp-card { ... }` - and
 * never by adding a modifier class to the shared component. The
 * component keeps one definition; the page states its own variation.
 *
 * !! THE TEMPLATE MATTERS. This page renders through page-sections.php,
 * not page.php, because page.php wraps the content in .container and a
 * container is what stops a full-width section being full width. The
 * page carries _wp_page_template = page-sections.php.
 *
 * NO DESIGN EXISTS BELOW 1440px. Section 10b at the end of this block
 * is the theme's own work.
 * ================================================================== */

/* ---- 10.1  shared page furniture ----
 *
 * !! display:contents ON THE INNER CONTAINER IS LOAD-BEARING, for the
 * same reason it is in 9.1: a wp:group renders its children inside a
 * .wp-block-group__inner-container, so a section would otherwise have
 * exactly ONE child and every grid below would be laying out a single
 * item. Named explicitly rather than with an attribute selector -
 * [class*="ca-"] is one careless class name away from matching
 * something it should not. */
.itv-section > .wp-block-group__inner-container {
	display: contents;
}

/* !! THE TWO BANDS ARE THE EXCEPTION: THEIR WRAPPER IS KEPT AND MADE
 * THE CONTAINER.
 *
 * They were dissolved with display:contents like the rest, and that was
 * a real bug rather than a style choice. A band sets no max-width - it
 * has to reach both edges to paint its ground - so with the wrapper
 * gone, a child capped at the reading measure had nothing to align to
 * but the VIEWPORT. `margin-inline: 0` then pinned it to the window's
 * left edge instead of the content column's.
 *
 * It is invisible at 1400 and below, because there the container is
 * already flush with the window and both answers are x=24. At 1920 the
 * container starts at 260 and the band's headings and rows were still
 * at 0 and 24 - a quarter of the screen out of line with every other
 * section. Found by Gerek on a wide monitor, after a sweep that ran
 * 390 -> 1400 and stopped at exactly the width that hides it.
 *
 * ALWAYS TEST ABOVE --container, NOT JUST UP TO IT.
 *
 * Keeping the wrapper as a normal block gives the children a centred
 * 1400 box to lay out in, so `margin-inline: 0` means the container's
 * left edge - which is what it was always meant to mean. Nothing in
 * either band needs the section itself as a grid parent, so nothing is
 * lost. The watermark still positions against .ca-techniques, which is
 * the positioned ancestor either way. */
.itv-band > .wp-block-group__inner-container {
	display: block;
	width: 100%;
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	margin-inline: auto;
	padding-inline: var(--container-pad);
}

/* EVERY SECTION CENTRES ITSELF. page-sections.php hands the content
 * straight to <main> with no wrapper, so a section that sets a
 * max-width must also centre it - out here that is not free. */
.itv-section {
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	margin-inline: auto;
	padding-inline: var(--container-pad);
}
/* A BAND IS THE ONE SECTION THAT MUST NOT BE CAPPED - it has to reach
 * both edges to paint its ground, so it hands the cap to the wrapper it
 * keeps (above) instead. */
.itv-section.itv-band {
	max-width: none;
	padding-inline: 0;
	background: var(--surface-alt);
}

/* THE RHYTHM IS 100px BETWEEN SECTIONS, NOT 100 ON EACH SIDE OF EVERY
 * ONE. Two sections on the same ground stack their pads into 200px of
 * nothing and the page comes apart; at a ground change the eye reads
 * the edge rather than the distance, so the full pad is right there.
 * The homepage arrived at the same rule one boundary at a time (9.1);
 * this page has ten sections in four runs, so it is written as a
 * default plus the four edges rather than as nine exceptions.
 *
 * .itv-panel is absent from the default because it is a PANEL: like
 * .itv-credentials, its air is margin rather than padding, since padding
 * would sit inside the navy and push its copy off its own edges. Both
 * pay half the rhythm and their neighbours pay the other half.
 *
 *   hero  intro  [panel]  split  workup       WHITE
 *   ---------------------------------------   ground change
 *   techniques                                CREAM
 *   ---------------------------------------   ground change
 *   situation  credentials  progress          WHITE
 *   ---------------------------------------   ground change
 *   not-for-everyone                          CREAM
 *   ---------------------------------------   ground change
 *   the closing CTA and footer                NAVY (footer.php)
 */
.itv-section { padding-block: calc(var(--section-pad) / 2); }
/* A BAND IS ALWAYS A GROUND CHANGE, so it always takes the full pad on
 * both sides - true on every page, so it lives on the class rather than
 * being re-stated per page. */
.itv-section.itv-band { padding-block: var(--section-pad); }
/* The remaining full pads are the page's own: the section BEFORE a band
 * and the section AFTER one each pay their half of that edge. */
.ca-workup    { padding-bottom: var(--section-pad); }
.ca-situation { padding-top:    var(--section-pad); }
.itv-prose    { padding-bottom: var(--section-pad); }

/* The hero opens the page, so it answers to the header rather than to
 * the rhythm. .site-main already pays 48; 24 more is the design's 72
 * from the header's bottom edge to the breadcrumb. */
.itv-hero { padding-top: var(--space-24); }

/* !! .itv-panel AND .itv-credentials DO NOT CARRY .itv-section.
 *
 * They are PANELS: their air is margin rather than padding, because
 * padding would sit inside the navy and push their own copy off their
 * own edges. Both set their own width, margin and padding below. The
 * inner container still needs dissolving, though, so they are named
 * here rather than inheriting it. */
.itv-panel > .wp-block-group__inner-container,
.itv-credentials > .wp-block-group__inner-container {
	display: contents;
}

/* RUNNING COPY IS CAPPED AT THE READING MEASURE, THE SECTION IS NOT.
 * Same rule as 9.1 and the same reason: --measure is 90 characters, and
 * at the full 1400 a paragraph runs about 150.
 *
 * !! RANGED LEFT, NOT CENTRED - and that is a difference from the
 * homepage, not an oversight. The homepage centres its copy because its
 * sections are short centred statements around diagrams. This is a
 * long-form service page: ten sections of running argument, and a
 * centred 960px column inside a 1400px page gives every paragraph a
 * different left edge to find. Left-ranged, the H1, every H2, every
 * paragraph, the cards and the rows all start on one line down the
 * page. It centres at <=767, per the standing mobile rule.
 *
 * !! p.wp-block-paragraph IS NOT ENOUGH ON ITS OWN, AND 9.1 SAYS IT IS.
 *
 * 9.1's note claims that scoping to p.wp-block-paragraph leaves the
 * button lines alone "because they carry their own classes". They do,
 * and it does not help: WordPress APPENDS wp-block-paragraph to
 * whatever class the block already has, so a stored
 * <p class="hp-btn-line"> renders as class="hp-btn-line
 * wp-block-paragraph" and matches this selector too. Checked against
 * the rendered homepage, not assumed.
 *
 * On the homepage that only ever centred a block around an inline-block
 * pill, which is invisible. Here it would have handed the amber buttons
 * a 32px bottom margin this page then tries to remove at a LOWER
 * specificity - so :not(.hp-btn-line) is the exclusion the comment in
 * 9.1 believes it already has. */
.itv-prose p.wp-block-paragraph:not(.hp-btn-line),
.itv-notfor p.wp-block-paragraph:not(.hp-btn-line) {
	max-width: var(--measure);
}

/* A section heading introduces several blocks, not the next sentence.
 * 16px made the first paragraph read as a subtitle hung off it. */
.ca-workup h2, .ca-techniques h2,
.ca-situation h2, .itv-prose h2, .itv-notfor h2 {
	margin: 0 0 var(--space-32);
	max-width: var(--measure);
}

/* GRID ITEMS MUST BE ALLOWED TO SHRINK. min-width:auto is the default,
 * which turns any fixed width inside a column into a floor the column
 * cannot go below - a horizontal scrollbar at phone size. The two
 * grids on this page both hold photographs at their design widths.
 * Targets the grandchildren, because the inner container above is
 * display:contents and the real items are one level further in. */
.itv-split > .wp-block-group__inner-container > *,
.itv-credentials > .wp-block-group__inner-container > * {
	min-width: 0;
}

/* ---- 10.1a  the two full-bleed bands ----
 *
 * These two sections are the page's only colour changes and they run
 * edge to edge, so they set NO max-width. The container is their kept
 * wrapper, above - which means their children cap themselves against a
 * true 960 exactly like every other section's do, with no gutter
 * arithmetic and no `margin-inline: 0` pinning anything to the window.
 */
.itv-band > .wp-block-group__inner-container > p.wp-block-paragraph:not(.hp-btn-line),
.itv-band > .wp-block-group__inner-container > h2,
.itv-band > .wp-block-group__inner-container > h3 {
	max-width: var(--measure);
}
/* ---- 10.1b  A SECTION WITH THE MONOGRAM BLEEDING OFF IT ----
 *
 * PROMOTED OUT OF .ca-techniques 2026-08-26, when a second page needed
 * the same thing on a WHITE ground. Everything here was page-scoped and
 * none of it was ever about that page.
 *
 * !! THE CLASS DOES ONE THING THAT IS EASY TO MISS: IT MAKES THE
 * SECTION FULL-BLEED AND HANDS THE CONTAINER TO THE WRAPPER, EXACTLY
 * LIKE .itv-band.
 *
 * That is not decoration, it is the whole mechanism. The monogram is
 * absolutely positioned with `right: 0`, which resolves against the
 * nearest positioned ancestor - this section. A normal .itv-section is
 * capped at 1448 and centred, so `right: 0` would be the CONTAINER's
 * right edge and overflow:hidden would clip the mark 236px short of
 * the window at 1920. The mark has to reach the viewport, so the
 * section has to. .ca-techniques got this for free by being a band;
 * a white section does not.
 *
 * isolation + z-index:-1 (from 9.6) keep it behind the copy without
 * escaping into a neighbouring section's stacking context. */
.itv-watermark {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	max-width: none;
	padding-inline: 0;
}
.itv-watermark > .wp-block-group__inner-container {
	display: block;
	width: 100%;
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	margin-inline: auto;
	padding-inline: var(--container-pad);
}

/* !! THE WATERMARK IS WRITTEN AT THE FULL PATH, NOT AS .itv-watermark
 * .hp-media--watermark, AND THAT IS THE POINT.
 *
 * It has to out-rank TWO rules at once: 9.6's own .hp-media--watermark
 * (0,1,0), which sets the homepage file's translate, and the band /
 * watermark centring rules above (0,2,0), which would otherwise cap it
 * at the container and pad it in from the edge - so it would not bleed
 * at all. This selector scores (0,3,0) and settles both without an
 * !important and without a race the next rule re-loses.
 *
 * Everything else it needs - position, z-index, top, right, margin,
 * radius, pointer-events - comes from 9.6 unchanged.
 *
 * !! THE WIDTH IS NOT HERE, IT IS PER PAGE. Each page's export is cut
 * to that page's own frame, so the visible rectangle is a different
 * number every time - 704 on Adjustments, 617 on Magic Hug. Sharing a
 * width would scale one of them. See 10.7 and 11.5.
 *
 * No translate, on either page. The homepage's file is the whole mark
 * and has to be pushed 28% off the edge to bleed; these are already cut
 * to the frame, so they sit flush against the right at their own size.
 *
 * It is absolutely positioned against the SECTION, not against the
 * wrapper it sits in - the wrapper is static - so `right: 0` from 9.6
 * is the viewport's right edge and the bleed is unaffected by the
 * container above. */
.itv-section > .wp-block-group__inner-container > .hp-media--watermark {
	max-width: none;
	translate: 0 -50%;
}

/* ---- 10.1c  A SPLIT INSIDE A BAND ----
 *
 * The two combinations exist on different pages - Adjustments puts its
 * split on white, Magic Hug puts one on cream - and they FIGHT, which
 * is worth stating because neither symptom points at the cause.
 *
 * .itv-split puts the grid on the SECTION and relies on the inner
 * container being display:contents, so the real children become the
 * grid items. .itv-band does the opposite: it KEEPS that wrapper and
 * makes it the container, because a band sets no max-width and its
 * children would otherwise align to the window (see 10.1a).
 *
 * Together, the band wins the wrapper and the section is left laying
 * out exactly ONE item - the copy column and the photograph stack, at
 * full width, and nothing in the CSS looks wrong.
 *
 * The fix is to put the grid where the container already is. The
 * section goes back to being a plain block; the wrapper keeps its cap,
 * its centring and its gutter and gains the columns. */
.itv-band.itv-split { display: block; }
.itv-band.itv-split > .wp-block-group__inner-container {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 430px);
	gap: var(--space-48);
	align-items: center;
}
/* Stacks with every other split. Restated at the wrapper for the same
 * reason as above - 10b's `.itv-split { grid-template-columns: 1fr }`
 * targets the section, which here is not the grid. */
@media (max-width: 991px) {
	.itv-band.itv-split > .wp-block-group__inner-container {
		grid-template-columns: 1fr;
	}
}

/* ---- 10.1d  A RUNNING-COPY LINK ON A NAVY GROUND ----
 *
 * !! THE KIT'S NAVY SURFACES COLOUR `h2` AND `p` AND HAVE NEVER
 * COLOURED `a`, SO AN INLINE LINK IN ONE COMES OUT BLACK.
 *
 * Found by Gerek on the built page 17, 2026-08-26: "If that is the
 * standard you want running your spine, SEE IF YOU ARE A CANDIDATE" is
 * an inline link inside .itv-credentials, and it rendered --ink
 * (#141413) with its underline, on the navy panel. Measured on the
 * live page, not reviewed: #141413 runs 2.72:1 against the gradient's
 * light end (#205e96) and 1.29:1 against its dark end (#102c46). White
 * is 6.77 and 14.28 on the same two.
 *
 * The cause is section 02's `a { color: var(--ink) }`. `color` does not
 * cascade into an anchor, because the anchor sets its own, so
 * `.itv-credentials p { color: var(--on-invert) }` never reached it. It
 * had gone unnoticed for four pages because page 17 is the FIRST to put
 * a running-copy link on a navy ground anywhere on this site - checked
 * on all five built pages, and every other anchor on navy is the amber
 * button, which is correctly --accent-contrast.
 *
 * !! HOVER IS THE HALF THAT IS WORSE, AND IT IS THE HALF A STATIC
 * REVIEW CANNOT SEE. Section 02's `a:not(.cta-button):hover { color:
 * var(--navy) }` takes an already-unreadable link to NAVY ON NAVY.
 * Both states are named below.
 *
 * !! NAMED, NOT BLANKET - which is 9.9's lesson, written after a
 * `.hp-proof a:not(.cta-button)` rule painted an amber button white at
 * 1.85:1 and got deleted rather than guarded a fourth time. "If
 * running-copy links ever return to this panel they need a rule that
 * names them, not one that catches every anchor." This is that rule:
 * it names a link inside a PARAGRAPH and excludes .hp-btn-line, so the
 * amber pill is untouchable by it rather than merely excluded from it.
 *
 * !! ALL THREE NAVY SURFACES, THOUGH ONLY ONE HAS A LINK TODAY. The
 * statement panel and the disqualifier rows are the same ground with
 * the same base rule underneath them and would fail the same way the
 * first time somebody puts a link in one - which 10.4 already flags as
 * a live possibility for .itv-panel. Closing the trap costs two
 * selectors; leaving it armed costs the next session a review cycle.
 *
 * The hover affordance is the underline getting heavier rather than a
 * colour change, for 1.2's reason: on navy the only other candidate is
 * --accent, and amber is a SURFACE colour on this site, not a text
 * colour. White on this gradient is about 5.7:1 and passes AA at both
 * states. */
.itv-credentials p:not(.hp-btn-line) > a,
.itv-panel p:not(.hp-btn-line) > a,
.itv-rows .itv-row a {
	color: var(--on-invert);
	text-decoration-line: underline;
}
.itv-credentials p:not(.hp-btn-line) > a:hover,
.itv-panel p:not(.hp-btn-line) > a:hover,
.itv-rows .itv-row a:hover {
	color: var(--on-invert);
	text-decoration-thickness: 2px;
}

/* ---- 10.2  PAGE HERO  (552:18) ----
 * Breadcrumb, H1, then one wide photograph across the content column.
 */
/* !! THERE IS NO VISIBLE BREADCRUMB, AND THE FRAME DRAWS ONE.
 *
 * Built as drawn, then removed on request 2026-08-26. The BreadcrumbList
 * JSON-LD at the foot of the page content STAYS - it is what search and
 * answer engines read, it was never the visible trail, and deleting it
 * would cost the page a rich result for no gain. If the trail ever comes
 * back it belongs in page-sections.php, derived from the title, rather
 * than hand-written into eighteen pages of post content. */
.itv-hero h1 { margin: 0 0 var(--space-40); max-width: var(--measure); }

/* !! THE PHOTOGRAPH TAKES THE FULL CONTENT COLUMN, NOT THE VIEWPORT.
 *
 * The design draws it 1140 wide inside a 1440 frame with the section's
 * own rounded corners - it is the width of the copy above it, not a
 * bleed. On this site that column is 1400, so the file (1140x235) is
 * carried about 23% past its own width. That is a deliberate trade: at
 * its natural 1140 it would sit centred and narrower than the H1 above
 * it, and a photograph that does not share the heading's left edge
 * reads as a mistake far more loudly than a soft photograph does.
 *
 * !! IT WAS NARROWED TO 1040 x 250 ON 2026-08-26 AND PUT BACK THE SAME
 * DAY. Gerek asked for narrower and proportionally taller, saw it, and
 * withdrew the note - full width is the call. Recorded so nobody
 * re-proposes it: the trade it needs is 86px of crop off EACH SIDE of
 * the file, because there is no source larger than 1140x235 and
 * object-fit:cover buys height by scaling and throwing away width. The
 * budget before it starts taking subject is about 60 on the left (dark
 * cabinet edge) and 90 on the right (empty wall to Dr. Michael's
 * shoulder), measured on the file.
 *
 * The aspect ratio is the file's own, so the crop is the design's. */
.itv-hero .hp-media { aspect-ratio: 1140 / 235; }

/* !! THE HERO IS ART-DIRECTED: TWO FILES, TWO SHAPES, ONE BREAKPOINT.
 *
 * The wide file is 1140x235 - 4.85:1 - and on a phone that is a 71px
 * strip under a three-line headline. It cannot be made taller by CSS:
 * cropping the sides to buy height takes 276px a side before it reads
 * as a photograph, and that takes Dr. Michael out of the frame. The
 * answer was a second file, not a second ruleset.
 *
 * 195 is 1000x1000, the same scene shot tight, and it is served ONLY
 * below 600 by a <source media> in the page content.
 *
 * !! THE 599 HERE AND THE 599 IN THE <source> ARE ONE DECISION IN TWO
 * PLACES. They must move together. If the CSS switches to square and
 * the <source> has not, object-fit:cover crops a 4.85:1 photograph into
 * a square box and shows a slice of somebody's shoulder; the other way
 * round letterboxes the square one. Nothing errors either way, which is
 * what makes it worth a warning rather than a comment.
 *
 * 599 and not 767 because that is where THIS SITE puts the phone - see
 * 1.8b: layout stacks at 767, type and the phone treatment start at
 * 599, and 600-991 is the tablet band, which keeps the wide file. */
@media (max-width: 599px) {
	.itv-hero .hp-media { aspect-ratio: 1 / 1; }
}

/* ---- 10.3  AEO INTRO  (552:21) ----
 * One dense credibility paragraph. It carries the licence number, the
 * years, and both review counts, and it is the block an answer engine
 * is most likely to lift, so it sits directly under the hero with
 * nothing between them.
 *
 * CENTRED, and it is the one running paragraph on the page that is.
 * The frame centres it, and it reads as the caption to the hero above
 * rather than as the start of the argument - which begins in the panel
 * below it, also centred. The two centred blocks are the page's
 * opening; everything from .itv-split down ranges left. */
.itv-intro p {
	max-width: var(--measure);
	margin: 0 auto;
	text-align: center;
}

/* ---- 10.4  THE STATEMENT PANEL  (553:18) ----
 *
 * "Dr. Michael Will Not Adjust a Spine He Has Not Seen" is a NAVY
 * PANEL WITH CENTRED WHITE COPY, not the plain white running-copy
 * section it was first built as.
 *
 * !! HOW THAT WAS MISSED, BECAUSE THE SAME MISTAKE IS ONE SAMPLE AWAY
 * ON EVERY REMAINING PAGE.
 *
 * The frame's section grounds were read by sampling the rendered PNG
 * down a single column at x=6 - the page gutter - which correctly
 * found the two cream bands and the navy footer. It cannot find a
 * PANEL, because every panel in this file is inset 150px and x=6 is
 * outside all of them. Re-sampled at x=203 the section reads navy from
 * y685 to y1086, which is exactly this frame.
 *
 * Sample INSIDE the inset as well as in the gutter. One column is not
 * a ground map.
 *
 * The air around it is margin, not padding, for the same reason as
 * .itv-credentials: padding would sit inside the navy. */
.itv-panel {
	width: calc(100% - (var(--container-pad) * 2));
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	margin: calc(var(--section-pad) / 2) auto;
	padding: var(--space-48);
	background: var(--gradient-navy-panel);
	color: var(--on-invert);
	border-radius: var(--radius-md);
	text-align: center;
}
.itv-panel h2 {
	max-width: var(--measure);
	margin: 0 auto var(--space-24);
	color: var(--on-invert);
}
.itv-panel p {
	max-width: var(--measure);
	margin-inline: auto;
	color: var(--on-invert);
}
.itv-panel p:last-child { margin-bottom: 0; }

/* THE FRAME GROUPS THE SIX LINES 2 / 1 / 2 / 1, and the grouping is the
 * argument: the two "here is what everyone else does" lines belong
 * together, and so do the two that answer them. A uniform paragraph
 * rhythm flattens that into six equal statements. The class closes the
 * gap under the first line of a pair; the standing 16px opens it
 * between pairs. */
.itv-panel__tight { margin-bottom: 0; }

/* The frame underlines one clause - "The structure underneath never
 * changes" - inside an otherwise unemphasised sentence. Set as drawn
 * rather than converted to another bold, because it is doing a
 * different job from the three bold lines: those are conclusions, this
 * is the pivot inside a sentence.
 *
 * An underline normally reads as a link, and here it does not, for one
 * checked reason: this panel contains NO anchors at all. If a link ever
 * lands in it, this needs re-deciding rather than guarding. */
.itv-panel__mark {
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

/* ---- 10.4a  RUNNING-COPY SECTION  (556:18) ----
 * "How You Will Know It Is Working" is the page's one plain section -
 * a heading over short paragraphs on white, nothing else. */
.itv-prose p:last-child { margin-bottom: 0; }

/* ---- 10.5  WHAT HAPPENS BEFORE YOUR FIRST ADJUSTMENT  (553:26) ----
 * Copy left, the X-ray review photograph right.
 *
 * The design's columns are 654 and 430 in a 1140 container. Only the
 * photograph's is fixed here: it is a real 430x420 file and should not
 * be carried past its own size, so the copy takes whatever is left -
 * 922 at a 1400 container, which is inside --measure and needs no
 * further cap. */
.itv-split {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 430px);
	gap: var(--space-48);
	align-items: center;
}
.itv-split__col > h2 { margin: 0 0 var(--space-32); }
.itv-split__col > p:last-child { margin-bottom: 0; }
.itv-split .hp-media { aspect-ratio: 430 / 420; }

/* ---- 10.6  THE WORKUP, STEP BY STEP  (564:22) ----
 *
 * FOUR of the homepage's process tiles, not three, and CREAM ON WHITE
 * rather than white on cream - this section sits on the page's white
 * ground and the design fills the tiles with --surface-alt to lift
 * them off it. Both differences are stated by the section; .hp-card
 * itself is untouched.
 *
 * --shadow-tile is the homepage's, and it exists because a tile on a
 * tinted ground needs a second, wider pass to register at all. On white
 * the plain --shadow-card is enough, and is what the design draws
 * (DROP_SHADOW #00000014, offset 0,2). */
.ca-workup .hp-cards {
	grid-template-columns: repeat(4, 1fr);
	margin-block: 0;
	text-align: left;
}
.ca-workup .hp-card {
	background: var(--surface-alt);
	box-shadow: var(--shadow-card);
}

/* STEP FOUR'S ICON IS TWO FILES AND A GLYPH.
 *
 * The design draws it as two spines with a short arrow between them
 * (619:451). The two spines exported as their own images - 22x100 and
 * 19x100, IDs 129 and 130 - and the arrow did not: it is a 16x5 vector
 * inside the group, which is a hairline nobody should be shipping as a
 * third image file. It is set as a text arrow in the accent instead,
 * which is the one place on this page amber is used as a mark.
 *
 * The wrapper is still .hp-media--icon, so the slot is the same 100px
 * tall auto-wide box the other three use and the four icons sit on one
 * baseline. */
.ca-icon-pair {
	display: flex;
	align-items: center;
	gap: var(--space-8);
}
.ca-icon-pair > img { width: auto; height: 100%; object-fit: contain; }
.ca-icon-pair__arrow {
	color: var(--accent);
	font-size: var(--type-h5);
	line-height: 1;
}

/* ---- 10.7  WHERE IT HURTS IS NOT WHERE WE STOP LOOKING  (554:18) ----
 * The first cream band. Copy, a sub-heading, then two technique cards.
 */
/* The monogram behind this section is brand furniture - it carries no
 * information and is aria-hidden - so it is allowed to run off the
 * right edge the way the design draws it.
 *
 * The file IS the bleed. 122 is 704x808, which is exactly the visible
 * rectangle of the design's 1020x1006 group once the frame clips it:
 * the artwork is already cut at the right edge. So it is pinned to the
 * right at its own size with no translate - the homepage's watermark
 * needs one only because its file is the whole mark.
 *
 * !! THE CONTAINING BLOCK MOVED TO .itv-watermark ON 2026-08-26 and
 * this section's markup carries that class now. position, isolation
 * and overflow used to be declared here; a second page needed the same
 * three on a white ground and copying them would have been the third
 * time the same mechanism was written out. Only the width is still a
 * page's own business - see 10.1b. */
.ca-techniques > .wp-block-group__inner-container > .hp-media--watermark {
	width: min(704px, 50vw);
}
/* h3, and it is the only one on the page: "Two Adjustments You Will Not
 * Find at Most Practices" introduces the two cards, not the section. */
.ca-techniques h3 { margin: var(--space-40) 0 var(--space-24); }

/* !! THE CARDS NEED AIR UNDER THEM, BECAUSE A PARAGRAPH FOLLOWS THEM.
 *
 * The frame ends this section with the two cards, so nothing there
 * needed a bottom margin. The 3.6 copy does not end there: it has one
 * more line - "Not every patient gets every technique. Your findings
 * decide." - which the frame simply does not place. Left at margin 0
 * that line sat flush against the cards, reading as a caption stuck to
 * the left-hand one rather than as the qualifier on both.
 *
 * 40 rather than the 16 a paragraph would take from another paragraph:
 * this is a change of gear back out of the cards, the same call 9.7
 * makes under the homepage's tiles. */
.ca-technique-cards {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: var(--space-32);
	margin: 0 0 var(--space-40);
	padding: 0;
	list-style: none;
}
.ca-technique {
	display: grid;
	gap: var(--space-12);
	align-content: start;
	padding: var(--space-32);
	background: var(--surface);
	border-radius: var(--radius);
	box-shadow: var(--shadow-card);
}
/* !! THE DESIGN DRAWS THIS LABEL IN AMBER AND IT IS NOT BUILT THAT WAY.
 *
 * #fbb03c on white measures about 1.9:1 against a 4.5:1 requirement.
 * Section 01 states the rule the whole theme runs on - amber is a
 * surface colour, never text on a light ground - and section 02 repeats
 * it for links. This is the same call, made once more rather than
 * quietly excepted: the clinical name is set in --ink-muted at the
 * eyebrow size, which is what .hp-eyebrow already does. */
.ca-technique__eyebrow {
	margin: 0;
	font-size: var(--type-eyebrow);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-eyebrow);
	text-transform: uppercase;
	color: var(--ink-muted);
}
.ca-technique__title {
	margin: 0;
	font-family: var(--font-heading);
	font-size: var(--type-h4);
	font-weight: var(--weight-bold);
}
.ca-technique__body { margin: 0; font-size: var(--type-small); color: var(--ink-muted); }
/* The pill sits at the bottom of the card and starts on the card's own
 * left edge. .hp-btn-line's own 24px margin-block would double the
 * grid's gap above it and hang 24 of nothing below it. */
.ca-technique .hp-btn-line {
	margin: var(--space-12) 0 0;
	justify-self: start;
}

/* ---- 10.8  FIND YOUR SITUATION  (555:18) ----
 *
 * THE ONE CENTRED SECTION ON THE PAGE, and deliberately so. It is an
 * interlude between two long arguments - a heading, one line, seven
 * chips and a door out to /conditions-treated/ - and it is the only
 * block here that is a set of objects rather than a column of copy. */
.ca-situation { text-align: center; }
.ca-situation h2 { margin: 0 0 var(--space-16); margin-inline: auto; }
.ca-situation p.wp-block-paragraph:not(.hp-btn-line) {
	max-width: var(--measure);
	margin: 0 auto var(--space-32);
}
/* !! CAPPED AT THE READING MEASURE, AND THE REASON IS THE WRAP.
 *
 * Seven chips of very different lengths in a centred flex row wrap
 * wherever the widths happen to land, and across the full 1400 column
 * they landed SIX AND ONE - a full row and one orphan, which reads as
 * a mistake rather than as a list.
 *
 * Measured at the 19px size, the chips run 127, 152, 232, 110, 208,
 * 310 and 390 wide with 12px gaps. Against 1352 the first six come to
 * 1199 and the seventh will not fit - hence 6 + 1. Against --measure
 * the first five come to 877, the sixth would take it past 960, so it
 * breaks there and the last two come to 712 on a line of their own:
 * FIVE AND TWO, which is what the frame draws (555:21 is 880 of 1440).
 *
 * It also puts the chips on the same width as the paragraph above
 * them, so the block has one edge rather than two.
 *
 * This is a wrap and not a grid, so it is width-dependent by nature.
 * The cap makes the DESKTOP case land where the design draws it and
 * lets the rest reflow; do not chase a count at every breakpoint. */
.ca-chips {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--space-12);
	max-width: var(--measure);
	margin: 0 auto var(--space-32);
	padding: 0;
	list-style: none;
}

/* !! NOT PILLS, AND NOT WHITE. BOTH OF THEM WERE SAYING "BUTTON".
 *
 * These are the seven answers to "is this me?". Nothing happens when
 * you press one - the button underneath is the only thing in this
 * section that goes anywhere - and they were drawn as white outlined
 * pills sitting directly above an amber pill, so they read as a row of
 * controls that had been disabled.
 *
 * !! ON THIS SITE THE PILL RADIUS *IS* THE AFFORDANCE. Every 999 on
 * the page is pressable: both CTAs, the header button, the page
 * buttons. Nothing else uses it. Spending it on a static label is what
 * created the expectation, so the radius comes down to --radius, which
 * is what the cards and tiles use and what nobody tries to press.
 *
 * The fill is the other half. White on white is "technically a fill,
 * visually nothing" - the same finding that put --fill-tint in section
 * 01 in the first place - so the chips take that tint and drop the
 * outline, which a filled shape does not need.
 *
 * !! THEY ARE LINKS NOW - 2026-08-26, WHEN PAGE 16 WAS BUILT.
 *
 * They were deliberately not links while page 16's pain states were a
 * plain <ul> with no ids: every chip would have had to point at
 * /conditions-treated/ and nothing else, and seven links to one
 * destination is worse than none - whichever you press you land in the
 * same place and none of them is your condition.
 *
 * Section 13.4 gives all six condition cards a stable id, so each chip
 * now points at the card for its own condition. SEVEN CHIPS, SIX CARDS:
 * "Low back pain" and "Sciatica" are one card on page 16 ("Low back
 * pain and sciatica") and both chips point there. The wording differs
 * on purpose - the chips are page 13's signed-off 3.6 copy and the
 * cards are page 16's - so the mapping is by MEANING and is listed in
 * HANDOFF.md rather than being inferable from the strings.
 *
 * !! THE PADDING MOVED OFF THE <li> AND ONTO THE <a>.
 *
 * An <li> cannot carry an href, so the anchor is a child - and a child
 * that only wraps the TEXT leaves the chip's 8/16 of padding dead to
 * the pointer. Same box, same 8/16, declared one level in, so the whole
 * chip is the target. Measured identical before and after.
 *
 * No underline: on this site underline is the affordance for a link in
 * running copy, and a filled box with a pointer cursor and a hover that
 * lifts is already saying it louder. The colour stays --ink, which is
 * what the chip was, rather than becoming a link colour. */
.ca-chip {
	background: var(--fill-tint);
	border-radius: var(--radius);
	font-size: var(--type-ui);
	color: var(--ink);
	transition: transform .18s ease, background-color .18s ease, box-shadow .18s ease;
}

.ca-chip > a {
	display: block;
	padding: var(--space-8) var(--space-16);
	color: inherit;
	text-decoration: none;
}

/* MOVEMENT ON HOVER, AND NOW IT IS A LINK.
 *
 * Asked for directly: they should answer when you point at one, without
 * clicking doing anything. So the chip lifts and grows very slightly
 * and its tint goes one step deeper - noticed, not pressable.
 *
 * !! THE CURSOR USED TO BE DOING THE HONEST WORK AND NOW THE HREF IS.
 *
 * While these were <li> the pointer stayed an arrow over them, which
 * was the whole argument for letting the movement be more generous than
 * the one 9.4 gives the stat row: it could say "this responded" without
 * saying "press this". Each chip now goes somewhere, so the pointer
 * turns and the promise is one the chip can keep. The movement is
 * unchanged - it was already the right amount for something pressable,
 * which is why nothing below had to move.
 *
 * 9.4's note still stands for the STATS, which are the page's proof and
 * should not invite a click they cannot honour.
 *
 * 3px and 1.03. Both deliberately small: at 1.06 the label visibly
 * re-rasterises mid-transition, and a chip is mostly text. */
.ca-chip:hover {
	transform: translateY(-3px) scale(1.03);
	background: var(--fill-tint-strong);
	box-shadow: var(--shadow-card);
}
@media (prefers-reduced-motion: reduce) {
	.ca-chip { transition: none; }
	.ca-chip:hover { transform: none; }
}

.ca-situation .hp-btn-line { margin: 0; }

/* ---- 10.9  THE CREDENTIALS BEHIND THE ADJUSTMENT  (555:37) ----
 *
 * The page's one navy panel: copy left, the cutout right, breaking the
 * panel's TOP edge. Same device as the homepage's Proof panel and the
 * same reason - the overhang is what gives the section depth - but
 * mirrored, and it is the top edge here rather than the bottom and the
 * left.
 *
 * The brief for this page said the portrait bleeds off the RIGHT. It
 * does not: measured on 562:30, the frame is 388x874 at x718 y-148 in a
 * 1140x726 panel, so it clears the top by 148 and stops 34px INSIDE the
 * right edge. Built as drawn. */
.itv-credentials {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 388px);
	gap: var(--space-48);
	align-items: center;
	width: calc(100% - (var(--container-pad) * 2));
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	/* The air around this panel is MARGIN, not padding: padding would
	 * sit inside the navy and push the panel's contents off its own
	 * edges. Half the rhythm, the same as every other same-ground
	 * boundary on this page - the section above pays the other half, and
	 * the portrait's 48px of overhang lands inside that 100 rather than
	 * on anybody's copy. */
	margin: calc(var(--section-pad) / 2) auto 0;
	padding: var(--space-48);
	background: linear-gradient(135deg, var(--navy-deep), var(--navy));
	color: var(--on-invert);
	border-radius: var(--radius-md);
}
.itv-credentials h2, .itv-credentials p { color: var(--on-invert); }
.itv-credentials h2 { margin: 0 0 var(--space-24); }
.itv-credentials__col > p:last-child { margin-bottom: 0; }
.itv-credentials__media { position: relative; align-self: stretch; }

/* !! THE CUTOUT IS TALLER THAN THE PANEL, ON PURPOSE.
 *
 * Absolutely positioned so the grid column still reserves its 388 and
 * the copy beside it is unaffected by the overflow. .itv-credentials
 * sets no overflow, so the bleed is allowed.
 *
 * The three 48s in the height: one cancels the panel's bottom padding,
 * one cancels the top, and the third is the overhang above the panel's
 * own edge.
 *
 * !! THE OVERHANG IS 48, NOT THE DESIGN'S 148, AND THAT IS A MEASURED
 * CEILING RATHER THAN A PREFERENCE.
 *
 * The overhang and the horizontal crop are the same number seen twice.
 * The file is 388x874; the slot is the column's 388 wide and
 * (panel content + 144) tall, so object-fit:cover scales by
 * slot-height / 874 and throws the surplus width away off BOTH SIDES.
 *
 *   overhang  48  ->  940 tall, scale 1.076, 14px off each side
 *   overhang 144  -> 1036 tall, scale 1.185, 36px off each side
 *
 * The file carries 13px of TRANSPARENT margin on each side, measured on
 * its alpha channel (opaque pixels start at x13 and end at x374), so
 * the first 13px of that crop is free and everything past it is his
 * arms. At 48 the crop is one pixel into him. At the design's 148 it is
 * 23px into each arm, and he reads as squeezed rather than as bleeding.
 *
 * Getting the design's proportion instead would mean widening the
 * column, and the column is what keeps the cutout off the copy. 48 is
 * also exactly what the homepage's Proof panel overhangs, so the two
 * panels on this site now agree.
 *
 * The file has 12px of transparent margin at the top and NONE at the
 * bottom - he stands on its own edge - which is why bottom:-48 puts him
 * on the panel's bottom edge rather than 12px through it. Changing the
 * image means re-measuring all three margins. */
.itv-portrait {
	position: absolute;
	right: 0;
	bottom: calc(var(--space-48) * -1);
	width: 100%;
	/* !! THE 940 CEILING IS WHAT STOPS HIM BEING STRETCHED INTO A STRIP.
	 *
	 * The first term is the overhang formula and it is right at the
	 * width this section was drawn for: 796 of panel content + 144 gives
	 * 940, which is the file's 874 at scale 1.076 - 15px off each side,
	 * and the file carries 13px of transparent margin, so the crop is
	 * one pixel into him.
	 *
	 * The formula alone is a trap, because the panel's height is the
	 * COPY'S height and the copy gets taller as the column narrows. At a
	 * 992 viewport the panel measured 1345 and the same expression asked
	 * for a 1393px-tall slot - scale 1.59, 116px off each arm. Measured
	 * on the real page; he was a vertical strip of torso.
	 *
	 * 940 is therefore a hard ceiling on the UPSCALE, not a layout
	 * value: past it he simply stops growing and stands on the panel's
	 * bottom edge with navy above his head, which is the correct way for
	 * a decorative bleed to give up. */
	height: min(calc(100% + (var(--space-48) * 3)), 940px);
	margin: 0;
	border-radius: 0;
	overflow: visible;
}
/* COVER, NOT CONTAIN. Contain fits the file inside the slot and pillars
 * it with 81px of navy above and below - which is not a subtle version
 * of the bleed, it is the bleed not happening. Anchored to the top so
 * that if the copy ever makes the panel tall enough to crop him
 * vertically as well, it takes his shins and not his head. */
.itv-portrait > img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center top;
}

/* The stat strip inside the panel is the homepage's stat, at the size
 * this panel can carry. .hp-stats__row is a four-column band 1400 wide;
 * here the same four sit in a 659px column, so the row becomes a
 * wrapping flex line and the type comes down one step. The stat itself
 * - icon spanning both text rows, figure over label - is untouched. */
/* !! TWO BY TWO, NOT A WRAPPING ROW.
 *
 * Written as a flex row first, which is what the design draws - four
 * stats on one line at 581px. It cannot be one line here: the design
 * sets the labels at 8.45px, and at a readable 15 the four measure 260,
 * 205, 245 and 235 against an 860px column. Flex-wrap then broke them
 * 3 + 1, with the fourth stranded on its own line under a full one.
 *
 * A two-column grid is the same four stats in the space that exists,
 * and the icons line up in two columns instead of four ragged
 * positions. Same lesson as the homepage's <=767 stat rule: 1fr eats
 * every spare pixel, so give the track an explicit share instead. */
.itv-credentials .hp-stats__row {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--space-16);
	max-width: none;
	margin: var(--space-24) 0;
	padding-inline: 0;
}
.itv-credentials .hp-stat { padding: var(--space-8); }
.itv-credentials .hp-stat__icon { width: 32px; height: 32px; }
.itv-credentials .hp-stat__figure { font-size: var(--type-h5); }
.itv-credentials .hp-btn-line { margin: 0; }

/* FULL CONTENT COLUMN, AS DRAWN.
 *
 * These were capped at --container-panel (1040, 74%) on the reasoning
 * that a scanned list wants something between the reading measure and
 * the container. The frame disagrees, and here the frame is right:
 * 556:28 is x150 w1140 in a 1440 frame, which IS the whole content
 * column. At 74% the rows read as a narrow stack floating in a wide
 * section rather than as the six doors they are.
 *
 * No max-width at all - the section's container is the cap. */
/* !! THE CLOSING LINE UNDER THESE CARDS HAD NO GAP AT ALL - FIXED
 * 2026-09-03, and it was FIVE pages, not the one it was reported on.
 *
 * The list had `margin-inline: 0` and nothing in the block direction, so
 * the <ul>'s own margin was reset to nothing and the paragraph that
 * follows it - which carries `margin-top: 0` - sat flush against the
 * last card. Measured on About: 0px after, 16 before. Every page that
 * closes this section with a sentence had it: About, Y-Strap, Magic
 * Hug, Conditions Treated and What to Expect. Pages 13 and 18 end at
 * the list and were never affected, which is why it survived review -
 * two of the seven look fine.
 *
 * Fixed on the COMPONENT rather than on .itv-notfor, because a card
 * stack butting into the next sentence is wrong wherever it happens,
 * and .itv-rows is the thing that owns its own air.
 *
 * 24 rather than the frame's 20. The frames draw 20 on both edges, and
 * 20 is only 4px more than the 16 that separates two ordinary
 * paragraphs - which is what made it read as tight rather than as a
 * gap. Gerek raised it on the rendered page, not in the frame.
 *
 * Symmetric on purpose. The 24 on top COLLAPSES with the preceding
 * paragraph's 16 - a grid container is still a block-level box in flow,
 * so adjacent-sibling margins collapse normally - which lands both
 * edges on 24 rather than 40 above and 24 below. */
.itv-rows {
	display: grid;
	gap: var(--space-12);
	margin-block: var(--space-24);
	margin-inline: 0;
	padding: 0;
	list-style: none;
}
.itv-row {
	padding: var(--space-16) var(--space-20);
	background: linear-gradient(135deg, var(--navy-deep), var(--navy));
	border-radius: var(--radius);
	box-shadow: var(--shadow-card);
	color: var(--on-invert);
}


/* ---- 10.10  THE CREDENTIAL LIST INSIDE A NAVY PANEL ----
 *
 * PROMOTED OUT OF .mh-creds 2026-08-26, when a second page needed the
 * same list in the same panel. Everything here was page-scoped and none
 * of it was ever about that page - the reasoning below is about the
 * COMPONENT (a scanned list of credentials on navy) and holds wherever
 * it lands.
 *
 * !! THE FRAMES WRITE THESE AS TWO PARAGRAPHS AND THE COPY IS A LIST.
 * The list is what is signed off, so the list is what is built - the
 * same call section 10 made on the condition chips, where the 3.6
 * wording won over the frame's.
 *
 * The frame's arrangement is kept, though, and only the LAST item
 * moves: "437 Google reviews at 5.0 stars..." sets as the paragraph
 * UNDER the stat strip, which is where both frames put it and where it
 * reads as the disclaimer it is rather than as a final credential
 * sitting directly above four icons saying the same numbers. Nothing is
 * reworded and nothing is dropped.
 *
 * The marker is an amber dot, not a bullet glyph and not amber text:
 * 1.2's standing rule is that amber is a SURFACE colour. As a filled
 * shape it is legal at any size; as a letterform it would be the 1.9:1
 * the frames draw elsewhere and this site does not. */
.itv-creds {
	display: grid;
	gap: var(--space-12);
	margin: 0 0 var(--space-24);
	padding: 0;
	list-style: none;
	/* !! COMPONENT SIZE, NOT BODY SIZE, AND IT IS LOAD-BEARING FOR THE
	 * PORTRAIT BESIDE IT.
	 *
	 * Measured on Magic Hug, where the finding was made: five
	 * credentials at the 22px body run 290px against a 728 column; at 19
	 * they run 231. That 59px is the difference between a panel of 969
	 * and one of 906, and the portrait's overhang is measured against
	 * the panel's HEIGHT - at 22px the man cleared the top edge by 30px
	 * instead of 124. Measured both ways before choosing.
	 *
	 * The same coupling exists on every page that uses this pair, which
	 * is the strongest argument for the two living together in the kit:
	 * change this size and you have moved a portrait on another page.
	 *
	 * It is also the right size on its own terms: --type-small is what
	 * every other scanned list inside a card or a panel on this site
	 * uses, and this is a credential list, not running argument. 12.1
	 * takes it to 20 across the tablet band with the rest of them. */
	font-size: var(--type-small);
	/* !! RANGED LEFT AT EVERY WIDTH, INCLUDING THE ONES WHERE THE REST
	 * OF THE PANEL CENTRES.
	 *
	 * 10b centres the whole credentials column from 1199 down, so the
	 * pill, the stats and the button sit under the stacked portrait.
	 * That is right for the paragraphs and wrong for a list: the marker
	 * dot below is positioned at each item's LEFT edge and the item is
	 * full width, so centring the sentence leaves every dot stranded out
	 * on the left with a ragged gap after it. Measured centred at
	 * 600-1199 before this line existed.
	 *
	 * Same call .itv-rows makes at <=599 and for the same reason - a
	 * scanned list of sentences needs one left edge. */
	text-align: left;
}
.itv-creds > li {
	position: relative;
	padding-left: var(--space-24);
	color: var(--on-invert);
}
.itv-creds > li::before {
	content: "";
	position: absolute;
	top: 0.6em;
	left: 0;
	width: var(--space-8);
	height: var(--space-8);
	border-radius: var(--radius-pill);
	background: var(--accent);
}

/* ==================================================================
 * 10b  CHIROPRACTIC ADJUSTMENTS - RESPONSIVE
 *
 * !! NO DESIGN EXISTS BELOW 1440px AND NONE IS COMING. Everything here
 * is a judgement call, written down so it can be argued with.
 *
 * 992-1199  Unchanged except the four workup tiles, which go two-up.
 *
 * 600-991   The two side-by-side sections STACK - the split and the
 *           credentials panel. A photograph beside a paragraph stops
 *           being a layout and starts being two cramped columns
 *           somewhere around here. The technique cards go two -> one.
 *           The workup tiles STAY two-up: this is the tablet band, and
 *           a tablet has room for two.
 *
 * <600      One column everywhere and MOST THINGS CENTRE, which is the
 *           standing rule for this site on a phone. The rows and the
 *           card bodies stay ranged left: they are running sentences,
 *           and a centred sentence of two lines has no edge to read
 *           back to.
 *
 * !! NOTHING IS KEYED TO 767 HERE. Layout stacks at 767 and type drops
 * at 599, and every remaining decision on this page turned out to be a
 * type decision. See the note where the <=767 block would have been.
 *
 * The 600-991 tablet band in section 12 sets the component text sizes
 * for this page as well - see the end of 12.1. That block is LAST in
 * the file on purpose and wins on order.
 * ================================================================== */

@media (max-width: 1199px) {
	/* Four equal columns of running text stop working before the layout
	 * does. At 1100 each tile is 245 wide and "Re-Examination &
	 * Measured Change" sets on three lines inside it. */
	.ca-workup .hp-cards { grid-template-columns: repeat(2, 1fr); }

	/* !! THE CREDENTIALS PANEL STACKS AT 1199, NOT AT 991, AND IT IS
	 * THE ONLY THING ON THIS PAGE THAT DOES.
	 *
	 * A panel pays for its own inset twice - 48 of padding on each side
	 * - before it gives the two columns anything, and the portrait's
	 * column is a fixed 388. What is left for the copy:
	 *
	 *   1400 viewport   860   the width it was designed at
	 *   1200            620
	 *   1100            520
	 *    992            412   about 39 characters a line
	 *
	 * 412 is not a column, it is a gutter with words in it, and it is
	 * what made the panel 1345px tall at 992 - which is the same fault
	 * as the portrait ceiling above, seen from the other end. Stacked,
	 * the copy gets the whole 1055 and the panel comes back to a normal
	 * height.
	 *
	 * .itv-split is NOT moved with it: it has no padding to pay and its
	 * copy still has 466 at 992, so it holds to 991 the same way the
	 * homepage's hero does. */
	.itv-credentials { grid-template-columns: 1fr; margin-top: calc(var(--section-pad) / 2); }
	/* !! THE CUTOUT STANDS ON THE PANEL'S BOTTOM EDGE, NOT ON ITS
	 * PADDING.
	 *
	 * Stacked, the portrait is the panel's last item, and the panel's
	 * own bottom padding left 48px of navy under his feet - measured 24
	 * at phone widths, where the padding drops. The file is a cutout
	 * cropped at mid-shin with NO transparent margin at the bottom, so
	 * that gap turned the crop into a mistake: his legs stopped in
	 * mid-air. Flush with the edge, the same crop reads as him standing
	 * behind the panel, which is what it does at desktop.
	 *
	 * The negative margin cancels the padding exactly, so the two are a
	 * pair - change the panel's padding at a breakpoint and this has to
	 * move with it. See the <=599 block, where both are 24.
	 *
	 * overflow:hidden is safe HERE and nowhere above 1199: it is what
	 * clips his feet to the panel's 24px corner radius, and the only
	 * thing that ever bleeds OUT of this panel is the desktop overhang,
	 * which does not exist in the stacked layout. */
	.itv-credentials { overflow: hidden; }
	.itv-credentials__media {
		position: static;
		display: flex;
		justify-content: center;
		align-items: flex-end;
		margin-bottom: calc(var(--space-48) * -1);
	}
	/* 100%, not 70%. The panel's own 48px inset is already taking 96 out
	 * of the width before the portrait gets any, so 70% of what is left
	 * came to 172px at a 390 viewport - a thumbnail. 260 is still the
	 * ceiling; the percentage only has to stop him bursting the panel. */
	.itv-portrait {
		position: static;
		width: min(260px, 100%);
		height: auto;
		bottom: auto;
		right: auto;
	}
	.itv-portrait > img { width: 100%; height: auto; }

	/* The pill, the stats and the button centre with the portrait above
	 * them. .hp-eyebrow--pill already carries align-self:center, which
	 * does nothing here because .itv-credentials__col is not a flex
	 * column - margin-inline is what actually moves it. */
	.itv-credentials__col { text-align: center; }
	.itv-credentials__col .hp-eyebrow--pill { margin-inline: auto; }
	.itv-credentials .hp-stat { justify-self: center; }
	.itv-credentials .hp-btn-line { text-align: center; }
}

@media (max-width: 991px) {
	.itv-split { grid-template-columns: 1fr; }
	.itv-split .hp-media { width: min(430px, 100%); margin-inline: auto; }
	.ca-technique-cards { grid-template-columns: 1fr; }

	/* !! THE CUTOUT COMES BACK INTO THE FLOW ONE BREAKPOINT UP, AT 1199.
	 *
	 * Worth knowing where, because the failure mode is silent: its
	 * desktop position is absolute against a column that only exists in
	 * the two-column layout, so at one column that column collapses to
	 * nothing at the top of the panel and the portrait is not small or
	 * clipped - it is ABSENT. The homepage's Proof panel had exactly
	 * this fault and it was found by scrolling a real viewport, not by
	 * reading the CSS. The rules that undo it live with the stack, in
	 * the <=1199 block above. */
}

/* !! THERE IS NO <=767 BLOCK FOR THIS PAGE, AND ITS ABSENCE IS A
 * DECISION.
 *
 * 767 is where LAYOUT stacks, and everything on this page that has to
 * stack has already done so at 991. What is left - the centring, the
 * last column drop - is a TYPE decision, and type on this site changes
 * at 599, not 767. See the note at <=767 in 1.8b: an iPad Mini in
 * portrait is 744 CSS pixels, so anything keyed to 767 dresses it as a
 * phone. Putting the centring here would have ranged a tablet's copy
 * down the middle of a 700px column and dropped its four workup tiles
 * into a single stack. Both belong to the block below. */

@media (max-width: 599px) {
	.ca-workup .hp-cards { grid-template-columns: 1fr; }

	/* MOST THINGS CENTRE, which is the standing rule for this site on a
	 * phone. The headings and the objects - not the paragraphs, which
	 * keep their left edge below.
	 *
	 * .itv-intro and .itv-panel are absent because they are centred at
	 * EVERY width already - listing them here would say nothing and
	 * would suggest the centring is a mobile decision, which for those
	 * two it is not. */
	.itv-hero, .itv-split, .ca-workup,
	.ca-techniques, .ca-situation, .itv-prose, .itv-notfor {
		text-align: center;
	}
	.itv-hero h1, .ca-workup h2, .ca-techniques h2,
	.ca-techniques h3, .itv-prose h2, .itv-notfor h2,
	.itv-split__col > h2 {
		margin-inline: auto;
	}

	/* !! THE RUNNING COPY DOES NOT CENTRE WITH THEM.
	 *
	 * A centred paragraph of four lines gives the eye a new left edge to
	 * find on every line. Headings and objects centre because they are
	 * one or two lines and read as objects; paragraphs are read, and
	 * they stay ranged left inside a centred column. */
	/* The panel's copy joins them: its longest line runs to eight lines
	 * inside a 246px phone panel, and eight centred lines is a ragged
	 * column with no edge to read back to. Its H2 stays centred. */
	.itv-panel p,
	.itv-prose p.wp-block-paragraph:not(.hp-btn-line),
	.ca-techniques > .wp-block-group__inner-container > p.wp-block-paragraph:not(.hp-btn-line),
	.itv-notfor > .wp-block-group__inner-container > p.wp-block-paragraph:not(.hp-btn-line),
	.itv-split__col > p {
		margin-inline: auto;
		text-align: left;
	}
	/* The tiles, the cards and the rows keep their own left edge too -
	 * each is a box of its own, and centring the sentence inside it just
	 * makes six ragged boxes. */
	.ca-workup .hp-card, .ca-technique { text-align: left; }
	/* 48 of inset on each side is a third of a 390px screen. Same call
	 * .hp-panel makes at <=991. */
	.itv-panel, .itv-credentials { padding: var(--space-24); }
	/* Paired with the padding above - see the note at <=1199. */
	.itv-credentials__media { margin-bottom: calc(var(--space-24) * -1); }
	.ca-technique .hp-btn-line, .ca-workup .hp-media--icon { justify-self: start; }
	.itv-rows { text-align: left; margin-inline: auto; }

	/* !! THE STAT STRIP GOES TO ONE COLUMN, AND THE MEASUREMENT THAT
	 * FORCED IT IS THE PANEL'S PADDING, NOT THE SCREEN.
	 *
	 * The panel keeps its 48px inset at every width, so at a 390
	 * viewport its CONTENT is 246px - and two columns of that is 115
	 * each. Take the 32px icon and its 16px gutter and the longest
	 * label, "Google rating - 437 reviews", has 67px to set 26
	 * characters in: measured 127px tall, four lines, against the 70 it
	 * takes on a desktop. At 599 it is 219 per column and still two
	 * lines.
	 *
	 * One column gives it the whole 246 and the strip comes back to one
	 * line per label. The two-up grid is a desktop and tablet layout;
	 * this is where it stops.
	 *
	 * The first track is matched to the icon as well: the stats band's
	 * own <=767 rule gives .hp-stat a 44px first column, which is 12px
	 * wider than this panel's 32px icon and pushes the text further
	 * still. */
	.itv-credentials .hp-stats__row { grid-template-columns: 1fr; }
	.itv-credentials .hp-stat { grid-template-columns: 32px 1fr; }
}


/* ==================================================================
 * 11  MAGIC HUG CHIROPRACTOR
 *
 * VISUAL SOURCE: Figma 569:28 "(unverified) Magic Hug Chiropractor",
 * nine section frames, 569:97 down to 569:69. Every colour, size and
 * space below comes from a token in section 01, or from a measurement
 * of an image file that is written down beside it.
 *
 * !! EIGHT OF THE NINE SECTIONS ARE NOT IN THIS BLOCK, AND THAT IS THE
 * POINT OF THE .itv-* PROMOTION.
 *
 * The hero, the AEO intro, the navy statement panel, the running-copy
 * section, the split, the disqualifier rows and the credentials panel
 * are all section 10's shared kit, used unchanged. What is here is
 * only what THIS page does differently, plus the two pieces the kit
 * did not have:
 *
 *   .mh-careplan   copy, a bleeding monogram and a row of three
 *                  cross-link buttons, on white
 *   .itv-creds     the credential list inside the navy panel (kit)
 *   .mh-statement  the navy statement panel, which this frame insets
 *                  and the other page's does not
 *
 * Where a shared component needs to look different here, the change is
 * made BY THIS PAGE - `.mh-what .mh-what__icon { ... }` - and never by
 * adding a modifier to the component. Same rule section 10 follows.
 *
 * !! DO NOT REUSE .ca-*. Those are what is left of Chiropractic
 * Adjustments after the promotion and they belong to that page.
 *
 * NO DESIGN EXISTS BELOW 1440px. Section 11b is the theme's own work.
 * ================================================================== */

/* ---- 11.1  the rhythm ----
 *
 * Same rule as 10.1: 100px BETWEEN sections, paid 50 + 50 on a shared
 * ground and 100 + 100 at a colour change. .itv-section and .itv-band
 * do the default; these are this page's four edges.
 *
 *   hero  intro  [panel]  prose             WHITE
 *   ------------------------------------    ground change
 *   what the magic hug actually is          CREAM
 *   ------------------------------------    ground change
 *   one adjustment, not your care plan      WHITE
 *   ------------------------------------    ground change
 *   who the magic hug is not for            CREAM
 *   ------------------------------------    ground change
 *   [credentials]                           WHITE
 *   ------------------------------------    ground change
 *   the closing CTA and footer              NAVY (footer.php)
 *
 * .itv-prose already pays the pad above the first cream band - it is
 * on the class in 10.1, not on .ca-*, so this page inherits it.
 */
.mh-careplan { padding-block: var(--section-pad); }

/* !! THE TOP MARGIN CLEARS THE PORTRAIT'S HEAD ONTO WHITE. IT IS NOT
 * RHYTHM PADDING AND IT MUST NOT BE TRIMMED BACK TO 100.
 *
 * This was margin-top: 0, on the reasoning that the cream band above
 * already pays the full 100 of a colour change. That reasoning was
 * simply wrong, and it is worth being precise about why: a band's
 * padding is INSIDE the cream. It buys air between the band's own copy
 * and the band's own edge - it buys nothing outside it. So the panel's
 * top edge sat exactly ON the cream's bottom edge, with no white
 * between them at all, where 570:70 draws 100px of it.
 *
 * That was invisible while the portrait stayed inside the panel. Once
 * 11.8 gave him a real overhang it stopped being invisible: his head
 * cleared the panel by 124 and landed 124px INSIDE THE CREAM BAND,
 * which reads as the photograph belonging to the section above.
 * Measured on the rendered page - cream is rgb(245,244,241), the page
 * is rgb(255,255,255), head top y=4253 against a cream edge at y=4377.
 * Found by Gerek on the preview.
 *
 * !! SO THE RHYTHM IS MEASURED TO THE HEAD, NOT TO THE PANEL'S EDGE.
 * The head is the topmost visible thing in this section, so it is what
 * the eye reads the boundary against:
 *
 *   100   the standing colour-change rhythm, which was missing
 * + 148   the overhang, which is 124 at 1400 and 150 at 1920
 *   ---
 *   248
 *
 *   @1400   248 - 124 = 124px of white above his head
 *   @1920   248 - 150 =  98px
 *
 * !! IT IS TIED TO 11.8'S 480px TRACK. The overhang is a function of
 * the portrait's width - change the track and this has to be
 * re-measured, or his head goes back into the cream. Below 1400 the
 * panel stacks and there is no overhang at all, so 11b hands this back
 * to the plain 100.
 *
 * The bottom stays at the standing 100, which is what the frame draws
 * from the panel's bottom edge to the navy. Asymmetric on purpose: the
 * top has a head in it and the bottom is a clean edge. */
.mh-credentials {
	margin-block: calc((var(--section-pad) * 2) + var(--space-48)) var(--section-pad);
}

/* ---- 11.2  PAGE HERO  (569:97) ----
 *
 * !! THE HERO IS ART-DIRECTED: TWO FILES, TWO SHAPES, ONE BREAKPOINT -
 * and every rule it needs is section 10's already.
 *
 * 139 is 1140x235 - 4.85:1 - which is a 71px strip under a three-line
 * headline on a 390px phone. It cannot be made taller by CSS: buying
 * height with object-fit:cover crops 276px off EACH SIDE of the file
 * before it reads as a photograph, and that takes the subject out of
 * the frame. The answer is a second file, not a second ruleset - the
 * same conclusion 10.2 reached for the other page's hero.
 *
 * The page shipped 2026-08-26 with the theme's dashed empty-slot frame
 * here, because the square crop did not exist yet. It arrived the same
 * day and the placeholder came out; nothing is left in this section
 * but this note.
 *
 * !! MEDIA 218 IS NAMED chiropractic-adjustments-Hero-Occipital-Lift-
 * Photo-Mobile AND IT BELONGS TO THIS PAGE. The slot name is right and
 * the page prefix is wrong - it came out of a batch export. Checked
 * against the frame before wiring: it is 1000x1000 and it is the same
 * scene as 139, and page 13's own mobile hero is a different file
 * (217, Treatment-Room). Do not "fix" this page by pointing it at
 * anything with a magic-hug- prefix; there is no such file.
 *
 * !! THE 599 IN 10.2's MEDIA QUERY AND THE 599 IN THE <source> ARE ONE
 * DECISION IN TWO PLACES. They must move together. If the CSS switches
 * to square and the <source> has not, object-fit:cover crops a 4.85:1
 * photograph into a square box and shows a slice of a shoulder; the
 * other way round letterboxes the square one. Neither errors, which is
 * what makes it worth a warning rather than a comment.
 *
 * 10.2 also gives .hp-media its `> picture > img` branch, which is not
 * optional once a slot art-directs: a <picture> puts an element between
 * the figure and the <img>, so a `.hp-media > img` selector silently
 * stops matching and the photograph reverts to its intrinsic size. */

/* ---- 11.2a  THE STATEMENT PANEL IS INSET HERE  (569:104) ----
 *
 * !! THE TWO PAGES GENUINELY DIFFER AND IT IS NOT AN OVERSIGHT ON
 * EITHER. 553:18 on Adjustments runs the full content column, x150 to
 * x1290 of a 1440 frame. 569:104 here is x260 w920 - inset 110 a side
 * from that same column. Same component, one page states an inset.
 *
 * Built full-width first, on the reasoning that two sibling service
 * pages with different-width navy panels would read as an
 * inconsistency. Gerek looked at it against the frame and overruled
 * that, correctly: the frames disagree, so the pages should.
 *
 * The width and its derivation are in 1.13 with the token.
 *
 * !! max-width ONLY. .itv-panel's `width: calc(100% - pad*2)` still
 * does the narrow-viewport work, so this cannot be written as a width
 * or the panel stops shrinking below 1130 and bursts the gutter. */
.mh-statement { max-width: var(--container-statement); }

/* ---- 11.3  WHAT THE MAGIC HUG ACTUALLY IS  (570:31) ----
 *
 * A split on a cream band - the combination 10.1c exists for. Copy and
 * an icon left, the hand-placement photograph right.
 *
 * The photograph's column is 420, not the kit's 430: 138 is a real
 * 420x460 file and there is no reason to carry it past its own size.
 *
 * !! .itv-band IS IN THE SELECTOR BECAUSE THE PAGE CLASS ALONE LOSES.
 * 10.1c writes the combination as `.itv-band.itv-split > .wp-block-
 * group__inner-container`, which is (0,3,0). A bare `.mh-what >
 * .wp-block-group__inner-container` is (0,2,0) and the kit's 430 kept
 * winning - measured 430 on the rendered page, not assumed. Adding the
 * band back makes it (0,3,0) and later in the file.
 *
 * The photograph's own rule is (0,3,0) for a different reason: it has
 * to beat 10b's `.itv-split .hp-media` at every width, AND it must not
 * reach the ICON, which is also a .hp-media inside this section. */
.mh-what.itv-band > .wp-block-group__inner-container {
	grid-template-columns: minmax(0, 1fr) minmax(0, 420px);
}
.mh-what > .wp-block-group__inner-container > .hp-media {
	aspect-ratio: 420 / 460;
}

/* !! THE ICON HAS TO STATE ITS OWN SIZE, BECAUSE .itv-split .hp-media
 * REACHES IT.
 *
 * 145 is 76x100 and sits above the H2, which is a slot .hp-media--icon
 * already describes - except that at <=991 the kit widens every
 * .hp-media inside a split to min(430px, 100%) and centres it, and
 * that selector scores (0,2,0) against .hp-media--icon's (0,1,0). The
 * icon would have gone from 76 wide to 430 and jumped to the middle of
 * the column at exactly the width where nobody looks twice.
 *
 * (0,2,0) and later in the file, so it holds at every width. The file
 * is the size. */
.mh-what .mh-what__icon {
	width: 76px;
	height: 100px;
	margin: 0 0 var(--space-24);
}

/* ---- 11.4  ONE ADJUSTMENT, NOT YOUR CARE PLAN  (570:40) ----
 *
 * !! THIS SECTION KEEPS ITS WRAPPER, so its own children are one level
 * further in than a plain .itv-section's. That is .itv-watermark's
 * doing and it is deliberate - see 10.1b. It also means 10.1's
 * heading and measure rules, which are written against .ca-* and
 * .itv-prose, do not reach here. Restated at the full path rather than
 * bolted onto that list, so the two pages stay separable. */
.mh-careplan > .wp-block-group__inner-container > h2 {
	margin: 0 0 var(--space-32);
	max-width: var(--measure);
}
.mh-careplan > .wp-block-group__inner-container > p.wp-block-paragraph:not(.hp-btn-line) {
	max-width: var(--measure);
}

/* ---- 11.5  the monogram on a WHITE ground ----
 *
 * 136 is 617x579, which is exactly the visible rectangle of the
 * design's 770x760 group once the 1440 frame clips it: the group
 * starts at x823, so 1440 - 823 = 617 is all there ever was. Already
 * cut to the edge, so no translate - that comes from 10.1b.
 *
 * 43vw is 617 at the design's own 1440, so the mark holds the same
 * share of the screen as it narrows and stops growing past its own
 * size. 9.6 hides it below 992 outright: at tablet widths it scales
 * down onto the copy and runs a pale band through the heading, and it
 * carries no information worth that. */
/* !! RE-EXPORTED AND MOVED SIDES 2026-09-04 - media 136 -> 329.
 *
 * The width tracks the FILE, per 10.1b's note that each page's mark is cut
 * to its own frame: the new export is 494 wide where the old was 617, so
 * the ceiling moves with it or the mark is scaled up to fill a box it no
 * longer fits.
 *
 * !! AND IT IS ON THE LEFT NOW. 9.6 anchors every watermark `right: 0`,
 * which is where the other four pages want theirs. This frame does not:
 * 667:6 sits at x-277 in a 1440 section, so it bleeds off the LEFT edge.
 * `right: auto` has to be stated as well as `left: 0` - leaving the right
 * anchor in place with a left one pins the box between them and stretches
 * it across the section.
 *
 * The translate flips with it. 9.6 pushes 28% off the right edge to bleed;
 * the same push to the left is negative. 10.1b re-states it as `0 -50%`
 * for the pages whose exports are pre-cropped - this one is pre-cropped
 * too, so the horizontal stays 0 and only the anchor changes. */
.mh-careplan > .wp-block-group__inner-container > .hp-media--watermark {
	width: min(494px, 43vw);
	right: auto;
	left: 0;
}

/* !! AND THE COPY HAS TO STEP ASIDE, OR THE MOVE MAKES THINGS WORSE.
 *
 * On the right the mark had the section's empty half to itself - the copy
 * is capped at the reading measure and never reached it. On the left it
 * lands directly behind the words, and a watermark behind a paragraph is
 * just noise: rendered that way first, and the mark was unreadable and the
 * copy looked smudged.
 *
 * 570:40 indents its copy to x410 in a 1440 frame - 28.5% - which clears
 * the bulk of the mark and lets the heading overlap only its faint right
 * edge. A percentage, not a pixel value, so the copy holds that
 * relationship as the section widens.
 *
 * Desktop only. Below 992 the section centres its copy (11b) and the
 * watermark is hidden outright at 767, so an indent there would push the
 * text off its own centre for no reason. */
@media (min-width: 992px) {
	.mh-careplan > .wp-block-group__inner-container > h2,
	.mh-careplan > .wp-block-group__inner-container > p,
	.mh-careplan > .wp-block-group__inner-container > .mh-btn-row {
		margin-left: 28.5%;
	}
}

/* ---- 11.6  the three cross-link buttons  (570:46) ----
 *
 * The frame draws the section's own three links a second time as a row
 * of amber pills. They are not new copy - each one already exists as
 * an inline link in the paragraphs above it - so this is a navigation
 * strip, not an addition.
 *
 * .hp-btn-line paints the anchor and carries 24px of margin-block for
 * the case it was written for, a single button on its own line. Three
 * in a row need a row instead: the margin comes off and the 12px gap
 * is the frame's own (the buttons sit at x0, 266 and 464 at widths
 * 254, 186 and 209).
 *
 * 40 above rather than the 24 a paragraph would take: this is a change
 * of gear out of the running copy, the same call 9.7 makes under the
 * homepage's tiles and 10.7 makes under the technique cards. */
.mh-btn-row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-12);
	margin-top: var(--space-40);
}
.mh-btn-row .hp-btn-line { margin: 0; }

/* ---- 11.8  the portrait  (660:7) ----
 *
 * Copy left, the cutout right, breaking the panel's TOP edge - the
 * same device as Adjustments and as the homepage's Proof panel.
 *
 * !! THIS DOES NOT USE 10.9'S HEIGHT FORMULA, AND THAT IS THE WHOLE
 * POINT. Read this before touching any number in here.
 *
 * 10.9 sizes the cutout from the PANEL - `height: min(100% + 144,
 * ceiling)` - and lets object-fit:cover throw away whatever width that
 * leaves over. It works on Adjustments because that file is 874 tall
 * against a 796 panel: the man is taller than the box he stands in.
 *
 * 137 is 352x755 and this panel's copy is 812. He is SHORTER than the
 * box. Built 10.9's way, the ceiling binds before the overhang starts
 * and he stands buried inside the panel with navy above his head -
 * shipped that way once, and Gerek sent back the frame beside it.
 *
 * !! SO THE CUTOUT IS SIZED FROM ITS OWN COLUMN, NOT FROM THE PANEL,
 * AND aspect-ratio IS WHAT MAKES THAT SAFE.
 *
 *   width          the column, a flat 480
 *   aspect-ratio   352 / 755, the file's own
 *   height         auto, so it follows the width
 *
 * With the slot at the file's exact ratio, object-fit:cover has no
 * surplus to throw away and the crop is ZERO at every width - which is
 * what frees the height to be whatever the composition needs. The old
 * arithmetic traded crop against overhang and could not have both; this
 * trades SHARPNESS against overhang instead, and sharpness is the
 * cheaper of the two on a flat studio cutout.
 *
 * 480 is the number the frame asks for. 660:7 draws him 352x755 in a
 * 1140 panel; ours is 1352, so everything scales by 1.186 and he wants
 * 417x895. Measured, 417 lands the overhang short - our copy is 22px
 * where the frame's is 18 - so the track is 480, which puts the
 * proportion exactly on the frame:
 *
 *                    man        panel   man/panel   overhang
 *   frame 660:7      352x755      664     1.138        91
 *   built @1400      480x1030     906     1.137       124
 *   built @1920      480x1030     880     1.171       150
 *
 * The cost is a 36% upscale on a 352px file. Accepted deliberately: he
 * is a decorative cutout with no fine texture, and every alternative
 * measured worse - at 352 there is no overhang at all, and buying one
 * through 10.9's formula cropped 47 to 57px into each arm against 12px
 * of transparent margin. A ~352x960 export removes the upscale
 * outright and nothing else has to change; it is in HANDOFF.md.
 *
 * !! THE COLUMN IS FLAT, NOT A PERCENTAGE, and 60fr/40fr was tried
 * first. A proportional track shrinks the man at exactly the widths
 * where the panel is getting taller - the copy narrows, the copy grows,
 * the panel grows and he is smaller than he was. Measured at 1200 it
 * put him 167px BELOW the panel's top edge. Flat, he is the same
 * photograph at every width and only the copy column moves.
 *
 * The file has 11px of transparent margin at the top and NONE at the
 * bottom - he stands on its own edge - which is why 10.9's bottom:-48
 * puts his feet on the panel's bottom edge rather than 11px through it.
 * Measured 0 at every width. */
.mh-credentials {
	grid-template-columns: minmax(0, 1fr) minmax(0, 480px);
}
.mh-credentials .itv-portrait {
	width: 100%;
	height: auto;
	aspect-ratio: 352 / 755;
}


/* ==================================================================
 * 11b  MAGIC HUG CHIROPRACTOR - RESPONSIVE
 *
 * !! NO DESIGN EXISTS BELOW 1440px AND NONE IS COMING. Everything here
 * is a judgement call, written down so it can be argued with.
 *
 * Most of this page's stacking is section 10b's, inherited through the
 * shared classes. What is here is the FOUR PLACES where a rule in this
 * section out-scores one of 10b's and has to hand it back, plus the
 * phone centring for the one section 10b cannot name.
 *
 * That is the whole hazard of a page block that comes after a
 * responsive block: a plain `.mh-credentials { ... }` at (0,1,0) sits
 * LATER in the file than `@media (max-width: 1199px) { .itv-credentials
 * { ... } }`, which is also (0,1,0) - and a media query adds no
 * specificity. The desktop rule wins inside the media query. Every
 * property this page restates at desktop has to be restated here too.
 * ================================================================== */

/* !! THIS PANEL STACKS AT 1399, NOT AT 10b'S 1199, AND THE WHOLE
 * <=1199 TREATMENT IS RESTATED HERE TO MOVE WITH IT.
 *
 * 10.9 stacks at 1199 because that is where ITS copy column stops being
 * a column - 388 of image plus 96 of panel padding leaves 620 at 1200.
 * This page spends 480 on the image, so the same arithmetic runs out
 * 200px earlier. Measured, two-column, with the flat 480 track:
 *
 *   1400   copy 728   panel  906   overhang  124   the design
 *   1300   copy 628   panel 1006   overhang   24
 *   1200   copy 528   panel 1125   overhang  -96   he is buried
 *
 * The composition needs the width it was drawn at and there is no
 * number that rescues 1200 - the copy column and the overhang are the
 * same quantity pulling in opposite directions. Above 1400 it holds;
 * below it, stacked is the correct layout and it already exists.
 *
 * !! EVERY LINE BELOW IS 10b's <=1199 BLOCK, SCOPED TO THIS PAGE AND
 * MOVED UP 200px. It is duplication and it is deliberate: widening
 * 10b's own query to 1399 would restack Chiropractic Adjustments,
 * which is a page that has already been reviewed.
 *
 * !! AND IT CANNOT BE HALF-DONE. The portrait's desktop position is
 * absolute against a column that only exists in the two-column layout.
 * Stack the grid without the six rules under it and that column
 * collapses to nothing at the top of the panel and the portrait is not
 * small or clipped - it is ABSENT. Same silent failure 10b warns about
 * at <=991. */
@media (max-width: 1399px) {
	.mh-credentials {
		/* Back to the standing colour-change rhythm. 11.1's 248 is the
		 * overhang's clearance and stacked there is no overhang - left
		 * in place it would be 148px of empty white above a panel whose
		 * portrait is at the BOTTOM. */
		margin-top: var(--section-pad);
		grid-template-columns: 1fr;
		/* overflow:hidden is safe HERE and nowhere above 1399: it is
		 * what clips his feet to the panel's 24px corner, and the only
		 * thing that bleeds OUT of this panel is the desktop overhang,
		 * which the stacked layout does not have. */
		overflow: hidden;
	}
	.mh-credentials .itv-credentials__media {
		position: static;
		display: flex;
		justify-content: center;
		align-items: flex-end;
		/* Paired with the panel's padding - cancels it exactly so he
		 * stands on the panel's edge rather than 48px above it. Change
		 * the padding at a breakpoint and this moves with it. */
		margin-bottom: calc(var(--space-48) * -1);
	}
	/* aspect-ratio comes off with the absolute positioning: stacked, the
	 * img inside is width:100% height:auto and sets the box itself. */
	.mh-credentials .itv-portrait {
		position: static;
		width: min(260px, 100%);
		height: auto;
		aspect-ratio: auto;
		bottom: auto;
		right: auto;
	}
	.mh-credentials .itv-portrait > img { width: 100%; height: auto; }
	/* !! THE STACKED COPY COLUMN IS CAPPED AT THE READING MEASURE, AND
	 * 10.9 DOES NOT DO THIS.
	 *
	 * It did not have to: it stacks at 1199, so its copy never gets more
	 * than 1055. This one stacks 200px higher, and at 1399 the column is
	 * 1255 wide - about 114 characters a line against --measure's 90.
	 * The cap is the only thing this page adds to the stacked layout
	 * rather than inherits, and it is here because the breakpoint moved,
	 * not because the panel is different. */
	.mh-credentials .itv-credentials__col {
		max-width: var(--measure);
		margin-inline: auto;
		text-align: center;
	}
	.mh-credentials .itv-credentials__col .hp-eyebrow--pill { margin-inline: auto; }
	.mh-credentials .hp-stat { justify-self: center; }
	.mh-credentials .hp-btn-line { text-align: center; }
}

@media (max-width: 991px) {
	/* !! THE SPLIT STACKS HERE, AND 10.1c ALREADY SAYS SO - IT JUST
	 * LOSES ON ORDER.
	 *
	 * 10.1c's stacking rule and 11.3's column rule are BOTH (0,3,0), and
	 * 11.3 is later in the file, so it won inside 10.1c's media query
	 * and the two columns held all the way down to 600. Measured 475px
	 * + 420px at a 991 viewport. Exactly the hazard this section's own
	 * header describes, walked into once while writing it. */
	.mh-what.itv-band > .wp-block-group__inner-container {
		grid-template-columns: 1fr;
	}
	/* 10b widens a split's photograph to min(430px, 100%) when the two
	 * columns stack. 420 is this file's own width - see 11.3 - and
	 * 11.3's (0,3,0) selector already beats that rule, so the width has
	 * to come back here or the photograph stays at its desktop column
	 * width in a one-column layout. */
	.mh-what > .wp-block-group__inner-container > .hp-media {
		width: min(420px, 100%);
		margin-inline: auto;
	}
}

@media (max-width: 599px) {
	/* MOST THINGS CENTRE, the standing rule for this site on a phone.
	 * 10b's list already covers this page's hero, split, prose and
	 * disqualifier rows through the shared classes; .mh-careplan is the
	 * one section it cannot name. */
	.mh-careplan { text-align: center; }
	.mh-careplan > .wp-block-group__inner-container > h2 { margin-inline: auto; }
	/* The running copy does NOT centre with them - four centred lines
	 * give the eye a new left edge on every line. Same reasoning and
	 * the same exclusion as 10b's. */
	.mh-careplan > .wp-block-group__inner-container > p.wp-block-paragraph:not(.hp-btn-line) {
		margin-inline: auto;
		text-align: left;
	}
	/* The button row centres as an object. flex-start is what a stacked
	 * row of pills defaults to, and against centred copy above it that
	 * reads as three buttons pushed to one side. */
	.mh-btn-row { justify-content: center; }
	/* The icon centres over its heading with everything else - 11.3
	 * pinned its margin-inline to 0 to keep 10b from centring it at
	 * tablet, so it has to be given back here. */
	.mh-what .mh-what__icon { margin-inline: auto; }

	/* !! THE NEGATIVE MARGIN AND THE PANEL'S PADDING ARE A PAIR, AND
	 * THIS IS THE BREAKPOINT WHERE THE PADDING MOVES.
	 *
	 * 10b drops .itv-credentials to 24px of padding at <=599, and its
	 * own rule drops the media column's negative margin to match. This
	 * page's <=1399 block restates that margin at -48 and scores
	 * (0,2,0), so it was still winning down here - measured, his feet
	 * hung 24px THROUGH the panel's bottom edge at 599 and 390.
	 *
	 * The file is a cutout cropped at mid-shin with no transparent
	 * margin at the bottom, so it has to land on the edge exactly:
	 * 24 short and his legs stop in mid-air, 24 long and they run out
	 * of the panel. Measured 0 at both widths after. */
	.mh-credentials .itv-credentials__media {
		margin-bottom: calc(var(--space-24) * -1);
	}
}

/* ==================================================================
 * 13  CONDITIONS TREATED
 *
 * VISUAL SOURCE: Figma 566:22 "(unverified) Conditions Treated", eight
 * section frames, 566:91 down to 566:63. Every colour, size and space
 * below comes from a token in section 01.
 *
 * !! SIX OF THE SEVEN BUILT SECTIONS ARE THE KIT, UNCHANGED. The hero,
 * the AEO intro, the statement panel, the split, the credentials panel
 * and the disqualifier rows are all .itv-* components; this section is
 * mostly the four places where the frame asks for something the kit
 * does not already do. What is genuinely new is the condition-card
 * grid in 13.4.
 *
 * THE GROUND MAP, sampled off the 1440x4956 render at nine columns -
 * four in the gutters and five inside the insets:
 *
 *   white   0-1019      hero, AEO intro, and the navy panel inset in it
 *   cream   1019-2181   The Pain States
 *   white   2181-3755   Your X-rays Decide, The Credentials (navy inset)
 *   cream   3755-4400   Who This Practice Is Not For
 *   navy    4400+       Final CTA + footer (footer.php)
 *
 * !! 566:98 IS A NAVY PANEL AND A GUTTER SAMPLE CANNOT SEE IT - which
 * is 10.4's warning, and it caught this file out a second time in a new
 * costume. This panel is inset 260 a side, not the file's usual 150, so
 * a sampler with inset columns at 150 and 1240 has TWO of its five
 * probes outside this one panel. Reading the mode of the row then let
 * those two outvote the three that were inside it, and the section
 * classified as white. Sampled at x255-1178 it reads navy from y685 to
 * y954, which is exactly the frame.
 *
 * Sample INSIDE each inset at ITS OWN inset, and do not let a mode
 * across columns stand in for a per-column reading.
 *
 * !! THE TEMPLATE MATTERS. Page 16 carries _wp_page_template =
 * page-sections.php. Without it page.php wraps the content in
 * .container and no band can reach the edge.
 *
 * NO DESIGN EXISTS BELOW 1440px. Section 13b is the theme's own work.
 * ================================================================== */

/* ---- 13.1  the rhythm ----
 *
 * 10.1's default gives every .itv-section half the rhythm and every
 * .itv-band the full pad on both sides. What is left is this page's own
 * four colour-change edges, where the section on the WHITE side pays
 * its other half:
 *
 *   hero  intro  [statement panel]          WHITE
 *   ---------------------------------------  ground change
 *   the pain states                          CREAM
 *   ---------------------------------------  ground change
 *   your x-rays  [credentials panel]         WHITE
 *   ---------------------------------------  ground change
 *   who this practice is not for             CREAM
 *   ---------------------------------------  ground change
 *   the closing CTA and footer               NAVY (footer.php)
 *
 * The two panels are absent from .itv-section's default for the reason
 * in 10.1 - their air is margin, because padding would sit inside the
 * navy - so each states both of its own edges below. */
.ct-statement   { margin-bottom: var(--section-pad); }
.ct-xray        { padding-top:   var(--section-pad); }

/* ---- 13.2  page hero  (566:91) ----
 *
 * Nothing but the kit. 10.2 already sets the 1140/235 slot and the
 * <=599 switch to a square, and the page content carries the matching
 * <source media="(max-width: 599px)"> - one decision in two places,
 * see the warning in 10.2 before moving either.
 *
 * The wide file is media 203 and the square is media 216, both of this
 * page's own prefix. 216 is 1000x1002 rather than a true square; the
 * slot is 1/1 and object-fit:cover takes one pixel off the bottom.
 *
 * !! THE FRAME DRAWS A THREE-LEVEL BREADCRUMB (566:92) AND IT IS NOT
 * BUILT. The decision was made on page 13 and restated on page 15: the
 * BreadcrumbList JSON-LD is what engines read, it was never the visible
 * trail, and if a trail comes back it belongs in page-sections.php,
 * derived from the title, rather than hand-written into eighteen pages
 * of post content. The JSON-LD at the foot of this page STAYS. */

/* ---- 13.3  the statement panel  (566:98) ----
 *
 * !! THE SAME INSET AS MAGIC HUG'S, TO THE PIXEL. 566:98 is x260 w920
 * inside a content column that runs x150 to x1290 - which is 569:104's
 * geometry exactly, so --container-statement (1130) is already the
 * right number and 1.13 already carries its derivation. Page 13's
 * 553:18 is the odd one out at full width.
 *
 * !! max-width ONLY, per 11.2a. .itv-panel's `width: calc(100% - pad*2)`
 * does the narrow-viewport work; written as a width the panel would
 * stop shrinking below 1130 and burst the gutter.
 *
 * THE FOUR PARAGRAPHS RUN CONTIGUOUS, which is unusual enough to say
 * out loud. 661:200 stacks them at y0/27/81/108 against heights of
 * 27/54/27/27 - no gap anywhere. So all four take .itv-panel__tight
 * rather than the 2/1/2/1 grouping page 13's panel uses; here the four
 * lines are one spoken paragraph, not two pairs and two answers.
 *
 * The frame bolds the last clause of the first and fourth lines and
 * underlines the last clause of the third. The underline is
 * .itv-panel__mark, and 10.4's condition for it still holds: this panel
 * contains no anchors at all, so an underline cannot be misread as one.
 */
.ct-statement { max-width: var(--container-statement); }

/* ---- 13.4  THE PAIN STATES  (567:25) ----
 *
 * Six condition cards, three across and two down, on the cream band.
 *
 * !! THIS IS .hp-cards / .hp-card WITH THREE DIFFERENCES STATED, NOT A
 * NEW CARD. The brief for this page expected a new component; measured
 * against 567:29, the homepage's tile is already almost exactly right.
 * The frame's card is 364 wide with 28 of padding and 12 between its
 * rows; .hp-card is 32 and 12, and .hp-cards is already repeat(3, 1fr).
 * Its --shadow-tile is the correct shadow here for the reason 10.6
 * gives - these cards sit on a TINTED ground, which is the case that
 * shadow exists for.
 *
 * What differs is centring, the icon slot, and the margin. That is it.
 *
 * Reusing it also inherits the tablet band: 12.1 already takes
 * .hp-card__body to 20px across 600-991, so the card body does not need
 * a rule of its own the way .ca-technique__body and .itv-creds did. */
.ct-cards {
	/* !! 24 ABOVE AND 24 BELOW, WHERE .hp-cards GIVES 64 AND 48.
	 *
	 * The homepage's uneven 64/48 is deliberate there and belongs to
	 * that section - "more above, where the reader is arriving at them".
	 * This frame is symmetric and tight: 567:27 ends at y177 and the
	 * grid starts at y201, the grid ends at y945 and 567:53 starts at
	 * y969. 24 both ends.
	 *
	 * The top is written as the frame's 24 MINUS the 16px bottom margin
	 * the lead paragraph already contributes to the flow, because that
	 * margin is real and Figma has no equivalent of it. 8 + 16 = the
	 * frame's 24. */
	margin-block: calc(var(--space-24) - var(--space-16)) var(--space-24);
	/* CENTRED, WHERE .hp-cards RANGES LEFT. The frame centres the icon,
	 * the title and the body inside every card, and the cards keep that
	 * at every width - unlike 10b's tiles, which go back to a left edge
	 * on a phone. The difference is the body length: those run to four
	 * and five lines, these are one and two, and a centred pair of lines
	 * under a centred illustration reads as a label rather than as a
	 * ragged column. */
	text-align: center;
}

/* THE ILLUSTRATION IS 200px TALL - THE FILE'S OWN HEIGHT, NOT THE
 * FRAME'S PROPORTION.
 *
 * All six exports are exactly 200 tall at their own widths (94, 169,
 * 162, 123, 171 and 142), and those widths match 567:29-49's icon
 * frames to the pixel, so the frame draws them at native size in a 364
 * card. Our card is 445 at a 1400 container, and the site's standing
 * mapping - the design's 1140 content column IS our 1400 - would scale
 * them to 246 to hold the proportion.
 *
 * They are left at 200. The hero photograph takes its 23% upscale in
 * 10.2 because it has to share the H1's left edge and a photograph that
 * misses that edge reads as a mistake; nothing aligns to these, so the
 * upscale would buy proportion and pay for it in softness on line art,
 * which is where softness shows most. Same reasoning as .hp-media--icon
 * itself: height is the binding dimension, the width follows the
 * artwork, and the file is the size.
 *
 * The consequence, stated so it can be argued with: the icon occupies
 * 45% of the card's width here against the frame's 55%, so the cards
 * are airier than drawn. */
.ct-condition__icon {
	height: 200px;
	justify-self: center;
}

/* ---- 13.5  YOUR X-RAYS DECIDE  (568:25) ----
 *
 * The kit's split on WHITE - so unlike 11.3 this one is not inside a
 * band, the inner container is still display:contents, and the grid is
 * still on the section. .ct-xray scores the same (0,1,0) as .itv-split
 * and wins on order; no .itv-band needs to be in the selector the way
 * 10.1c forces it to be on the other page.
 *
 * The photograph's column is 420, not the kit's 430: media 215 is a
 * real 420x460 file and there is no reason to carry it past its own
 * size. Same call 11.3 made on media 138.
 *
 * The frame's columns are 664 and 420 with a 56 gutter in a 1140
 * container. Only the photograph's is fixed here; the copy takes what
 * is left, which is 932 at a 1400 container - inside --measure and
 * needing no further cap. */
.ct-xray { grid-template-columns: minmax(0, 1fr) minmax(0, 420px); }
/* (0,2,0) and later in the file than 10b's `.itv-split .hp-media`, so
 * it holds at every width. This section has only ONE .hp-media in it -
 * no icon, unlike 11.3 - so the plain descendant selector is safe. */
.ct-xray .hp-media { aspect-ratio: 420 / 460; }

/* THE THREE CROSS-LINKS ARE THE SECTION'S OWN LINKS A SECOND TIME.
 * "Chiropractic adjustments", "See the Y-Strap" and "See what to
 * expect" each already exist as an inline link in the paragraphs above
 * them, so this is a navigation strip and not new copy - the same call
 * 11.6 made, and the frame draws all three (568:35/37/39 at 254, 186
 * and 210 wide, wrapping 2 + 1).
 *
 * .hp-btn-line carries 24px of margin-block for the single-button case
 * it was written for; a row needs the 12px gap instead. 40 above rather
 * than a paragraph's 24, because this is a change of gear out of the
 * running copy. */
.ct-btn-row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-12);
	margin-top: var(--space-40);
}
.ct-btn-row .hp-btn-line { margin: 0; }

/* ---- 13.6  THE CREDENTIALS BEHIND THE FINDINGS  (568:42) ----
 *
 * !! THE MIRROR OF .itv-credentials: THE PORTRAIT IS ON THE LEFT, BLEEDS
 * OFF THE LEFT EDGE, AND BREAKS THE PANEL'S TOP EDGE WITH HIS HEAD.
 *
 * !! THE SOURCE ORDER IS NOT MIRRORED WITH IT, ON PURPOSE. The copy
 * column stays FIRST in the markup and the media column second, exactly
 * as on the other two pages, and only the grid columns are swapped.
 * That is what lets the stacked treatment in 13b apply here almost
 * unchanged: it pulls the portrait back into the flow and hangs him on
 * the panel's bottom edge, all of which assumes he comes AFTER the copy.
 * Mirroring the markup would have put him above the heading on every
 * tablet and phone, and the failure would have been silent.
 *
 * !! THIS SECTION IS THE RESULT OF A REVIEW, AND THE FIRST ATTEMPT IS
 * WORTH KNOWING ABOUT because its reasoning was sound and its conclusion
 * was wrong.
 *
 * It shipped with NO overhang and no bleed, on the measured argument
 * that the frame's proportion was unreachable: media 202 is 596x720, an
 * aspect of 0.83 where media 137 and 132 are 0.47 and 0.44, so a cutout
 * this wide has to get very WIDE to get tall, and the width comes out of
 * the copy column, which makes the panel taller, which means he has to
 * be taller again. Measured at eight column widths, the upscale needed
 * never dropped below 1.32 and rose with the column.
 *
 * **That arithmetic was done on his BOUNDING BOX, and the bounding box
 * is not the man.** Measured on the alpha channel in twelve bands, the
 * opaque silhouette is:
 *
 *      file y     left   right   width
 *        0- 60     212     354     143   <- head
 *       60-240     184     365     182   <- head and shoulders
 *      240-360     102     488     412
 *      420-540      73     523     450   <- folded arms, the widest point
 *      660-720     110     442     333
 *
 * **The part that overhangs the panel's top edge is 182px wide, not
 * 596.** He is only full width across his folded arms, which sit beside
 * the middle of the copy, and the head that has to clear the buttons
 * above is a quarter of the file. Sizing the layout off the box charged
 * the whole 596 at every height and priced the overhang out of reach.
 *
 * Three changes make it fit, and Gerek named all three from the frame:
 * the stat strip goes to one line, the button gets air under it, and the
 * man moves left and up.
 */

/* THE RHYTHM IS MEASURED TO THE TOP OF HIS HEAD, NOT TO THE PANEL.
 *
 * The section above this one is the X-ray split, and the last thing in
 * its copy column is the three-button row - directly above the portrait,
 * because both sit on the left. .itv-section already pays 50 below the
 * buttons, so a plain half-rhythm here would leave 100 from the buttons
 * to the panel's edge and only 44 to his head.
 *
 * 50 + 56 puts the standing 100 between the buttons and his hair, which
 * is what the rhythm is for. The frame is tighter than this - 568:34
 * ends at y2903 and 659:4 starts at y2935, a 32px gap - and Gerek asked
 * for the room, so the page takes the room.
 *
 * The bottom stays at the full pad: it is a colour change into the cream
 * band and it is a clean edge with nothing breaking out of it. */
.ct-credentials { margin-block: calc((var(--section-pad) / 2) + 56px) var(--section-pad); }

/* !! THE MEDIA COLUMN IS A FLAT 530, AND IT IS SET BY HIS ELBOWS.
 *
 * The column does not have to clear his whole box - it has to clear the
 * widest band of him that has copy beside it, which is file y480-540,
 * his folded arms at x523. At the 1.37 upscale below that is 717px from
 * the image's left edge, and the image's left edge is fixed against the
 * panel, so the column is the only term left:
 *
 *   his right edge   panel + 24 (bleed) is his left, + 717   =  panel + 693
 *   copy's left      panel + 48 + 530 + 48                   =  panel + 626
 *   clearance        35px at a 1400 viewport, and it grows from there
 *
 * 1400 IS THE WORST CASE AND THAT IS WHY THE PANEL STACKS AT 1399. Above
 * 1400 the panel content goes 1256 -> 1352 and stops, so the copy column
 * only ever gets WIDER, the panel only ever gets SHORTER, and he only
 * ever gets smaller. Below 1400 all three run the other way and the
 * clearance is gone within one breakpoint - see 13b.
 *
 * The copy keeps 678 of the panel's 1256 at 1400 and 774 above 1496.
 * Page 15's is 728. **The H2 sets on two lines below a 778px column**,
 * which is where the panel's height steps by 47 - measured, and the
 * reason 530 is not wider. */
.ct-credentials { grid-template-columns: minmax(0, 530px) minmax(0, 1fr); }
.ct-credentials > .wp-block-group__inner-container > .itv-credentials__col   { grid-column: 2; grid-row: 1; }
.ct-credentials > .wp-block-group__inner-container > .itv-credentials__media { grid-column: 1; grid-row: 1; }

/* !! THE STAT STRIP IS FOUR ACROSS HERE, WHERE THE KIT STACKS IT 2x2.
 *
 * 10.9 makes it a two-column grid because at page 13's 860px column the
 * four stats measure 260, 205, 245 and 235 and cannot share a line. That
 * is still true of their LABELS - three of the four wrap to two or three
 * lines in a 152px track - but the strip is 111px tall in one row against
 * 156 in two, and **45px of panel height is 45px of overhang** on a page
 * where the overhang is the whole argument. The frame draws them on one
 * line (661:206, four stats in 581px) and Gerek asked for the same.
 *
 * The labels are left alone rather than shortened to fit: they are the
 * same four strings the homepage and pages 13 and 15 carry, and a stat
 * that reads differently here would be a worse inconsistency than a
 * wrapped label. */
.ct-credentials .hp-stats__row { grid-template-columns: repeat(4, minmax(0, 1fr)); }

/* !! THE STAT'S OWN SIDE PADDING COMES OFF, AND IT BUYS TWO LINES BACK.
 *
 * Four across means each stat gets a 182px track instead of 381, and
 * 10.9's 8px of side padding plus the kit's 16px column gap were eating
 * 32 of it before the label saw any. At 118px of text width three of the
 * four labels wrapped to three lines; at 138 only one still wraps to
 * two, and "Yelp reviews, 5.0" comes back to a single line.
 *
 * Measured: the strip goes 111 -> 91 and the panel 881 -> 860, so this
 * is 21 more pixels of overhang as well as an evener row. The padding
 * was only ever there to give the hover ground a little room, and the
 * hover ground is the one thing on this site nobody has ever seen -
 * see the note in 12 about the compositor. */
.ct-credentials .hp-stat {
	padding-inline: 0;
	column-gap: var(--space-12);
}

/* AIR UNDER THE BUTTON. 10.9 zeroes .hp-btn-line's margin inside this
 * panel, which is right when the button is the last thing in it. Here a
 * closing line follows it and the two were touching. */
.ct-credentials .hp-btn-line { margin-bottom: var(--space-24); }

/* !! HE IS NOT CROPPED AT ALL - the slot takes the FILE'S aspect ratio,
 * so the inherited object-fit:cover has no surplus to throw away. That
 * is what frees the height to be whatever the composition needs, and it
 * is 11.8's mechanism rather than 10.9's: 10.9 buys height with cover
 * and pays in crop, which on a 0.83-aspect file would take 90px off each
 * side rather than 14.
 *
 * THE HEIGHT IS THE PANEL PLUS THE OVERHANG:
 *
 *   100%    the copy column's height, which IS the panel's content
 *           height - .itv-credentials__media is align-self:stretch
 *   + 48    cancels the panel's bottom padding
 *   + 48    cancels its top padding
 *   + 56    the overhang, which is 659:4's own: the frame sits at y-56
 *           in a 663.6 panel and its bottom lands at 664, so he stands
 *           ON the bottom edge and clears the top by 56
 *
 * At a 1400 viewport that is a 984px slot from a 720px file - a 1.37
 * upscale, against page 15's 1.36 for the same gesture. **A ~815x984
 * export of the same pose removes it outright and nothing here moves.**
 * The 1000px ceiling is a backstop, not a layout value: the panel is
 * tallest at exactly 1400 and shorter at every width above it, so the
 * formula never reaches the ceiling in the range where this layout
 * exists. It is there so that a future copy change cannot silently make
 * him enormous. */
.ct-credentials .itv-portrait {
	width: auto;
	height: min(calc(100% + (var(--space-48) * 2) + 56px), 1000px);
	aspect-ratio: 596 / 720;
	/* !! ANCHORED LEFT, NOT RIGHT, AND THE OFFSET IS THE FILE'S OWN
	 * TRANSPARENT MARGIN.
	 *
	 * The kit anchors `right: 0` because its cutout bleeds off the
	 * RIGHT. Mirrored, a right anchor would also make the bleed a
	 * function of the image's height, which is what the fixed geometry
	 * here exists to avoid.
	 *
	 * Media 202 carries 73px of transparent margin on the left and 72 on
	 * the right - opaque pixels run x73..x523 - so the FRAME and the MAN
	 * have different left edges and it is the man's that has to land.
	 * 73/596 is 12.248%, and a percentage in `translate` resolves against
	 * the ELEMENT's own width, so the placement survives any change to
	 * the size. The 48 after it is the panel's padding, which he has to
	 * come back out of to reach the panel's edge.
	 *
	 * !! THE BLEED IS CLAMPED BY THE WINDOW, NOT BY THE PANEL, and that
	 * is the one number the frame cannot give us. 568:42 sits 150px from
	 * its page's edge and ours sits 24, so the frame's 41px bleed puts
	 * 17px of his arm off the LEFT OF THE WINDOW at 1400 - measured,
	 * subject left edge at x=-17.
	 *
	 * So the bleed is the room that actually exists: 24 (the page gutter)
	 * up to 1496, where the container is flush with the window and 24 is
	 * all there is, then the real margin as the panel centres, capped at
	 * 50 - which is 568:42's own 41 carried to our wider container.
	 * Nothing is ever clipped, because below 1496 the panel is always
	 * exactly 24 from the edge and 24 is the clamp's floor. */
	--ct-bleed: clamp(
		var(--container-pad),
		calc((100vw - (var(--container) + (var(--container-pad) * 2))) / 2),
		50px
	);
	left: 0;
	right: auto;
	translate: calc(-12.248% - var(--space-48) - var(--ct-bleed)) 0;
	/* Decorative, and its box overhangs its column into the gutter - all
	 * of it transparent. Nothing should be able to lose a click to a
	 * picture of a man. */
	pointer-events: none;
}

/* ---- 13.7  WHO THIS PRACTICE IS NOT FOR  (568:51) ----
 *
 * Five navy rows on the cream band, and not one line of CSS: .itv-band,
 * .itv-notfor, .itv-rows and .itv-row are the whole section, and 10.1's
 * heading and measure rules already name .itv-notfor.
 *
 * The frame's rows drop the leading label - "The walk-in patient." -
 * that the database copy carries. The copy is what is signed off, so
 * the rows are the copy, the same call 11.7 and section 10 both made.
 */

/* ==================================================================
 * 13b  CONDITIONS TREATED - RESPONSIVE
 *
 * !! NO DESIGN EXISTS BELOW 1440px AND NONE IS COMING. Everything here
 * is a judgement call, written down so it can be argued with.
 *
 * 1200-1399  Unchanged. The condition grid is still three across: at a
 *            1200 viewport each card is 362, which is the frame's own
 *            364.
 *
 * 992-1199   The cards go three -> two, with the credentials panel that
 *            10b already stacks at this width. 362 is the floor for a
 *            card holding a 200px illustration over a two-line title.
 *
 * 600-991    The split stacks (10b). The cards STAY two-up - this is
 *            the tablet band and a tablet has room for two, the same
 *            call 10b makes for the workup tiles.
 *
 * <600       One column everywhere and most things centre. The CARDS
 *            were already centred and stay that way; see 13.4.
 *
 * Nothing here is keyed to 767, for the reason 10b gives: everything on
 * this page that has to stack has done so by 991, and what is left is a
 * type decision, which on this site happens at 599.
 * ================================================================== */

@media (max-width: 1399px) {
	/* !! THE CREDENTIALS PANEL STACKS AT 1399, NOT AT 10b's 1199, AND
	 * THE 200px MATTERS.
	 *
	 * 10.9 stacks at 1199 because 388 of image plus 96 of padding still
	 * leaves 620 of copy at 1200. This panel spends 530 on the image and
	 * its portrait is sized FROM the panel, so the same arithmetic runs
	 * out much earlier: at a 1200 viewport the copy is 478, the H2 goes
	 * to three lines, the panel grows past 1100, the portrait grows with
	 * it to 957 wide, and his elbows land 73px INSIDE the copy column.
	 * Measured. There is no column width that rescues 1200 - the copy and
	 * the overhang are the same quantity pulling opposite ways, which is
	 * exactly what 11b found on the other page.
	 *
	 * Above 1400 nothing needs rescuing: the panel content goes 1256 ->
	 * 1352 and stops, so the copy only widens and he only shrinks.
	 *
	 * !! IT CANNOT BE HALF-DONE. Stack the grid without the six rules
	 * under it and the portrait is not small or clipped, it is ABSENT -
	 * its desktop position is absolute against a column that only exists
	 * in the two-column layout. Same silent failure 10b and 11b both warn
	 * about. Restated here rather than widening 10b's own query, which
	 * would restack two pages that have already been reviewed. */
	.ct-credentials {
		/* Back to the standing colour-change rhythm. 13.6's 50 + 56 is
		 * the overhang's clearance and stacked there is no overhang -
		 * left in place it would be 56px of empty white above a panel
		 * whose portrait is at the BOTTOM. */
		margin-top: var(--section-pad);
		grid-template-columns: 1fr;
		/* overflow:hidden is safe HERE and nowhere above 1399: it clips
		 * his feet to the panel's 24px corner, and the only things that
		 * bleed OUT of this panel are the desktop overhang and the
		 * desktop left bleed, neither of which the stacked layout has. */
		overflow: hidden;
	}
	/* With one column declared, `grid-column: 2` on the copy would put it
	 * in an IMPLICIT second track and the panel would go back to two
	 * columns by another route. */
	.ct-credentials > .wp-block-group__inner-container > .itv-credentials__col,
	.ct-credentials > .wp-block-group__inner-container > .itv-credentials__media {
		grid-column: auto;
		grid-row: auto;
	}
	.ct-credentials .itv-credentials__media {
		position: static;
		display: flex;
		justify-content: center;
		align-items: flex-end;
		/* Paired with the panel's padding - cancels it exactly so he
		 * stands on the panel's edge rather than 48px above it. Change
		 * the padding at a breakpoint and this moves with it. */
		margin-bottom: calc(var(--space-48) * -1);
	}
	/* !! EVERY ONE OF 13.6'S DECLARATIONS HAS TO BE NAMED HERE. It is
	 * (0,2,0) and 10b's `.itv-portrait` is (0,1,0), so none of them fall
	 * away on their own.
	 *
	 * translate is the one that would have been silent: the portrait goes
	 * `position: static` here and a transform still applies to a static
	 * element, so 13.6's offset would go on shifting him left of the
	 * centred column at every width below 1400, off the panel's edge,
	 * with nothing in either rule looking wrong. */
	.ct-credentials .itv-portrait {
		position: static;
		width: min(260px, 100%);
		height: auto;
		aspect-ratio: auto;
		bottom: auto;
		left: auto;
		right: auto;
		translate: none;
		pointer-events: auto;
	}
	.ct-credentials .itv-portrait > img { width: 100%; height: auto; }
	/* Capped at the reading measure for 11b's reason: stacking 200px
	 * higher than the kit does means the column is 1255 wide at 1399,
	 * about 114 characters a line against --measure's 90. */
	.ct-credentials .itv-credentials__col {
		max-width: var(--measure);
		margin-inline: auto;
		text-align: center;
	}
	.ct-credentials .itv-credentials__col .hp-eyebrow--pill { margin-inline: auto; }
	.ct-credentials .hp-stat { justify-self: center; }
	.ct-credentials .hp-btn-line { text-align: center; }
	/* The four-across strip is a desktop layout: stacked, the copy column
	 * is capped at 960 and 10.9's two-up is the right shape again. */
	.ct-credentials .hp-stats__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 1199px) {
	/* Three columns of card stop working before the layout does. At a
	 * 1100 viewport each card is 325 and "A developing curve in the neck
	 * or upper back" sets on three lines inside it, which drags all six
	 * cards taller for one card's title. */
	.ct-cards { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 991px) {
	/* !! THE SPLIT'S STACK HAS TO BE RESTATED HERE, AND LEAVING IT OUT
	 * IS THE ONE MISTAKE THIS PAGE ACTUALLY MADE.
	 *
	 * 10b stacks the split with `.itv-split { grid-template-columns:
	 * 1fr }` at (0,1,0). 13.5's `.ct-xray { ... }` is the same (0,1,0)
	 * and later in the file, so it won INSIDE 10b's media query and the
	 * section stayed two-up all the way down - with the photograph
	 * holding its flat 420 and the copy taking what was left.
	 *
	 * Measured at a 600 viewport before this rule existed: the tracks
	 * were `84px 420px` and the copy column was EIGHTY-FOUR PIXELS wide.
	 * Nine paragraphs set one or two words to a line and the section ran
	 * 5973px tall against the 2400 it should. It is not subtle once
	 * measured and it is invisible in the CSS, which is the whole point
	 * of 11.3's warning: media queries add no specificity, so a page
	 * rule written later beats a kit rule written inside a breakpoint.
	 *
	 * The credentials panel below got this right at <=1199 and the
	 * split did not, in the same session. Every page-level
	 * grid-template-columns needs its own line in every breakpoint the
	 * kit stacks it at. */
	.ct-xray { grid-template-columns: 1fr; }

	/* 10b widens every .hp-media inside a stacked split to
	 * min(430px, 100%). This file is 420 and there is no reason to carry
	 * it 10px past its own size - the same 420-not-430 call 13.5 makes
	 * at desktop, restated because 10b's selector is (0,2,0) and inside
	 * a media query, exactly like this one. */
	.ct-xray .hp-media { width: min(420px, 100%); }
}

@media (max-width: 599px) {
	/* One card per row. Two 2-up cards at 390 are 155 wide, which is
	 * narrower than five of the six illustrations are tall. */
	.ct-cards { grid-template-columns: 1fr; }

	/* MOST THINGS CENTRE - the standing phone rule. .itv-hero,
	 * .itv-split and .itv-notfor are already in 10b's list and reach
	 * this page unchanged; .ct-conditions is this page's own section and
	 * has to name itself.
	 *
	 * .ct-statement is absent for the reason 10b gives about
	 * .itv-panel: it is centred at every width already, so listing it
	 * here would suggest the centring is a mobile decision. */
	.ct-conditions { text-align: center; }
	.ct-conditions > .wp-block-group__inner-container > h2 { margin-inline: auto; }
	/* THE RUNNING COPY DOES NOT CENTRE WITH IT. The lead line is short
	 * enough to look fine centred, but the two paragraphs under the grid
	 * run to four lines on a phone, and a centred paragraph gives the
	 * eye a new left edge on every one of them. One rule for all three,
	 * because they are the same kind of thing. */
	.ct-conditions > .wp-block-group__inner-container > p.wp-block-paragraph {
		margin-inline: auto;
		text-align: left;
	}
	/* The cards keep their centred text, where 10b's tiles go back to a
	 * left edge - see 13.4 for why: one- and two-line labels under a
	 * centred illustration, not four-line running sentences. */

	/* !! THE PADDING AND THE NEGATIVE MARGIN ARE A PAIR, AND THIS IS THE
	 * BREAKPOINT WHERE THE PADDING MOVES.
	 *
	 * 10b drops .itv-credentials to 24px of padding at <=599, and its own
	 * rule drops the media column's negative margin to -24 to match. The
	 * <=1399 block above restates that margin at -48 and scores (0,2,0),
	 * so it was still winning down here - measured, his feet hung 24px
	 * THROUGH the panel's bottom edge at 390.
	 *
	 * The file is a cutout cropped mid-thigh with no transparent margin
	 * at the bottom, so it has to land on the edge exactly: 24 short and
	 * his legs stop in mid-air, 24 long and they run out of the panel.
	 * Page 15 hit this in the same place for the same reason. */
	.ct-credentials .itv-credentials__media {
		margin-bottom: calc(var(--space-24) * -1);
	}

	/* !! THE STAT STRIP GOES BACK TO ONE COLUMN, AND IT IS THE SAME
	 * FAULT AS THE MARGIN ABOVE IT, ONE PROPERTY OVER.
	 *
	 * Added 2026-08-26 while building page 17, whose panel is a copy of
	 * this one and hit it in the same place. 10b takes the strip to a
	 * single column at <=599 for a measured reason - the panel's content
	 * is 294px at a 390 viewport - and the <=1399 block above restates
	 * two columns at (0,2,0), later in the file, so it went on winning
	 * down here.
	 *
	 * Measured at 390 before this rule existed: the label had 95px,
	 * "Google rating - 437 reviews" and "Serving Huntington Beach" both
	 * set on THREE lines. One column gives each the whole 294 and all
	 * four come back to one line.
	 *
	 * The 12px column gap goes with it for the same reason: it is 13.6's
	 * and it exists to buy width back from a four-across strip that does
	 * not exist at this width. */
	.ct-credentials .hp-stats__row { grid-template-columns: 1fr; }
	.ct-credentials .hp-stat { column-gap: var(--space-16); }
}


/* ==================================================================
 * 14  WHAT TO EXPECT
 *
 * VISUAL SOURCE: Figma 572:34 "(unverified) What to Expect", seven
 * section frames, 572:103 down to 572:75. Every colour, size and space
 * below comes from a token in section 01.
 *
 * !! SIX OF THE SEVEN SECTIONS ARE THE KIT AND FOUR OF THEM NEED NO
 * CSS AT ALL. The hero, the statement panel, the running-copy section
 * and the disqualifier band are .itv-* components used exactly as they
 * stand - they carry no page class, because a class that states nothing
 * is a hook somebody will later assume is load-bearing. What is left is
 * the AEO intro's second paragraph (14.2), the two-visit workup (14.3),
 * which is this page's one real build, and the mirrored credentials
 * panel (14.4).
 *
 * THE GROUND MAP, sampled off the 1440x3569 render at fourteen columns
 * - four in the gutters and ten inside the insets, read as RUNS PER
 * COLUMN rather than as a mode across the row:
 *
 *   white   0-2500      hero, AEO intro, the navy panel inset in it,
 *                       the two-visit workup, the credentials panel
 *                       inset in it, How You Know It Is Working
 *   cream   2500-3013   Who This Process Is Not For
 *   navy    3014+       Final CTA + footer (footer.php)
 *
 * !! THIS PAGE HAS ONE CREAM BAND WHERE PAGES 13, 15 AND 16 HAVE TWO,
 * so it has ONE colour change inside its own content instead of four.
 * Everything above the band is white with two navy panels inset into
 * it. Worth stating because the rhythm below looks thin next to 13.1's
 * and is not missing anything.
 *
 * !! 572:111 IS A NAVY PANEL AND IT IS A THIRD PANEL WIDTH - which is
 * 10.4's warning arriving for the third time, in its third costume.
 * Page 13's panel runs the full content column (x150 w1140), pages 15
 * and 16 inset theirs to x260 w920, and this one is x138.5 w1163 -
 * 11.5px WIDER than the content column on each side. Sampled navy from
 * y652 to y856 across x145 to x1290, and white at x1301.
 *
 * That is close enough to page 13's that it takes PLAIN .itv-panel with
 * no width override: at a 1400 viewport .itv-panel is 1352 wide, which
 * is exactly the content column's own content width, and above 1496 it
 * is 1448 against the column's 1400 - 24px wider a side, which is the
 * frame's 11.5 carried to our wider container. --container-statement is
 * page 15 and 16's inset and would be wrong here.
 *
 * !! THE TEMPLATE MATTERS. Page 17 carries _wp_page_template =
 * page-sections.php, set when this page was built - it had only Rank
 * Math's keys before. Without it page.php wraps the content in
 * .container, no band can reach the edge, and the page renders NEARLY
 * right, which is the dangerous failure mode.
 *
 * NO DESIGN EXISTS BELOW 1440px. Section 14b is the theme's own work.
 * ================================================================== */

/* ---- 14.1  the rhythm ----
 *
 * 10.1's default gives every .itv-section half the rhythm, every
 * .itv-band the full pad on both sides, and .itv-prose the full pad
 * below - and .itv-prose is the section that sits above this page's one
 * colour change, so that edge is already paid for by the kit:
 *
 *   hero  intro  [statement]  workup  [credentials]  how you know  WHITE
 *   -------------------------------------------------------------  change
 *   who this process is not for                                    CREAM
 *   -------------------------------------------------------------  change
 *   the closing CTA and footer                                     NAVY
 *
 * The statement panel needs nothing: .itv-panel's own 50 above and 50
 * below meets .itv-intro's 50 and the workup's 50 at 100 each side.
 *
 * The credentials panel is the one exception, below. */

/* !! THE RHYTHM ABOVE THE CREDENTIALS PANEL IS MEASURED TO THE TOP OF
 * HIS HEAD, NOT TO THE PANEL'S EDGE.
 *
 * The portrait clears the panel's top edge by 48 and it is on the LEFT,
 * directly under the workup's left-hand card. A plain half-rhythm would
 * leave the standing 100 between the card and the PANEL and only 52
 * between the card and his hair, which is not what the 100 is for.
 *
 * 50 + 48 is therefore the overhang added back: the section above pays
 * its 50, this pays 50 + the 48 he sticks out by, and the gap the eye
 * actually reads - card to hair - comes back to 100. Page 16 does the
 * same arithmetic with its own 56.
 *
 * The bottom is a plain half-rhythm and NOT the full pad: unlike page
 * 16 this panel is followed by another white section rather than by a
 * cream band, so .itv-prose above the band pays that edge instead. */
.wte-credentials {
	margin-block: calc((var(--section-pad) / 2) + var(--space-48))
	              calc(var(--section-pad) / 2);
}

/* ---- 14.2  AEO INTRO  (572:106) ----
 *
 * !! TWO PARAGRAPHS, WHERE 10.3 WAS WRITTEN FOR ONE - and the second
 * one would sit ON the first without this, because .itv-intro's
 * `margin: 0 auto` zeroes the bottom margin a paragraph would normally
 * carry. It is the kind of fault that reads as a font problem.
 *
 * 24 rather than the standing 16: these two are not a paragraph pair,
 * they are a dense block and its punchline, and the extra air is what
 * lets the second one land. */
.wte-intro p + p { margin-top: var(--space-24); }

/* THE SECOND PARAGRAPH IS SET LARGER, AS DRAWN.
 *
 * 572:107 is 40px tall for two lines and 572:108 is 27 for one - the
 * frame sets the AEO block BELOW its own body size and the punchline AT
 * it, a 35% step. We cannot take the first half of that: the AEO
 * paragraph is the block an answer engine lifts (10.3) and shrinking it
 * to mark a contrast would be paying for typography with reach.
 *
 * So the step is made upwards instead, from the 22px body to the 24 of
 * --type-body-lg. Smaller than the frame's step and in the same
 * direction, which keeps the relationship the designer drew without
 * touching the paragraph that has a job. */
.wte-intro__lede { font-size: var(--type-body-lg); }

/* ---- 14.3  THE TWO-VISIT WORKUP  (572:117) ----
 *
 * THE ONE REAL BUILD ON THIS PAGE: a header row of two labelled steps
 * with an arrow between them, over two large cards.
 *
 * !! THE FRAME DRAWS A SECTION HEADING - 572:118, "The Two-Visit
 * Workup" - AND IT IS NOT IN THE COPY, so it is not built. Same call
 * 13.6 flags on page 16's button label: the database is what is signed
 * off. Building it would also break the outline, because the two step
 * labels are H2s in the database and an invented H2 above them would
 * make them siblings of their own parent.
 *
 * !! THE CARDS ARE .hp-card WITH THREE DIFFERENCES STATED, NOT A NEW
 * CARD - the same finding 13.4 made, and the brief for this page
 * expected a new component here too. 572:120 is 554 wide with 32 of
 * padding and a ~14px gap between its paragraphs; .hp-card is 32 and
 * 12, and its radius is already the frame's. What differs is the fill,
 * the shadow and the gap. That is it.
 *
 * The card bodies are NOT .hp-card__body and that is deliberate. On
 * every other page that class holds a one- or two-line label and sits
 * at --type-small; here the cards hold the section's entire argument -
 * four paragraphs and five - so they are plain <p> at the 22px body.
 * The house rule that running copy is 22px on desktop, laptop AND
 * tablet outranks the frame's proportion, and it also means 12.1's
 * `.hp-card__body { font-size: 20px }` correctly does not reach them. */

/* !! THE HEADINGS ARE ABOVE THE CARDS AND THE READING ORDER STILL HAS
 * TO BE H2 -> ITS OWN CARD -> H2 -> ITS OWN CARD.
 *
 * The frame draws this as two rows - a header row of both steps, then a
 * row of both cards - and built that way the DOM reads heading,
 * heading, card, card, so the first card's four paragraphs arrive after
 * the second card's heading. Correct on screen, wrong in a screen
 * reader and wrong for anything that reads the outline.
 *
 * So the DOM is grouped by STEP and the grid is not. Each .wte-step
 * holds its own icon, heading and card in reading order and is
 * display:contents, which dissolves it exactly as 9.1 and 10.1 dissolve
 * WordPress's inner container - its three children become items of the
 * grid below and take their own row each. Both steps therefore share
 * one set of rows, which is the other thing this buys: if one label
 * wraps to two lines and the other does not, BOTH cards still start on
 * the same line. Nested grids cannot do that without subgrid.
 *
 * The arrow comes LAST in the DOM and sits in the middle column, which
 * is the one place source order and visual order are allowed to
 * disagree here - it is decorative, aria-hidden, and the sequence it
 * marks is already carried by the two headings.
 *
 * 554 / 32 / 554 in a 1140 frame, so the arrow lives IN the gutter
 * rather than beside it and there is no column gap on top of it. */
.wte-steps {
	display: grid;
	grid-template-columns: minmax(0, 1fr) var(--space-32) minmax(0, 1fr);
	grid-template-rows: auto auto 1fr;
}
.wte-step { display: contents; }
.wte-step > .wte-step__icon  { grid-row: 1; }
.wte-step > .wte-step__label { grid-row: 2; }
.wte-step > .wte-step__card  { grid-row: 3; }
/* :nth-child is DOM position and is unaffected by display:contents, so
 * these still name the two steps and not their children. */
.wte-step:nth-child(1) > * { grid-column: 1; }
.wte-step:nth-child(2) > * { grid-column: 3; }

/* THE FILE IS THE SIZE. 153 and 154 are 58x70 and 62x70 and the frame
 * draws them at exactly that inside its 554 column - so height is the
 * binding dimension and the width follows the artwork, the same call
 * .hp-media--icon makes at 100 and .ct-condition__icon makes at 200.
 *
 * The consequence, stated so it can be argued with: our column is 660
 * at a 1400 container against the frame's 554, so the mark occupies
 * 10.6% of the column against the frame's 12.6% and reads smaller than
 * drawn. Upscaling line art to fix that buys proportion and pays in
 * softness, which is where softness shows most. A 100-tall export of
 * both marks would settle it and is PARKED in HANDOFF. */
.wte-step__icon {
	width: auto;
	height: 70px;
	justify-self: center;
}
.wte-step__icon > img { width: auto; }

/* !! --type-h3 AND NOT --type-h2, AND THESE ARE REAL H2s.
 *
 * "Visit One: The Workup" and "Visit Two: Your Report of Findings" are
 * H2s in the database and stay H2s here - the level is the outline and
 * the outline is correct. The SIZE is a separate question and the frame
 * answers it: 572:122 is a 26px-tall label under its icon, where the
 * frame's own section headings are 38.
 *
 * At the full 40 they would be the two loudest things on a page that
 * already carries four other H2s, they would sit side by side shouting
 * at each other, and "Visit Two: Your Report of Findings" measures
 * about 545px at 40px against a 660px column - one line at 1400 and two
 * at 1200, which is a wrap that buys nothing.
 *
 * 30 keeps them unmistakably headings, sets both on one line down to
 * about 1100, and holds the frame's icon-over-label relationship. */
.wte-step__label {
	margin: var(--space-16) 0 var(--space-32);
	font-size: var(--type-h3);
	text-align: center;
}

/* THREE DIFFERENCES FROM .hp-card AND NOTHING ELSE.
 *
 * FILL: --surface-alt, not --surface. The frame fills both cards
 * #f5f4f1, which is --surface-alt to the byte, on the page's white
 * ground - the same cream-on-white inversion 10.6 makes for the workup
 * tiles, and for the same reason: on white a card needs a fill to be a
 * card.
 *
 * SHADOW: none. Sampled at the card's bottom edge, the render goes
 * straight from #f5f4f1 to #ffffff with nothing in between - the frame
 * draws a flat tinted panel, not a lifted tile. --shadow-tile exists
 * for a tile on a TINTED ground (10.6) and would be wrong twice over
 * here.
 *
 * GAP: 16, not 12. .hp-card's 12 is set against --type-small labels; at
 * the 22px body these are running paragraphs and 16 is the site's
 * standing paragraph rhythm. The paragraphs' own margins come off, or
 * the gap and the margin stack into 28 and leave 16 of dead space under
 * the last line. */
.wte-step__card {
	gap: var(--space-16);
	background: var(--surface-alt);
	box-shadow: none;
}
/* !! NO --measure CAP, AND THAT IS MEASURED RATHER THAN ASSUMED.
 *
 * Every other running-copy block on this site caps at the reading
 * measure, and one was written here first. It never fires: the cards go
 * two-up down to 991 and stack below it, so the widest a card's text
 * ever gets is at exactly 991 - 943 of card less 64 of padding, which
 * is 879, about 82 characters. Inside --measure's 90 at its widest
 * point. A rule that cannot fire is a rule somebody later assumes is
 * holding something up.
 *
 * The margin is what this rule is actually for: .hp-card is a grid and
 * the gap above already spaces these, so the paragraphs' own 16px
 * bottom margin would stack into 28 between them and leave 16 of dead
 * space under the last line. */
.wte-step__card > p { margin: 0; }

/* Centred on the icon row, which is 70 tall at every width because both
 * files are. --navy rather than the ink: it is the only mark on the
 * page that means "then", and at the ink it read as a stray glyph. */
.wte-steps__arrow {
	grid-column: 2;
	grid-row: 1;
	align-self: center;
	justify-self: center;
	font-size: var(--type-h3);
	line-height: 1;
	color: var(--navy);
}

/* ---- 14.4  WHO RUNS YOUR WORKUP  (573:37) ----
 *
 * !! THE MIRROR OF .itv-credentials, LIKE PAGE 16's - PORTRAIT LEFT,
 * COPY RIGHT - AND SIMPLER IN THE TWO WAYS THAT MATTER.
 *
 * !! THE SOURCE ORDER IS NOT MIRRORED WITH IT. The copy column stays
 * FIRST in the markup and the media column second, exactly as on the
 * other three pages, and only the grid columns are swapped. That is
 * what lets 14b's stacked treatment apply almost unchanged - it pulls
 * the portrait back into the flow and hangs him on the panel's bottom
 * edge, all of which assumes he comes AFTER the copy. Mirroring the
 * markup would have put him above the heading on every tablet and
 * phone, and the failure would have been silent. 13.6 found this.
 *
 * !! THERE IS NO BLEED HERE, AND PAGE 16'S --ct-bleed MUST NOT BE
 * COPIED. 660:4 places the file at x-90 in the panel and media 146's
 * opaque pixels start at x116, so the SUBJECT's left edge lands 26px
 * INSIDE the panel's left edge. Page 16 fought the window for a 41px
 * bleed because its file's subject reaches the frame's edge; this one
 * simply stands inside the panel.
 *
 * !! MEASURE THE SILHOUETTE, NOT THE BOUNDING BOX - the mistake page 16
 * made and paid a review cycle for. Profiled on the alpha channel in
 * twelve bands, media 146 is 600x588 with the opaque man at x116..483
 * and NO transparent margin top or bottom:
 *
 *      file y     left   right   width
 *        0- 49     230     345     116   <- head
 *       49-196     207     355     ~145  <- head and shoulders
 *      196-294     119     455     337
 *      343-441     116     483     368   <- folded arms, the widest
 *      539-588     146     417     272
 *
 * The part that overhangs the panel's top edge is 116px wide, not 600.
 * He is only full width across his folded arms, which sit beside the
 * middle of the copy. Sizing the media column off the file would charge
 * the whole 600 at every height and price the column out of reach.
 *
 * !! HE IS NEARLY SQUARE, WHICH IS THE ONE THING THAT IS HARDER HERE
 * THAN ON PAGE 16. Media 202 is 596x720, an aspect of 0.83; this file
 * is 1.02. A near-square cutout sized off the panel's HEIGHT gets very
 * wide very fast, and our panel is roughly twice the frame's height
 * because our type is twice the frame's size. The frame gives him 35%
 * of the panel's width; we cannot hold that and keep the overhang, so
 * he takes about 45% and the ceiling below stops it going further. */

/* !! THE MEDIA COLUMN IS A FLAT 540, AND IT IS SET BY HIS ELBOWS.
 *
 * The column has to clear the widest band of him that has copy beside
 * it - file y343-441, his folded arms at x483 - not his whole box:
 *
 *   subject width   368/600 of the image width, which is 0.6258 of its
 *                   height, which is (panel content + 144)
 *   his left edge   panel + 24, fixed against the panel, not the column
 *   copy's left     panel + 48 + 540 + 48  =  panel + 636
 *
 * Measured on the built page rather than solved, because the panel's
 * height is the COPY's height and the copy gets taller as the column
 * narrows - the two quantities pull opposite ways, which is what 11b
 * and 13b both ran into. See the measurements in HANDOFF.
 *
 * 1400 IS THE WORST CASE AND THAT IS WHY THE PANEL STACKS AT 1399.
 * Above 1400 the panel's content goes 1256 -> 1352 and stops, so the
 * copy column only ever gets WIDER, the panel only ever gets SHORTER,
 * and he only ever gets smaller. Below 1400 all three run the other way
 * and the clearance is gone within one breakpoint - see 14b. */
.wte-credentials { grid-template-columns: minmax(0, 540px) minmax(0, 1fr); }
.wte-credentials > .wp-block-group__inner-container > .itv-credentials__col   { grid-column: 2; grid-row: 1; }
.wte-credentials > .wp-block-group__inner-container > .itv-credentials__media { grid-column: 1; grid-row: 1; }

/* FOUR ACROSS, WHERE THE KIT STACKS IT 2x2 - 661:260 draws four stats
 * on one 581px line and 13.6 made the same call for the same reason:
 * the strip is 90px tall in one row against 138 in two, and on a panel
 * whose portrait is sized from its height, 48px of panel height is
 * 48px of overhang. The labels are left long rather than shortened to
 * fit: they are the same four strings the homepage and pages 13, 15
 * and 16 carry, and a stat that reads differently here would be a
 * worse inconsistency than a wrapped label. */
.wte-credentials .hp-stats__row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* The stat's own side padding comes off, and it buys the labels a line
 * back - 13.6's finding, restated because it is scoped to that page.
 * The padding was only ever there to give the hover ground room, and
 * the hover ground is the one thing on this site nobody has ever seen;
 * see the note in 12 about the compositor. */
.wte-credentials .hp-stat {
	padding-inline: 0;
	column-gap: var(--space-12);
}

/* !! HE IS NOT CROPPED AT ALL - the slot takes the FILE's aspect ratio,
 * so the inherited object-fit:cover has no surplus to throw away. That
 * is 11.8's mechanism rather than 10.9's: 10.9 buys height with cover
 * and pays in crop, which on a 1.02-aspect file would take the width
 * off his arms.
 *
 * THE HEIGHT IS THE PANEL PLUS THE OVERHANG, and it is the kit's own
 * expression unchanged - 100% (the copy column's height, which IS the
 * panel's content height, because .itv-credentials__media is
 * align-self:stretch) + 48 to cancel the panel's bottom padding + 48 to
 * cancel its top + 48 of overhang. 660:4 sits at y-48 in a 539.6 panel
 * and its bottom lands at 540, so he stands ON the bottom edge and
 * clears the top by 48. Only the ceiling changes.
 *
 * !! THE CEILING IS A BACKSTOP, NOT A LAYOUT VALUE. The formula never
 * reaches it in the range where this layout exists - the panel is
 * tallest at exactly 1400 and shorter at every width above. It is there
 * so a future copy change cannot silently make him enormous, which is
 * the failure 10.9 measured at 992 before it had one. */
.wte-credentials .itv-portrait {
	width: auto;
	height: min(calc(100% + (var(--space-48) * 3)), 1000px);
	aspect-ratio: 600 / 588;
	/* !! ANCHORED LEFT, AND THE OFFSET IS THE FILE'S OWN TRANSPARENT
	 * MARGIN PLUS THE PANEL'S PADDING.
	 *
	 * The kit anchors `right: 0` because its cutout bleeds off the
	 * RIGHT. Mirrored, a right anchor would also make the placement a
	 * function of the image's height, which is exactly what the fixed
	 * geometry here exists to avoid.
	 *
	 * The element starts at the media column's left edge, which is the
	 * panel's padding edge - panel + 48. Media 146 carries 116px of
	 * transparent margin on the left, so the FRAME and the MAN have
	 * different left edges and it is the man's that has to land:
	 *
	 *   -19.3333%   116/600 of the element's own width. A percentage in
	 *               `translate` resolves against the ELEMENT's width, so
	 *               this survives any change to his size.
	 *   -48         back out of the panel's padding, to the panel edge
	 *   +24         and in again by the frame's own inset. 660:4 puts
	 *               his left edge 26px inside the panel; 24 is that in
	 *               a token, and the 2px is below the threshold of
	 *               anything.
	 *
	 * NOT A BLEED. Nothing leaves the panel horizontally at any width,
	 * so there is no window to clamp against and no --ct-bleed here. */
	left: 0;
	right: auto;
	translate: calc(-19.3333% - var(--space-48) + var(--space-24)) 0;
	/* Decorative, and his box overhangs his column - all of it
	 * transparent. Nothing should be able to lose a click to a picture
	 * of a man. */
	pointer-events: none;
}

/* ==================================================================
 * 14b  WHAT TO EXPECT - RESPONSIVE
 *
 * !! NO DESIGN EXISTS BELOW 1440px AND NONE IS COMING. Everything here
 * is a judgement call, written down so it can be argued with.
 *
 * 1400-      Unchanged. The panel is tallest at exactly 1400 and gets
 *            shorter above it, so desktop's worst case is its floor.
 *
 * 1200-1399  The credentials panel stacks, for 13.6's reason and with
 *            13b's six-rule undo. The workup is still two-up: at 1200
 *            each card holds 496px of text, which is wider than the
 *            kit's own split holds at 992.
 *
 * 992-1199   The workup stacks. Two columns of running paragraphs stop
 *            working before the layout does.
 *
 * 600-991    Nothing further. Both two-column sections are already
 *            stacked and the tablet band gets full-width cards, which
 *            is the right shape for four running paragraphs.
 *
 * <600       The panel's padding drops to 24 and the portrait's
 *            negative margin has to drop with it - see the pair below.
 *
 * Nothing here is keyed to 767, for the reason 10b gives: everything
 * that has to stack has done so by 991, and what is left is a type
 * decision, which on this site happens at 599.
 * ================================================================== */

@media (max-width: 1399px) {
	/* !! THE CREDENTIALS PANEL STACKS AT 1399, NOT AT 10b's 1199, AND
	 * THE 200px MATTERS - 13.6's finding, and this panel is in a worse
	 * position than that one because its portrait is nearly square. It
	 * spends 540 on the image and the portrait is sized FROM the panel,
	 * so at 1200 the copy is 468, the H2 goes to two lines, the panel
	 * grows, the portrait grows with it, and his elbows land inside the
	 * copy column. There is no column width that rescues 1200: the copy
	 * and the overhang are the same quantity pulling opposite ways.
	 *
	 * !! IT CANNOT BE HALF-DONE. Stack the grid without the rules under
	 * it and the portrait is not small or clipped, it is ABSENT - its
	 * desktop position is absolute against a column that only exists in
	 * the two-column layout. Same silent failure 10b, 11b and 13b all
	 * warn about. Restated here rather than widening 10b's own query,
	 * which would restack three pages that have already been reviewed. */
	.wte-credentials {
		/* Back to the standing colour-change rhythm. 14.1's 50 + 48 is
		 * the overhang's clearance and stacked there is no overhang -
		 * left in place it would be 48px of empty white above a panel
		 * whose portrait is at the BOTTOM. */
		margin-top: calc(var(--section-pad) / 2);
		grid-template-columns: 1fr;
		/* overflow:hidden is safe HERE and nowhere above 1399: it clips
		 * his feet to the panel's 24px corner, and the only thing that
		 * bleeds out of this panel is the desktop overhang, which the
		 * stacked layout does not have. */
		overflow: hidden;
	}
	/* With one column declared, `grid-column: 2` on the copy would put
	 * it in an IMPLICIT second track and the panel would go back to two
	 * columns by another route. */
	.wte-credentials > .wp-block-group__inner-container > .itv-credentials__col,
	.wte-credentials > .wp-block-group__inner-container > .itv-credentials__media {
		grid-column: auto;
		grid-row: auto;
	}
	.wte-credentials .itv-credentials__media {
		position: static;
		display: flex;
		justify-content: center;
		align-items: flex-end;
		/* Paired with the panel's padding - cancels it exactly so he
		 * stands on the panel's edge rather than 48px above it. Change
		 * the padding at a breakpoint and this moves with it, which is
		 * what the <=599 block below is for. */
		margin-bottom: calc(var(--space-48) * -1);
	}
	/* !! EVERY ONE OF 14.4'S DECLARATIONS HAS TO BE NAMED HERE. It is
	 * (0,2,0) and 10b's `.itv-portrait` is (0,1,0), so none of them fall
	 * away on their own. translate is the one that would have been
	 * silent: the portrait goes position:static here and a transform
	 * still applies to a static element, so 14.4's offset would go on
	 * shifting him left of the centred column at every width below 1400,
	 * off the panel's edge, with nothing in either rule looking wrong. */
	.wte-credentials .itv-portrait {
		position: static;
		/* 320, where 10b's kit rule says 260 - because the subject is
		 * 368 of this file's 600 and only 61% of the box is man, against
		 * 76% on page 16. At 260 he would come to 159px of actual person
		 * where page 16's is 196. 320 puts him at 196 as well, so the
		 * two stacked panels agree. */
		width: min(320px, 100%);
		height: auto;
		aspect-ratio: auto;
		bottom: auto;
		left: auto;
		right: auto;
		translate: none;
		pointer-events: auto;
	}
	.wte-credentials .itv-portrait > img { width: 100%; height: auto; }
	/* Capped at the reading measure for 11b's reason: stacking 200px
	 * higher than the kit does means the column is 1255 wide at 1399,
	 * about 114 characters a line against --measure's 90. */
	.wte-credentials .itv-credentials__col {
		max-width: var(--measure);
		margin-inline: auto;
		text-align: center;
	}
	.wte-credentials .itv-credentials__col .hp-eyebrow--pill { margin-inline: auto; }
	.wte-credentials .hp-stat { justify-self: center; }
	.wte-credentials .hp-btn-line { text-align: center; }
	/* The four-across strip is a desktop layout: stacked, the copy
	 * column is capped at 960 and 10.9's two-up is the right shape. */
	.wte-credentials .hp-stats__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 991px) {
	/* !! THE WORKUP STACKS, AND EVERY PAGE-LEVEL grid-template-columns
	 * NEEDS ITS OWN LINE IN EVERY BREAKPOINT - which is 13b's lesson,
	 * where a page rule written later beat a kit rule written inside a
	 * media query and a copy column measured EIGHTY-FOUR PIXELS.
	 *
	 * Nothing above stacks this one for us: .wte-steps is this page's
	 * own component and the kit has never heard of it. Both steps'
	 * column assignments and all three row assignments have to be
	 * undone by name for the same reason - they are (0,2,0) and
	 * (0,1,0)+(0,1,0), and a media query adds no specificity.
	 *
	 * 991 and not 1199: at 1200 each card holds 496px of running text,
	 * which is wider than .itv-split's own copy column at 992 and it
	 * holds to 991. At 992 the cards are 392, which is not a column. */
	.wte-steps {
		grid-template-columns: minmax(0, 1fr);
		grid-template-rows: auto;
	}
	.wte-step > .wte-step__icon,
	.wte-step > .wte-step__label,
	.wte-step > .wte-step__card { grid-row: auto; }
	.wte-step:nth-child(1) > *,
	.wte-step:nth-child(2) > * { grid-column: 1; }
	/* Stacked, the sequence is the vertical order and the two headings
	 * carry it. A left-pointing arrow between two stacked cards would
	 * mean nothing; rotating it to point down would be a second thing to
	 * maintain for a mark that is already decorative. */
	.wte-steps__arrow { display: none; }
	/* The rhythm between the two steps. 16 above the label and 32 below
	 * it are step-internal; this is the gap between one step and the
	 * next, and it has to be bigger than either. */
	.wte-step:nth-child(1) > .wte-step__card { margin-bottom: var(--space-48); }
}

@media (max-width: 599px) {
	/* !! THE PADDING AND THE NEGATIVE MARGIN ARE A PAIR, AND THIS IS
	 * THE BREAKPOINT WHERE THE PADDING MOVES.
	 *
	 * 10b drops .itv-credentials to 24px of padding at <=599 and its own
	 * rule drops the media column's negative margin to -24 to match. The
	 * <=1399 block above restates that margin at -48 and scores (0,2,0),
	 * so without this it goes on winning down here and his feet hang 24px
	 * THROUGH the panel's bottom edge.
	 *
	 * Media 146 has NO transparent margin at the bottom - he is cropped
	 * mid-thigh and stands on the file's own edge - so it has to land
	 * exactly: 24 short and his legs stop in mid-air, 24 long and they
	 * run out of the panel. Pages 15 and 16 both hit this in the same
	 * place for the same reason. */
	.wte-credentials .itv-credentials__media {
		margin-bottom: calc(var(--space-24) * -1);
	}

	/* !! THE STAT STRIP GOES BACK TO ONE COLUMN, AND WITHOUT THIS LINE
	 * 14.4'S FOUR-ACROSS SURVIVES ALL THE WAY DOWN TO THE PHONE.
	 *
	 * 10b takes the strip to a single column at <=599 and gives .hp-stat
	 * a 32px icon track, for a measured reason: the panel's content is
	 * 294px at a 390 viewport and two columns of that is 139 each. The
	 * <=1399 block above restates two columns at (0,2,0) and is LATER in
	 * the file, so it went on winning down here.
	 *
	 * Measured at 390 before this rule existed: 32 of icon and a 12px
	 * gutter left the label 95px, "Google rating - 437 reviews" and
	 * "Serving Huntington Beach" both set on THREE lines, and the strip
	 * came to 230px tall. One column gives each label the whole 294 and
	 * every one of the four comes back to a single line.
	 *
	 * The 12px column gap goes with it: it is 14.4's, and it exists to
	 * buy width back from a four-across strip that does not exist here.
	 * Left in, it would only pull the label tighter to its icon. */
	.wte-credentials .hp-stats__row { grid-template-columns: 1fr; }
	.wte-credentials .hp-stat { column-gap: var(--space-16); }
}

/* ==================================================================
 * 15  ABOUT
 *
 * Page 12, /about/. Figma frame 574:38, 1440x4944.6.
 *
 * The sixth page built and the one that leans hardest on the kit:
 * seven of its eight sections are .itv-* components that already
 * exist. Only two things here are new - the hero, which puts the H1
 * INSIDE the photograph, and the "Two Doctors" panel, whose ground is
 * a photograph rather than a colour.
 *
 * Ground map, sampled per column off the 1440x4945 render rather than
 * down the gutter - this page has panels inset at both 260 and 150 and
 * a single column finds neither:
 *
 *   white   0-976      hero, AEO intro, the navy panel inset in it
 *   cream   976-1688   How Dr. Michael VanDerschelden Works
 *   white   1688-3788  What This Practice Rejects, Credentials, Two
 *                      Doctors
 *   cream   3788-4389  Who This Practice Is Not For
 *   navy    4390+      the closing CTA and the footer (footer.php)
 *
 * Four colour-change edges inside the content, so four sections pay a
 * full --section-pad where the default gives them half. They are all
 * in 15.1.
 *
 * !! "Who This Practice Is Not For" HAS NO CLASS OF ITS OWN AND THAT
 * IS DELIBERATE. .itv-band, .itv-notfor, .itv-rows and .itv-row are
 * the whole section, exactly as 13.7 found. A class that states
 * nothing is a hook the next session assumes is load-bearing.
 *
 * NO DESIGN EXISTS BELOW 1440px. Section 15b at the end of this block
 * is the theme's own work.
 * ================================================================== */

/* ---- 15.1  the page's rhythm edges ----
 *
 * 10.1's default is half the rhythm on every .itv-section and half on
 * each of .itv-panel and .itv-credentials. The four edges below are
 * where this page changes ground and the pad goes to full on both
 * sides of the line.
 *
 * .ab-statement -> the cream band  (100 + 100)
 * the cream band -> .ab-rejects    (100 + 100)
 * .ab-twodocs -> the cream band    (100 + 100)
 * the cream band -> footer.php     (the band's own, from .itv-band)
 *
 * !! .ab-rejects HAS TO RESTATE ITS BOTTOM PAD AS WELL AS ITS TOP.
 * 10.1 carries `.itv-prose { padding-bottom: var(--section-pad) }`,
 * which is page 13's and page 17's rhythm - on both of those the prose
 * section is the last thing before a cream band. Here it is followed
 * by the credentials panel on the SAME white ground, so it owes half,
 * not all. Same specificity, later in the file, so it wins on order. */
.ab-statement { margin-bottom: var(--section-pad); }
.ab-rejects   { padding-block: var(--section-pad) calc(var(--section-pad) / 2); }

/* ---- 15.2  PAGE HERO  (574:107) ----
 *
 * !! THIS IS NOT .itv-hero AND IT MUST NOT BECOME IT.
 *
 * 10.2 puts the H1 ABOVE a wide photograph. This frame puts it INSIDE
 * one: 574:107 contains exactly one child, the photograph, and the
 * render shows a two-line H1 sitting in the photo's left third on a
 * pale wash. Four reviewed pages depend on .itv-hero, so the overlay
 * is a page component and .itv-hero is left alone.
 *
 * !! THE WASH IS BAKED INTO MEDIA 157 AND CANNOT BE RE-TUNED FROM CSS.
 * The file is RGBA 1140x307 and its left third is a white scrim over
 * the beach. Measured against --ink on the composited file, the H1
 * region runs 7.96:1 at worst and 15.9 at the median. It holds above
 * 7:1 out to file x620 and falls off a cliff at x640, where the pier
 * starts - 4.83 at x640, 1.13 by x920. So x620 is a hard right edge
 * for text, and every number below is derived from it.
 *
 * The photograph is placed at the file's own aspect (1140/307), the
 * full content column, for 10.2's reason: a hero narrower than the
 * copy it introduces reads as a mistake.
 *
 * THE BREADCRUMB IS NOT BUILT. The frame draws a two-level trail and
 * the standing decision from pages 13, 15, 16 and 17 is that the
 * BreadcrumbList JSON-LD at the foot of the content is what engines
 * read. It stays; the visible trail does not arrive here. */
.ab-hero {
	display: grid;
	isolation: isolate;
	/* The H1's size is a share of the PHOTOGRAPH, not of the viewport,
	 * so the container is the section's own content box - which is the
	 * photograph's width at every width. See the font-size below. */
	container-type: inline-size;
	/* 10.2's opening: .site-main pays 48 and 24 more is the design's 72
	 * from the header's bottom edge. */
	padding-top: var(--space-24);
}
/* !! NOT `.ab-hero > h1`, AND THE FIRST BUILD OF THIS SECTION WAS.
 *
 * 10.1 dissolves the wp:group's inner container with display:contents,
 * so the H1 IS a grid item of .ab-hero - but display:contents changes
 * the BOX tree, not the element tree, and a child combinator reads the
 * element tree. `.ab-hero > h1` matches nothing; the photograph took
 * grid-area 1/1 on its own class selector, the H1 auto-placed into the
 * row below it, and the overlay was a stack with nothing in the CSS
 * looking wrong. 11.5 hits the same wall from the other side and
 * answers it by writing the full path. Here the two elements have
 * classes of their own, so a descendant selector is enough. */
.ab-hero h1,
.ab-hero__photo { grid-area: 1 / 1; }
.ab-hero__photo { aspect-ratio: 1140 / 307; }

/* !! THE H1's SIZE IS TIED TO THE PHOTOGRAPH, BECAUSE THE SCRIM IS.
 *
 * The scrim is a fixed share of the file - text may run from file x50
 * to x620 - so as the photograph narrows, the room for the headline
 * narrows with it. --type-h1 does the opposite: it is 46 at desktop
 * and goes UP to 50 across the 600-991 tablet band (12.2). At a 992
 * viewport the photograph is 944 wide, the scrim gives 472px of text,
 * and a 50px "Huntington Beach Chiropractic" wants about 745. It would
 * have run out over the pier with nothing in the CSS looking wrong.
 *
 * The text box is 49.6% of the photograph - 54% less the 4.4% left
 * inset - and both are shares, so the whole lockup holds its
 * proportion as the photograph scales.
 *
 * !! THE RATIO IS SOLVED FROM MEASURED STRING WIDTHS, NOT FROM AN
 * em ESTIMATE. Sora at 700 carries -0.5px of tracking, which is an
 * ABSOLUTE offset, so a string's width is a*F + b rather than a clean
 * multiple of the size. Measured in the browser at two sizes and
 * solved:
 *
 *   "Meet the Doctor Behind"              12.2491 F - 11
 *   "Meet the Doctor Behind Huntington"   18.4238 F - 16.5
 *   "Huntington Beach Chiropractic"       16.1403 F - 14.5
 *
 * The design's set is TWO lines breaking after "Behind", which needs
 * the third string to fit the box and the second one not to. Solving
 * both against a box of 0.496W leaves F/W in [0.0276, 0.0314] across
 * 1152 -> 1400. 2.95cqw sits in the middle of that with about 6% of
 * slack at each end, which is the margin a webfont substitution needs.
 *
 * cqw and not vw: vw would include the page gutter and the scrollbar,
 * and the photograph stops growing at the container while the viewport
 * does not. min() keeps --type-h1 as the ceiling so this headline is
 * never LARGER than the rest of the site's. At 1400 it resolves to
 * 41px against the frame's own 36 at 1440. */
.ab-hero h1 {
	z-index: 1;
	align-self: center;
	justify-self: start;
	margin: 0;
	padding-left: 4.4%;
	max-width: 54%;
	/* !! 4.8cqw, RAISED FROM 2.95 ON 2026-08-27. See the note under
	 * "THE OVERLAY HEROES WERE ALL UNDERSIZED" in 15.2a. */
	font-size: min(var(--type-h1), 4.8cqw);
}

/* ---- 15.2a  THE OVERLAY HEROES WERE ALL UNDERSIZED  (2026-08-27) ----
 *
 * !! GEREK LOOKED AT THREE PAGES AND SAID THE H1 READ SMALL. HE WAS
 * RIGHT, AND THE REASON WAS THE SAME ON ALL THREE.
 *
 * Measured at a 1600 viewport, where --type-h1 is 50:
 *
 *   homepage             57   (--type-h1-hero, its own scale)
 *   Adjustments          50
 *   Magic Hug            50
 *   Conditions Treated   50
 *   What to Expect       50
 *   Reviews              50
 *   ------------------------- the six pages whose H1 sits ABOVE a photo
 *   Articles           44.8   (3.2cqw)
 *   About              41.3   (2.95cqw)
 *   Y-Strap              35   (2.5cqw)
 *
 * Every page whose headline sits INSIDE its photograph was smaller than
 * every page whose headline sits above one, and Y-Strap was 30% down.
 * Below desktop it got worse rather than better - About measured 26.6px
 * at 768 and 20.4px at 600, against a 22px body. The note at <=1199
 * already caught half of this in the About build and called a headline
 * the size of its own body copy "not a headline"; it raised the tablet
 * value to 3.7cqw and never revisited the desktop one.
 *
 * !! THE CAP WAS NEVER WHAT MADE THESE HEROES SAFE, AND THAT IS THE
 * WHOLE FINDING.
 *
 * Each of these headlines sits in a box capped by max-width - 54% here,
 * 56% on Articles, 45.8% on Y-Strap - and text cannot leave the box it
 * is laid out in. So the CONTRAST GUARANTEE IS THE max-width. The
 * font-size cap only ever decided how many LINES the headline took.
 *
 * Verified by sweeping the size on all three built pages and measuring
 * where the glyphs actually land. On Y-Strap, from 35px to 50px, the
 * rightmost glyph moved between 35.7% and 45.6% of the photograph and
 * NEVER passed the 45.8% box - it goes DOWN as the type grows, because
 * a larger size breaks the line earlier. Same shape on the other two.
 *
 * So the caps were buying nothing and costing 15px of headline.
 *
 * !! WHAT DOES BIND IS VERTICAL FIT, which is what 15.2's original note
 * was reaching for: enough lines and the headline is taller than the
 * photograph it is centred in. That is a real limit and it is why a
 * flat var(--type-h1) is still wrong here. Swept on the built pages,
 * block height against photograph height:
 *
 *   About   600   28px -> 3 lines,  93 in 149   32px -> 5 lines, 178  X
 *   About   768   34px -> 3 lines, 113 in 194   38px -> 4 lines, 169
 *   About   992   46px -> 3 lines, 153 in 254   50px -> 4 lines, 222
 *   Artic.  992   46px -> 3 lines, 153 in 328   50px -> 5 lines, 278
 *   Y-Str. 1200   50px -> 4 lines, 222 in 410
 *
 * 4.8cqw is the largest round value that clears every one of those. It
 * reaches --type-h1 - the site's own H1, the same number the other six
 * pages use - from about a 1090 viewport up, and scales below that
 * where the photograph genuinely cannot hold more lines. One value on
 * all three heroes, because the constraint is the same on all three.
 *
 * The three separate numbers it replaces (2.95, 3.7, 3.2, 2.5) were
 * each solved from a different string's width, which is why they all
 * disagreed. The string is not the constraint; the box and the
 * photograph's height are. */

/* !! THE PHONE HERO IS A SECOND FILE, NOT A SECOND RULESET.
 *
 * At 390 the content column is 342 and media 157 renders 342x92 - a
 * strip. A two-line headline cannot sit over it at any size, and 10.2
 * already rejected exactly this shape on page 13.
 *
 * Media 249 (about-hero-family, 1000x1000) is the same scene shot
 * square and is served ONLY below 600, by a <source media> in the page
 * content. It landed 2026-08-27 and replaced the dashed placeholder
 * this section shipped with.
 *
 * !! THE 599 IN 15b AND THE 599 IN THE <source> ARE ONE DECISION IN TWO
 * PLACES, and 10.2 says the same thing about page 13's hero. They must
 * move together. If the CSS switches to square and the <source> has
 * not, object-fit:cover crops a 3.71:1 photograph into a square box and
 * shows a slice of somebody's shoulder; the other way round letterboxes
 * the square one. Nothing errors either way, which is what makes it
 * worth a warning rather than a comment.
 *
 * The overlay does NOT come back with the file. The wash the headline
 * sits on is baked into media 157 and 249 does not carry one, so below
 * 600 the hero stays stacked: headline, then the photograph. That is
 * also what Gerek asked for. */

/* ---- 15.3  THE STATEMENT PANEL  (574:114) ----
 *
 * !! THE SAME INSET AS MAGIC HUG'S AND CONDITIONS TREATED'S, TO THE
 * PIXEL. 574:114 is x260 w920 inside a content column that runs x150
 * to x1290, which is 569:104's and 566:98's geometry exactly - so
 * --container-statement (1130) is already the right number and 1.13
 * carries its derivation. This is the fourth navy panel on the site
 * and the third time this inset has appeared; page 13's is the odd one
 * out at full width.
 *
 * max-width ONLY, per 11.2a: .itv-panel's `width: calc(100% - pad*2)`
 * does the narrow-viewport work, and written as a width the panel
 * would stop shrinking below 1130 and burst the gutter.
 *
 * Sampled navy y617-926 at x270 through x1100 and white at x203 and
 * x1180, per 10.4's warning. Read the render inside the inset: this
 * page's OTHER panel is at 150, so one probe column would have found
 * one of the two and called the other white.
 *
 * The frame bolds two clauses and underlines a third; .itv-panel__mark
 * is the underline and 10.4's condition for it still holds - this
 * panel contains no anchors at all, so an underline cannot be misread
 * as a link. The three paragraphs run with the standing gap between
 * them, so no .itv-panel__tight anywhere: 661:198 spaces all three
 * evenly, unlike page 13's 2/1/2/1 grouping. */
.ab-statement { max-width: var(--container-statement); }

/* ---- 15.4  HOW DR. MICHAEL VANDERSCHELDEN WORKS  (574:119) ----
 *
 * The first cream band, with the monogram bleeding off its right. That
 * is 10.1b, which was promoted out of .ca-techniques on 2026-08-26 for
 * exactly this - and its warning is the one thing here that is easy to
 * miss: the class makes the section full-bleed and hands the container
 * to the wrapper, like .itv-band, because `right: 0` resolves against
 * the nearest positioned ancestor.
 *
 * !! THE WIDTH IS THE PAGE'S OWN AND IS NOT IN THE KIT. Each export is
 * cut to its own frame: 704 on Adjustments, 617 on Magic Hug. 667:2 is
 * drawn 1015x1001 at x606 within a 1440-wide section, so the visible
 * rectangle is 1440 - 606 = 834 - which is media 155's width exactly,
 * and 712 is its height. The file IS the bleed, so it is pinned right
 * at its own size with no translate, per 10.1b.
 *
 * 58vw is 834 at the design's own 1440, so the mark holds its share of
 * the screen as the page narrows and stops growing past its own size.
 * 9.6 hides it outright below 992.
 *
 * The mark is #f9f9f9 on transparent and the band is #f5f4f1, so it is
 * a LIGHTER mark on cream at about 1.03:1 - near-invisible by design,
 * and it matches the render. Do not "fix" it. */
.ab-works > .wp-block-group__inner-container > .hp-media--watermark {
	width: min(834px, 58vw);
}
/* 10.1's heading rule names .ca-* and .itv-prose and does not reach
 * here, for 11.5's reason: this section KEEPS its wrapper, so its own
 * children are one level further in. The measure cap does reach it,
 * from 10.1a's .itv-band selector. */
.ab-works h2 { margin: 0 0 var(--space-32); }
/* The button closes the section, so .hp-btn-line's symmetrical 24 hangs
 * 24px of nothing inside the band's 100. Same call 10.8 makes. */
.ab-works .hp-btn-line { margin: var(--space-32) 0 0; }

/* ---- 15.5  WHAT THIS PRACTICE REJECTS  (575:43) ----
 *
 * .itv-prose with one addition: this is the only page on the site with
 * an H3 under an H2 and nothing between them. 575:44 and 575:45 sit at
 * y0 and y62 against a 48px heading, so the sub-heading belongs to the
 * heading rather than to the paragraph under it - which is the
 * opposite of .ca-techniques' H3 (10.7), where the H3 introduces the
 * two cards below it and takes 40 above.
 *
 * The margins collapse to the larger of the two, so the H2's own 32
 * from 10.1 is what actually sets the gap; 16 here is what separates
 * the H3 from its first paragraph. */
.ab-rejects h3 { margin: 0 0 var(--space-16); max-width: var(--measure); }

/* ---- 15.6  CREDENTIALS AND PROOF  (575:50) ----
 *
 * !! THIS IS THE KIT DEFAULT, NOT THE MIRROR. 575:50 puts the portrait
 * on the RIGHT, breaking the panel's top edge, which is 10.9's own
 * orientation and the one page 13 uses. 13.6 and 14.4 are the mirror
 * and nothing from either belongs here: no grid-column swap, no
 * `left: 0`, no bleed clamp.
 *
 * The panel is x150 w1140 in the frame - the full content column - so
 * .itv-credentials needs no width override, unlike 15.3's panel.
 *
 * !! THE SLOT TAKES THE FILE'S ASPECT RATIO, SO HE IS NOT CROPPED AT
 * ALL. That is 11.8's mechanism rather than 10.9's, and here it is not
 * a refinement, it is the only way this file works.
 *
 * 10.9 buys height with object-fit:cover and pays in crop: the slot is
 * the column wide and the surplus width is thrown away off BOTH sides.
 * On page 13 that costs 14px a side, because media 132 is 388x874 and
 * the panel is short. Media 156 is 358x779 - 95px shorter than page
 * 13's file - against a panel this page makes TALLER, because the
 * credential list here is seven items and page 13's copy is four
 * paragraphs. Run 10.9's arithmetic on it and the surplus is about 108
 * at a 1400 viewport: 41px off each side in file space, against 12px
 * of transparent margin on the left and 13 on the right. That is
 * roughly 29px into each of his folded arms, at the file's widest band
 * (y325-390, opaque x12..343). Measured on the alpha channel, not
 * estimated from the bounding box - which is the mistake page 16 made
 * and it cost a review cycle. THE PART THAT OVERHANGS THE TOP EDGE IS
 * 113px WIDE, NOT 358.
 *
 * With the slot at 358/779 the surplus is zero and the height is free
 * to be whatever the composition needs. The cost moves to WIDTH: the
 * box is now taller than the column and therefore wider than it, so
 * the column is the number that has to be checked. See below.
 *
 * NO TRANSLATE, AND THAT IS WORTH SAYING BECAUSE 13.6 NEEDS ONE. The
 * kit anchors `right: 0`, which puts the BOX's right edge on the
 * media column's - 48px inside the panel. The file carries 13px of
 * transparent margin on that side, so at this scale the MAN's right
 * edge lands about 65px inside the panel, which is where 575:50 draws
 * him (1074 in a 1140 panel, 66 inside). The file's own margin is the
 * offset; adding one would move him off the frame's mark.
 *
 * THE HEIGHT IS THE PANEL PLUS THE OVERHANG:
 *
 *   100%   the copy column's height, which IS the panel's content
 *          height - .itv-credentials__media is align-self:stretch
 *   + 48   cancels the panel's bottom padding
 *   + 48   cancels its top padding
 *   + 104  the overhang, which is 660:10's own: the frame sits at
 *          y-104 in a 674.6 panel and its bottom lands at 675, so he
 *          stands ON the bottom edge and clears the top by 104
 *
 * The ceiling is a backstop against a future copy change, not a layout
 * value - the formula does not reach it in the range where this layout
 * exists. Both numbers are custom properties because the overhang is
 * used twice: the rhythm above the panel is measured to the top of his
 * HEAD, not to the panel, per 13.6 and 14.1. */
/* !! REPHOTOGRAPHED 2026-09-03 - MEDIA 156 -> 307, AND THREE NUMBERS MOVED
 * WITH IT. Everything above this note still describes the MECHANISM and is
 * still true; the measurements in it belong to media 156 and are kept because
 * they explain why the mechanism is shaped the way it is.
 *
 * The new cutout is `904:167` in the frame - and it is NOT a child of the
 * panel any more, it is a top-level node overlapping it, which is why
 * `575:50`'s tree comes back with only the copy in it and a screenshot of the
 * panel alone renders no man at all. `660:10` is deleted; do not look for it.
 *
 * Figma places it at its OWN pixel size, 448x748, with:
 *   - its right edge on the panel's OUTER right edge (x1290 in a panel that
 *     ends at 1290) - NOT inset by the padding the way 156 was
 *   - its bottom on the panel's bottom edge, as before
 *   - 73 of overhang above the panel's top, where 156 had 104
 *
 * WHAT CHANGED HERE, AND WHY EACH ONE HAD TO:
 *
 * 1. `aspect-ratio` 358/779 -> 448/748. The file is a different SHAPE, not
 *    just a different crop: 0.599 against 0.460. Leaving the old ratio would
 *    have squeezed him; leaving the height formula alone with the new ratio
 *    is what would have burst the column - at the height this slot resolves
 *    to, 0.599 asks for 549px where 0.460 asked for 436.
 * 2. `--ab-overhang` 104 -> 73, which is the frame's own number. It is used
 *    TWICE - the slot's height and the rhythm above the panel, which is
 *    measured to the top of his HEAD - so both move together, as intended.
 * 3. `right` 0 -> -48. NEW. `right: 0` put the box on the media column's
 *    edge, 48 inside the panel; the frame now wants the panel's outer edge.
 *    -48 cancels the padding exactly the way the -48 `bottom` already
 *    cancels it at the foot, so the two anchors now read as one idea.
 *
 * !! THE BOX IS NOW WIDER THAN ITS COLUMN AND THAT IS THE DESIGN, NOT A BUG.
 * 549 in a 440 column, anchored 48 further right, puts its left edge 13px
 * inside the copy column. The file carries 17/448 of transparent margin on
 * that side, which is 21px at this scale - so the MAN starts 8px clear of
 * where the copy ends. The frame draws exactly that overlap: its copy runs to
 * x850 and its portrait box starts at x842. Measured on the alpha channel,
 * not the bounding box - 15.6's own note records that estimating from the
 * bounding box cost a review cycle once.
 *
 * The column itself is deliberately NOT widened. Doing that would take 121px
 * out of the copy and reflow every paragraph in the panel to buy nothing:
 * the part of the box that spills is transparent plus 8px of clearance. */
.ab-credentials {
	--ab-overhang: 73px;
	--ab-portrait-col: 440px;
	--ab-portrait-max: 1200px;
	grid-template-columns: minmax(0, 1fr) minmax(0, var(--ab-portrait-col));
	/* !! THE BOTTOM IS ZERO AND .ab-twodocs DECLARES THE WHOLE 100.
	 *
	 * The kit's own bottom margin here is 0 because on page 13 the next
	 * thing is an .itv-section, which pays its half in PADDING. This
	 * page follows one panel with another, and two adjacent margins
	 * COLLAPSE - so 50 and 50 came out as 50, not 100. Measured: the
	 * gap was exactly half the rhythm. Written as 0 + 100 rather than
	 * 50 + 50 so the number in the file is the number on the screen. */
	margin-block: calc((var(--section-pad) / 2) + var(--ab-overhang)) 0;
}
.ab-credentials .itv-portrait {
	width: auto;
	height: min(
		calc(100% + (var(--space-48) * 2) + var(--ab-overhang)),
		var(--ab-portrait-max)
	);
	aspect-ratio: 448 / 748;
	/* Cancels the panel's right padding, so his box lands on the panel's
	 * OUTER edge - the same job the -48 bottom does at his feet. The
	 * <=1399 block resets this to `right: auto` along with the rest, and
	 * has to: stacked, the column this is absolute against is gone. */
	right: calc(var(--space-48) * -1);
	/* Decorative, and its box overhangs its column into the gap - all of
	 * that part transparent. Nothing should be able to lose a click to a
	 * picture of a man. */
	pointer-events: none;
}

/* !! THE STAT STRIP IS FOUR ACROSS HERE, WHERE THE KIT STACKS IT 2x2.
 *
 * 10.9 makes it a two-column grid because at page 13's 860px column
 * the four stats measure 260, 205, 245 and 235 and cannot share a
 * line. The copy column here is narrower than that and still takes
 * them, once the stat's own side padding comes off: 13.6 measured that
 * padding plus the kit's 16px column gap eating 32px of a 182px track
 * before the label saw any. The frame draws them on one line (575:55).
 *
 * It is worth doing for the same reason it was on page 13: the strip
 * is one row instead of two, and every pixel of panel height is a
 * pixel of overhang on a page where the overhang is the point.
 *
 * !! IT OWES TWO UNDOS AND BOTH ARE IN 15b. This selector is (0,2,0)
 * and so are 10b's, so neither of 10b's own breakpoints falls away on
 * its own - not the 2x2 it restores when the panel stacks, and not the
 * single column it goes to at 599. That second one shipped twice on
 * this site before it was caught. */
.ab-credentials .hp-stats__row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.ab-credentials .hp-stat {
	padding-inline: 0;
	column-gap: var(--space-12);
}

/* ---- 15.7  TWO DOCTORS. ONE BUILDING.  (575:63) ----
 *
 * The page's one genuinely new component. Its closest relatives are
 * .itv-credentials (a rounded panel, copy in one column, an image in
 * the other) and .itv-split, and it is neither: the photograph is the
 * panel's GROUND, not a column.
 *
 * 698:343 is 1140x589 at the content column and CLIPS its children;
 * 698:325 inside it is the photograph at 1301x743, larger than its
 * frame. So the file is cropped by the panel, not letterboxed into it,
 * which is what object-fit:cover on an absolutely-positioned figure
 * does for free. Media 163 is 1140x589 - the frame's size, not the
 * photograph's, so the export is already the visible rectangle.
 *
 * The panel's geometry is .itv-credentials' to the property, so the
 * page's two rounded panels sit on exactly the same edges.
 *
 * !! THE COPY SITS ON A JPEG, AND THAT IS A REAL DEPENDENCY.
 *
 * The photograph fades to near-white across its right half and the
 * copy is set in --ink on top of it. There is no scrim: adding one
 * would grey the fade the design is built on. Measured on media 163
 * against --ink, per column down the copy's own band:
 *
 *   x 41.0%   6.81  <- the frame's own left edge, her hair
 *   x 41.5%   8.26
 *   x 42.5%  11.67
 *   x 43.5%  11.11  <- built here
 *   x 50%+   13.3 and up, median 15.6
 *
 * So the copy column starts at 43.5% rather than the frame's 41%. It
 * costs 34px of column at a 1400 viewport - the copy still gets 717,
 * well inside --measure - and it buys the section AAA instead of a
 * marginal AA. Where the frame and CONTRAST disagree, contrast wins;
 * that is 1.2's standing rule and the same call 10.7 makes on the
 * amber technique label.
 *
 * !! IF MEDIA 163 IS EVER REPLACED, RE-MEASURE THIS. A frame that is
 * darker on the right takes the copy with it and NOTHING IN THE CSS
 * WILL LOOK WRONG. Parked against the photo session in HANDOFF. */
.ab-twodocs {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	width: calc(100% - (var(--container-pad) * 2));
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	/* The full rhythm above - see the note on .ab-credentials' bottom
	 * margin: these are two adjacent panels, so the two margins collapse
	 * to the larger rather than adding. The full pad below is the cream
	 * band's colour change. */
	margin: var(--section-pad) auto var(--section-pad);
	border-radius: var(--radius-md);
}
/* The ground. z-index:-1 rather than a source order trick, so the copy
 * can never end up behind it; isolate on the panel keeps that negative
 * layer from escaping into a neighbouring section's stacking context,
 * which is 9.6's lesson. The radius comes off because the PANEL is
 * doing the rounding, with overflow:hidden. */
.ab-twodocs__photo {
	position: absolute;
	inset: 0;
	z-index: -1;
	margin: 0;
	border-radius: 0;
}
/* 53% and 3.5%, both shares, so the column holds its place on the fade
 * as the panel scales. --measure is a guard rather than a working cap:
 * 53% is 717 at a 1400 viewport and 767 at 1920, and it never reaches
 * 960. The frame's own padding is 50 in a 1140 panel; --space-48 is
 * that number at our width. */
.ab-twodocs__col {
	width: min(53%, var(--measure));
	margin-inline: auto 3.5%;
	padding-block: var(--space-48);
}
.ab-twodocs__col > h2 { margin: 0 0 var(--space-32); }

/* The two-button row, 575:69. Neither is new copy: both links are
 * already inline in the paragraph above them, so this is 11.6's
 * navigation strip rather than an addition. */
.ab-twodocs__btns {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-16);
	margin: var(--space-32) 0 0;
}

/* !! THE MOMMA'S CHIRO BUTTON IS PINK AND GREEN AND THAT IS GEREK'S
 * CALL, MADE 2026-08-26, NOT A CSS ONE.
 *
 * 575:72 is drawn as a pink-to-mint pill and neither colour is on this
 * site. 1.2's standing rule is that --accent is a SURFACE colour and
 * the site has exactly two brands; a third appearing once, for an
 * outbound link to another business, is a brand decision. It was put
 * to Gerek with the site's own secondary treatment as the alternative
 * and he chose to keep it as drawn.
 *
 * The gradient is sampled off 575:72 rather than guessed: a horizontal
 * linear ramp, #f9d0d9 at the left cap to #a6f2c9 at the right, which
 * is Momma's Chiro's own pair. It is the ONE place these two values
 * appear, so they are written here rather than promoted to tokens -
 * a token would invite a second use.
 *
 * !! PROMOTED OUT OF .ab-btn-mommas ON 2026-08-27, when the Articles
 * page needed the same button. Everything here was page-scoped and none
 * of it was ever about that page - it is what a link to Momma's Chiro
 * looks like on this site, wherever it lands. Same move 10.1b made out
 * of .ca-techniques and 10.10 out of .mh-creds.
 *
 * The Articles frame draws its own version of this pill with a flat
 * #f0c5ff border and a grey shadow - a colour this site does not have.
 * It is NOT built: this component was already decided, already
 * contrast-checked, and a decided component beats a frame one-off. No
 * second accent colour went into section 01 for it.
 *
 * !! IT IS A PAGE CLASS ON .cta-button, NOT A MODIFIER OF IT. The
 * geometry, the gloss, the lift and the ink label all come from
 * section 04 unchanged; only the three painted layers are restated.
 *
 * CONTRAST: --accent-contrast (#141413) measures 13.21:1 on the pink
 * cap and 14.19 on the mint - better than the amber button's own 9.99,
 * so the label is safe at every point along the ramp.
 *
 * The rim is the amber rim's structure with its amber taken out: two
 * white specular catches, a darker band at 54%, and the same stops, so
 * the pill reads as the same object in a different colour rather than
 * as a different kind of button. */
.itv-btn-mommas {
	background:
		linear-gradient(90deg, #f9d0d9 0%, #a6f2c9 100%) padding-box,
		linear-gradient(
			180deg,
			rgba(255, 255, 255, 0.95)  0%,
			rgba(255, 255, 255, 0.70) 20%,
			rgba(120, 110, 118, 0.34) 54%,
			rgba(255, 255, 255, 0.60) 84%,
			rgba(255, 255, 255, 0.90) 100%
		) border-box;
	box-shadow:
		0 4px 7.5px rgba(214, 118, 156, 0.30),
		0 0 18px rgba(166, 242, 201, 0.45);
}
.itv-btn-mommas:hover {
	box-shadow:
		0 6px 12px rgba(214, 118, 156, 0.38),
		0 0 26px rgba(166, 242, 201, 0.55);
}


/* ==================================================================
 * 15b  ABOUT - RESPONSIVE
 *
 * !! NO DESIGN EXISTS BELOW 1440px AND NONE IS COMING. Everything here
 * is a judgement call, written down so it can be argued with.
 *
 * <=1399    BOTH OF THIS PAGE'S PANELS STOP BEING TWO THINGS SIDE BY
 *           SIDE. They fail at the same width for the same underlying
 *           reason and the measurements are under each block: the copy
 *           column narrows, the panel grows taller, and each panel's
 *           image is sized FROM the panel's height, so it grows into
 *           the copy just as the copy runs out of room. 13b found this
 *           on page 16 and put the same breakpoint on it.
 *
 * 1200-1399 is where they break rather than 992, so 10b's own <=1199
 *           stack is TOO LATE for this page and every one of its rules
 *           has to be restated here.
 *
 * <=1199    The headline goes to three lines and gets bigger for it.
 *
 * <600      One column everywhere, most things centre, and the hero
 *           becomes the headline over a dashed slot until the square
 *           export lands.
 *
 * Nothing is keyed to 767 or to 991, for 10b's reason: everything that
 * has to stack has done so by 1399 and what is left is a type
 * decision, which on this site happens at 599. An iPad Mini in
 * portrait is 744 CSS pixels and an iPad Air is 820 - both are
 * tablets here and both keep the overlay hero.
 * ================================================================== */

@media (max-width: 1399px) {

	/* ---- the credentials panel ----
	 *
	 * !! IT IS THE PORTRAIT'S WIDTH THAT RUNS OUT, NOT THE COPY'S, AND
	 * THE TWO ARE THE SAME QUANTITY PULLING OPPOSITE WAYS.
	 *
	 * 15.6 gives the slot the file's aspect ratio so nothing is cropped,
	 * and the price of that is a box whose WIDTH is a function of the
	 * panel's height. The panel's height is the copy's height. So as the
	 * viewport narrows, the copy column narrows, the panel gets taller,
	 * and the portrait gets WIDER at exactly the moment there is less
	 * room for it. Measured, with the media column held at 440:
	 *
	 *   1400   copy 768   panel  844   portrait 436   clear 48
	 *   1366   copy 734   panel ~870   portrait 448   clear 40
	 *   1300   copy 668   panel ~905   portrait 464   clear 24
	 *   1250   copy 618   panel ~940   portrait 480   clear  8
	 *   1200   copy 568   panel  995   portrait 505   clear -17
	 *
	 * At 1200 his box starts INSIDE the copy column. There is no column
	 * width that rescues it - widening the media column narrows the copy,
	 * which lengthens the panel, which widens him again. Tried at 500 and
	 * it diverges rather than settling, which is 13.6's finding on the
	 * mirrored version of the same panel.
	 *
	 * Above 1400 nothing needs rescuing: the panel's content goes 1256 ->
	 * 1352 and stops, so the copy only widens and he only shrinks. 1400
	 * is the worst case in the range where this layout exists, and it is
	 * the one that measures 48px of clearance.
	 *
	 * !! IT CANNOT BE HALF-DONE. Stack the grid without the rules under
	 * it and the portrait is not small or clipped, it is ABSENT - its
	 * desktop position is absolute against a column that only exists in
	 * the two-column layout. 10b, 11b and 13b all warn about this and it
	 * has been found by scrolling a real viewport every time. */
	.ab-credentials {
		grid-template-columns: 1fr;
		/* Stacked there is no overhang, so the 104 that measured the
		 * rhythm to the top of his head is 104px of empty white above a
		 * panel whose portrait is at the BOTTOM. Back to the standing
		 * half - .ab-rejects above pays the other half. */
		margin-top: calc(var(--section-pad) / 2);
		/* Safe HERE and nowhere above 1399: it clips his feet to the
		 * panel's 24px corner, and the only thing that ever bleeds OUT of
		 * this panel is the desktop overhang, which the stacked layout
		 * does not have. */
		overflow: hidden;
	}
	.ab-credentials .itv-credentials__media {
		position: static;
		display: flex;
		justify-content: center;
		align-items: flex-end;
		/* !! PAIRED WITH THE PANEL'S PADDING - it cancels it exactly, so
		 * he stands on the panel's EDGE rather than 48px above it. Media
		 * 156 has no transparent margin at the bottom, so it has to land
		 * exactly: 48 short and his legs stop in mid-air. Change the
		 * padding at a breakpoint and this moves with it - see <=599,
		 * where both are 24. */
		margin-bottom: calc(var(--space-48) * -1);
	}
	/* !! EVERY DECLARATION 15.6 ADDED HAS TO BE NAMED HERE. It is (0,2,0)
	 * and 10b's own .itv-portrait rules are (0,1,0), so none of them fall
	 * away on their own - and 10b's do not fire until 1199 in any case.
	 * aspect-ratio is the one that would have been silent: left in place
	 * it fights `height: auto` and the cutout comes out 119px tall inside
	 * a 260px box. Same class of trap as 13b's translate. */
	.ab-credentials .itv-portrait {
		position: static;
		width: min(260px, 100%);
		height: auto;
		aspect-ratio: auto;
		bottom: auto;
		right: auto;
		pointer-events: auto;
	}
	.ab-credentials .itv-portrait > img { width: 100%; height: auto; }
	/* Capped at the reading measure for 11b's reason: stacked, the column
	 * is the panel's full 1255 at 1399, about 114 characters a line. */
	.ab-credentials .itv-credentials__col {
		max-width: var(--measure);
		margin-inline: auto;
		text-align: center;
	}
	.ab-credentials .itv-credentials__col .hp-eyebrow--pill { margin-inline: auto; }
	.ab-credentials .hp-btn-line { text-align: center; }
	/* !! THE FOUR-ACROSS STRIP IS A DESKTOP LAYOUT AND THIS IS ITS FIRST
	 * UNDO. Stacked, the copy is capped at 960 and 10.9's two-up is the
	 * right shape again. It does not come back on its own: 15.6's
	 * selector is (0,2,0), the same as 10.9's, and later in the file. */
	.ab-credentials .hp-stats__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.ab-credentials .hp-stat {
		justify-self: center;
		column-gap: var(--space-16);
	}

	/* ---- the Two Doctors panel ----
	 *
	 * !! IT IS THE PHOTOGRAPH'S CROP THAT RUNS OUT, AND THE THING IT
	 * CROPS IS DR. MICHAEL'S HEAD.
	 *
	 * The panel is as wide as the page and as tall as its copy, and
	 * object-fit:cover resolves that against a file whose aspect is
	 * 1.936. While the panel is WIDER than 1.936 the crop is vertical
	 * and costs sky; the moment it is taller, the crop turns horizontal
	 * and comes off BOTH sides of a photograph whose subject is on the
	 * left. Measured, with the visible slice of the file in per cent:
	 *
	 *   1400   panel 1.988   no horizontal crop      0.0% - 100.0%
	 *   1366   panel 1.896   1.0% off each side      1.0% -  99.0%
	 *   1300   panel 1.778   4.0% off each side      4.0% -  96.0%
	 *   1200   panel 1.623   8.1% off each side      8.1% -  91.9%
	 *    992   panel 1.089   21.9% off each side    21.9% -  78.1%
	 *
	 * His head runs from 2.6% to 12.1% of the file. At 1200 more than
	 * half of it is gone and at 992 he is not in the photograph at all,
	 * which is a section about two doctors.
	 *
	 * object-position does not rescue it: anchoring left keeps him whole
	 * and takes the crop off the faded right half, which is the half the
	 * copy is standing on - at 992 that would put the copy over file
	 * 24%-54%, which is the couple, at about 4:1.
	 *
	 * So below 1400 the photograph becomes a normal image above the
	 * copy, at its own aspect, cropped nowhere - the same answer 10b
	 * gives every split on this site. overflow and the radius move off
	 * the panel and onto the photograph with it: the panel has no ground
	 * of its own, so a rounded box around copy on white rounds nothing.
	 *
	 * The rhythm above goes back to the standing half. The full 100 in
	 * 15.7 is there because two adjacent PANEL margins collapse; stacked,
	 * .ab-credentials is still a panel above it, so the collapse still
	 * applies and the 100 still has to be declared on this side. It is
	 * left alone. */
	.ab-twodocs {
		overflow: visible;
		border-radius: 0;
	}
	.ab-twodocs__photo {
		position: static;
		aspect-ratio: 1140 / 589;
		margin-bottom: var(--space-32);
		border-radius: var(--radius-md);
	}
	.ab-twodocs__col {
		width: auto;
		margin-inline: 0;
		padding-block: 0;
	}
}

@media (max-width: 1199px) {
	/* !! THE HEADLINE GOES TO THREE LINES HERE, AND IT GETS BIGGER FOR
	 * DOING IT.
	 *
	 * A single cqw ratio is a fixed proportion, so the two-line set in
	 * 15.2 scales the headline down exactly as fast as the photograph -
	 * which is right at desktop and wrong by the tablet band. Measured:
	 * at an iPad Air's 820 the photograph is 772 wide and the two-line
	 * rule sets the H1 at 23px, against a 22px body. A headline the size
	 * of its own body copy is not a headline.
	 *
	 * Three lines break the proportion. The binding string stops being
	 * "Huntington Beach Chiropractic" (16.1403F) and becomes "Meet the
	 * Doctor Behind" (12.2491F), so the same box carries a size about a
	 * quarter larger. Solved the same way as 15.2, over 552 -> 1151:
	 * F/W has to be under 0.0405 for the first line to fit and over
	 * 0.0324 for the second to break. 3.7cqw is the middle of that, with
	 * 11% of slack at 992 and 17% of margin on the break - the room a
	 * webfont substitution needs.
	 *
	 * Measured on the built page: 20.4px at 600, 25.8 at 744, 28.6 at
	 * 820, 34.9 at 992, 42.6 at 1199, and the text clears the scrim's
	 * right edge by 37 to 55px at every one of them.
	 *
	 * !! AND 20.4px AT 600 IS THIS NOTE'S OWN COMPLAINT, UNFIXED. It
	 * says a headline the size of its own body copy is not a headline,
	 * then leaves one two points UNDER the 22px body at the bottom of
	 * the band. The step from 2.95 to 3.7 was half a fix.
	 *
	 * !! SO THIS OVERRIDE IS GONE, 2026-08-27. 15.2a raises the base to
	 * 4.8cqw, which is larger than 3.7 at every width in this band, so
	 * restating a smaller number here would only put the old bug back.
	 * The band now measures 26.5px at 600, 34.6 at 768 and 45.3 at 992.
	 * The reasoning above about three lines and the binding string still
	 * describes what happens; it is the NUMBER that was too small, and
	 * 15.2a explains why the string was never the right thing to solve
	 * from. */
}

@media (max-width: 599px) {
	/* !! THE HERO STACKS AND THE OVERLAY GOES AWAY, AND THE PHOTOGRAPH
	 * CHANGES SHAPE WITH IT. See 15.2 - the wide file is a 92px strip at
	 * this width, so below 600 a second file is served: media 249, the
	 * same scene shot square.
	 *
	 * THE ASPECT RATIO HERE IS HALF OF A DECISION. The other half is the
	 * <source media="(max-width: 599px)"> in the page content and the
	 * two have to move together - see the warning in 15.2. */
	.ab-hero { display: block; }
	.ab-hero__photo { aspect-ratio: 1 / 1; }
	.ab-hero h1 {
		max-width: none;
		margin: 0 0 var(--space-24);
		padding-left: 0;
		/* Back to the site's own H1. The cqw cap exists to keep the
		 * headline inside a scrim that is not on the screen any more, and
		 * left in place it would set the phone H1 at 11px. */
		font-size: var(--type-h1);
	}

	/* MOST THINGS CENTRE - the standing phone rule. .itv-notfor is
	 * already in 10b's list and reaches this page unchanged; .itv-intro
	 * and .ab-statement are absent for the reason 10b gives about
	 * .itv-panel - they are centred at every width already, so naming
	 * them here would suggest the centring is a mobile decision.
	 * .ab-rejects is .itv-prose, which 10b also already names. */
	.ab-hero, .ab-works, .ab-twodocs__col { text-align: center; }
	.ab-works > .wp-block-group__inner-container > h2,
	.ab-twodocs__col > h2 { margin-inline: auto; }
	/* THE RUNNING COPY DOES NOT CENTRE WITH THEM. A centred paragraph of
	 * four lines gives the eye a new left edge to find on every one. */
	.ab-works > .wp-block-group__inner-container > p.wp-block-paragraph:not(.hp-btn-line),
	.ab-twodocs__col > p {
		margin-inline: auto;
		text-align: left;
	}
	.ab-twodocs__btns { justify-content: center; }

	/* !! THE COUPLE GET A SQUARE FILE TOO, AND THE SAME PAIRING APPLIES.
	 *
	 * The <=1399 block puts media 163 above the copy at its own 1.936,
	 * which is right down to about 600 and wrong below it: at a 342px
	 * column that is a 177px band, and the two faces the section is
	 * about are in the left 40% of it. Media 250
	 * (about-drmichael-wife, 1000x1000) is the same scene shot square
	 * and is served only here, by a <source media> in the page content.
	 *
	 * Same two-places-one-decision as the hero above, and (0,1,0) later
	 * in the file than the <=1399 rule it overrides. */
	.ab-twodocs__photo { aspect-ratio: 1 / 1; }

	/* !! THE PADDING AND THE NEGATIVE MARGIN ARE A PAIR, AND THIS IS THE
	 * BREAKPOINT WHERE THE PADDING MOVES.
	 *
	 * 10b drops .itv-credentials to 24px of padding here, and its own
	 * rule drops the media column's negative margin to -24 to match. The
	 * <=1399 block above restates that margin at -48 and scores (0,2,0),
	 * so it goes on winning down here unless it is named - and the
	 * symptom is his feet hanging 24px THROUGH the panel's bottom edge.
	 * Media 156 has NO transparent margin at the bottom, so it has to
	 * land exactly. Pages 15, 16 and 17 all hit this. */
	.ab-credentials .itv-credentials__media {
		margin-bottom: calc(var(--space-24) * -1);
	}

	/* !! THE STAT STRIP GOES BACK TO ONE COLUMN, AND THIS IS THE UNDO
	 * THAT SHIPPED TWICE BEFORE IT WAS CAUGHT.
	 *
	 * 10b takes the strip to a single column here for a measured reason:
	 * the panel's content is 294px at a 390 viewport, and two columns of
	 * that gives the label 95px, which sets "Google rating - 437
	 * reviews" on three lines. The <=1399 block above restates two
	 * columns at (0,2,0), later in the file, so it goes on winning down
	 * here unless it is named. Pages 16 and 17 both shipped with it
	 * wrong. Measured at 390 after this rule: all four labels on one
	 * line, the stat 66px tall instead of 107. */
	.ab-credentials .hp-stats__row { grid-template-columns: 1fr; }
	.ab-credentials .hp-stat { grid-template-columns: 32px 1fr; }
}


/* ==================================================================
 * 16  REVIEWS
 *
 * Page 18, /chiropractor-reviews/. Figma frame 576:45, 1440x3276 -
 * the SHORTEST frame in the file, and structurally the simplest page
 * built so far. There is no navy panel, no credentials panel, no
 * portrait cutout and no hero photograph. Six of its eight sections
 * are .itv-* components that already existed.
 *
 * ONE new component: the review card. Everything else here is a
 * rhythm edge, a restated grid, or two small lockups.
 *
 * !! THE PAGE ORDER FOLLOWS THE FRAME, NOT THE COPY DOCUMENT'S BUILD
 * NOTE. The 3.6 document puts its [REVIEW DISPLAY] marker after the
 * AEO intro paragraph; the frame puts the first row of cards BETWEEN
 * the H1 and the intro (hero 82-274, Review Display 1 274-635, AEO
 * intro 635-743). Checked on the frame's own node geometry rather
 * than inferred. No signed-off COPY is overridden by this - the marker
 * is a build note, not a sentence - so it is not the 10.x/13.7 "copy
 * beats frame" case and needed no decision.
 *
 * !! THE REVIEWS ARE NOT MARKED UP AS SCHEMA AND MUST NOT BE.
 *
 * The eight cards are third-party Google and Yelp reviews republished
 * here. They are a QUOTATION, not first-party reviews collected on
 * this site, so there is no `Review` node, no `UserReview` node and no
 * `aggregateRating` computed from them - and none may be added later.
 * functions.php:617 carries the full reasoning, written 2026-08-26
 * when aggregateRating was removed from #business for the same policy:
 * a business may not mark up its own aggregate rating, it is excluded
 * from rich results, and it puts the whole site's structured data at
 * risk for nothing.
 *
 * The counts ARE on this page in the two places that are legitimate -
 * visible running copy and the stat figures in 16.4. That is the
 * compliant home for them and this page is the largest instance of it
 * on the site.
 *
 * The page's own JSON-LD is a WebPage whose about and mainEntity are
 * both #business, plus a three-level BreadcrumbList. Both are in the
 * post content and both are correct; page 16's six MedicalCondition
 * entities are NOT a pattern to copy here.
 *
 * NO DESIGN EXISTS BELOW 1440px. Section 16b at the end of this block
 * is the theme's own work.
 * ================================================================== */

/* ---- 16.1  the page's rhythm edges ----
 *
 * 10.1's default is half the rhythm on every .itv-section and the full
 * pad on both sides of every .itv-band. The three edges below are
 * where this page changes ground and the WHITE side pays its half:
 *
 *   hero  reviews 1  intro                  WHITE
 *   --------------------------------------  ground change
 *   437 Google / 252 Yelp                   CREAM
 *   --------------------------------------  ground change
 *   why the reviews read  reviews 2         WHITE
 *   --------------------------------------  ground change
 *   who this practice isn't for             CREAM
 *   --------------------------------------  ground change
 *   the closing CTA and footer              NAVY (footer.php)
 *
 * !! "Who This Practice Isn't For" HAS NO CLASS OF ITS OWN, and the
 * AEO intro's only reason for one is the edge below. .itv-band,
 * .itv-notfor, .itv-rows and .itv-row are that whole section, exactly
 * as 13.7 and 15.1 found - a class that states nothing is a hook the
 * next session assumes is load-bearing.
 *
 * !! .rv-reviews--close EXISTS ONLY TO PAY AN EDGE. Both rows of cards
 * are the same section class; the second one happens to be the last
 * white section before the cream band, and CSS has no way to say
 * "the one before the band". The modifier names that position and does
 * nothing else. It is on .rv-reviews, which is this page's own class,
 * not on a shared .itv-* or .hp-* component. */
.rv-intro          { padding-bottom: var(--section-pad); }
.rv-reviews--close { padding-bottom: var(--section-pad); }
.rv-why            { padding-top:    var(--section-pad); }

/* ---- 16.2  PAGE HERO  (576:114) ----
 *
 * !! THE FIRST HERO ON THIS SITE THAT IS AN H1 ON WHITE AND NOTHING
 * ELSE. 576:114 is 192px tall and holds exactly two text nodes - the
 * breadcrumb, which this site does not draw (see 10.2), and the
 * headline. There is no hero photograph in the media library for this
 * page and none is coming; do not go looking for one.
 *
 * .itv-hero itself needs no change for that. Its padding-top and its
 * H1 cap are unconditional, and the aspect-ratio rules are all on
 * `.itv-hero .hp-media`, which simply never matches. Verified on the
 * built page rather than assumed - it had never been used without a
 * photograph before.
 *
 * The ONE thing that does not survive is the H1's bottom margin. 10.2
 * sets 40 to separate the headline from the photograph under it; with
 * no photograph that 40 lands on top of the section's own 50 and the
 * next section's 50, and the first row of cards sat 140px below the
 * headline instead of the house's 100. */
.rv-hero h1 { margin-bottom: 0; }

/* ---- 16.3  THE REVIEW CARDS  (717:2, 718:2) ----
 *
 * THE PAGE'S ONE NEW COMPONENT, and the first review component on this
 * site. HANDOFF 9.10 describes the homepage's review cluster as a flat
 * PHOTOGRAPH - artwork, not markup. It is unrelated to this and shares
 * no CSS.
 *
 * !! THE FRAME'S BLEEDING RAIL WAS NOT BUILT, ON GEREK'S CALL
 * 2026-08-27. 717:2 is 1916 wide starting at x150 in a 1440 frame, so
 * 626px of it runs off the right edge: five cards of 364 at a 388
 * pitch, of which you see three and a half. The cut card is the
 * affordance that says "there are more", and the only honest ways to
 * honour it were a scroll-snap rail or an auto-scrolling marquee.
 *
 * Asked directly, Gerek asked for neither: four cards fully visible on
 * one row, nothing running off the right, 2x2 on a tablet, one per row
 * on a phone. So the row is a plain grid and the eighth review is the
 * eighth card rather than the fourth one a reader has to find.
 *
 * That decision removes a whole class of problem with it. A horizontal
 * scroller has to be keyboard-reachable or its cards are unreadable to
 * anyone not using a mouse - tabindex="0" and an accessible name on
 * the container, or focusable cards. A grid of eight list items needs
 * none of that: every card is in the reading order already.
 *
 * FOUR CARDS PER ROW, NOT FIVE. The frame's 1916 measurement does not
 * survive the change and should not be scaled: at the house container
 * the row is 1400 and four cards on a 24 gutter are 332 each, giving
 * the quote 268px inside the 32 of padding. Five would be 256 and 192.
 *
 * !! EQUAL HEIGHT, WHICH IS A CHANGE FROM THE FRAME AND IS DELIBERATE.
 * The frame's cards are top-aligned and sized by their content - 225
 * five times in row 1 and 249 in row 2, with one 273 outlier that left
 * with Adam Folkes. The approved eight are all four lines or fewer, so
 * the variance is now one line at most, and a row of four cards that
 * each stop at a different place reads as ragged rather than as
 * deliberate. The grid stretches them; the row below does the work.
 *
 * `auto 1fr auto` is why the badges line up. The quote's row takes the
 * slack, so the attribution sits on the card's bottom edge in all four
 * cards instead of floating wherever its own quote happened to end. */
.rv-cards {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--space-24);
	margin: 0;
	padding: 0;
	list-style: none;
}
.rv-card {
	display: grid;
	grid-template-rows: auto 1fr auto;
	gap: var(--space-16);
	padding: var(--space-32);
	background: var(--gradient-review-card);
	border-radius: var(--radius);
	box-shadow: var(--shadow-card);
}

/* !! THE STARS ARE AMBER ON A NEAR-WHITE CARD, WHICH IS ABOUT 1.76:1,
 * AND THAT IS A CONSIDERED EXCEPTION RATHER THAN A MISS.
 *
 * Measured: --accent (#fbb03c) against the gradient's light end
 * (#f9f9f9) is 1.76:1, and 1.42:1 against its dark end (#d5e4f1).
 * 1.4.11 wants 3:1 for a graphic that carries meaning, and this site's
 * standing rule is that where the frame and contrast disagree,
 * contrast wins. Both were checked before this shipped.
 *
 * It is drawn as the frame draws it, for three reasons that hold
 * together and would not hold separately:
 *
 *   - THE STARS ARE NOT THE ONLY CARRIER. Every card also carries
 *     "Rated 5 out of 5." in .visually-hidden text, and the section
 *     above states 437 five-star Google reviews and 252 five-star Yelp
 *     reviews in running copy at full contrast.
 *   - THERE IS NO DISCRIMINATION TASK. All five glyphs are filled and
 *     all eight cards are identical, so nothing has to be told apart -
 *     which is what the 3:1 threshold exists to protect. A three-star
 *     card among five-star ones would be a different question.
 *   - THE SHAPE IS THE INFORMATION, NOT THE COLOUR. A filled star row
 *     reads as a five-star rating in greyscale.
 *
 * The alternative was a deeper gold - #b8770e clears 3:1 - and it is
 * not the brand amber, would sit next to real --accent buttons on the
 * same page, and buys nothing a reader does not already have twice
 * over. Flagged to Gerek in plain English rather than decided quietly;
 * if he wants the darker star it is this one declaration.
 *
 * Sized off --type-small rather than the frame's 84x19 lockup: five
 * glyphs at 19px come to about 95, near enough, and the row then moves
 * with the type ladder instead of holding a fixed pixel width down to
 * a phone. */
.rv-card__stars {
	margin: 0;
	font-size: var(--type-small);
	line-height: 1;
	color: var(--accent);
}
/* --type-small and --ink, matching .hp-card__body's size. This is the
 * card's CONTENT - it is what the reader came for - so it keeps the
 * full ink and the attribution below recedes instead. */
.rv-card__body { margin: 0; font-size: var(--type-small); }
/* --type-detail (17) and --ink-muted. The frame sets the attribution
 * about two steps under the quote; this site's smallest step above a
 * caption is --type-detail, so the hierarchy is one size AND one ink,
 * rather than inventing a type step for eight lines of text. */
/* !! THE min-height IS WHAT KEEPS THE BADGES ON ONE LINE ACROSS A ROW,
 * AND WITHOUT IT THEY ARE RAGGED AT EVERY DESKTOP WIDTH. Measured
 * before it was added, at 1280: cards 5, 6 and 7 wrapped their
 * attribution to two lines and card 8 ("Aileen M. - Yelp review", the
 * shortest of the eight) did not, so its badge sat 12px below its
 * three neighbours.
 *
 * The wrap is not a narrow-screen case and cannot be tuned away. The
 * frame sets this line at about 13px, where "Mitchell Claman - Google
 * review" measures 201px; at --type-detail it needs about 250, and the
 * space available inside a card is 236 at the house 1400 container and
 * 178 at 1200. It wraps on every screen that shows four cards. Making
 * it fit would mean --type-xs, which 12.1 already calls illegible on a
 * tablet, to buy back a line nobody was going to read as two anyway.
 *
 * So the row is given the height of two lines and centres in it. The
 * cite is the card's last grid row and the card is stretched to its
 * row's height, so every cite BOTTOM already aligned - fixing the box
 * height is what makes every badge align too, whether its name takes
 * one line or two. --space-48 is two lines of --type-detail to within
 * a pixel, and it is a token rather than a measured constant, so a
 * three-line name would still grow the box rather than spill it. */
.rv-card__cite {
	display: flex;
	align-items: center;
	gap: var(--space-12);
	min-height: var(--space-48);
	margin: 0;
	font-size: var(--type-detail);
	color: var(--ink-muted);
}
/* !! THE BADGE BOX IS SET HERE, NOT BY THE FILE, AND THE TWO FILES ARE
 * DIFFERENT SHAPES.
 *
 * The frame draws an 18x18 badge. Media 164 (the Google G) is exactly
 * 18x18; media 169 (the Yelp burst) is 38x49 - it is the STAT icon,
 * exported for 16.4, and it is the only Yelp mark in the library. Left
 * to its own size it would be a 49px-tall badge next to a 17px line.
 *
 * object-fit:contain inside a square box scales whichever file is
 * given to it without distorting either, so the G fills the box and
 * the burst sits inside it at 14x18. Do not swap this for width/height
 * on the file - that is what the brief warned about. */
.rv-card__badge {
	flex: none;
	width: var(--space-20);
	height: var(--space-20);
	object-fit: contain;
}

/* "Individual results vary." under EACH row, not once at the top.
 *
 * The AEO intro carries the same sentence, and on the built page it
 * sits about 1,600px above the second row - too far to be read as
 * covering cards 5-8. It is the same approved sentence in both places,
 * not new copy. */
.rv-disclaimer {
	margin: var(--space-24) 0 0;
	font-size: var(--type-detail);
	color: var(--ink-muted);
}

/* ---- 16.4  THE COUNT STATS  (576:125) ----
 *
 * !! THIS IS NOT .hp-stat AND MUST NOT BECOME IT.
 *
 * .hp-stat is an icon-figure-label ROW sized for a strip inside a navy
 * panel or a stats band. These are CARDS: 364x132 white boxes on the
 * cream, at the same 388 pitch as the review cards above, with a 48px
 * figure. Reusing .hp-stat here would also inherit 10b's <=599 single
 * column and 10.9's 2x2 tablet grid, and any page-level
 * grid-template-columns would then owe an undo in BOTH - which is the
 * two-undos trap that bit pages 16 and 17. Not reused, so not owed;
 * checked rather than assumed.
 *
 * !! THE THIRD CARD HAS NO ICON AND ITS TEXT STARTS AT THE CARD'S LEFT
 * EDGE. That is how 576:132 is drawn - "680+" sits at x28 where the
 * other two have their laurels and burst - so the three figures do NOT
 * line up across the row. Built as drawn: each card is its own box and
 * a phantom 48px spacer to align three numbers nobody reads as a row
 * would be inventing a lockup the design does not have.
 *
 * !! THE ICON BOX IS THE CSS, NOT THE FILE. These two exports are
 * about 1.9x every other page's - 55x47 and 38x49 against the 29x25
 * and 23x29 used on pages 12, 13, 16 and 17 - and it is the same
 * artwork at a different export scale. A 48px square with
 * object-fit:contain takes both without either one setting the size. */
.rv-stats {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--space-24);
	margin: var(--space-40) 0;
	padding: 0;
	list-style: none;
}
.rv-stat {
	display: flex;
	align-items: center;
	gap: var(--space-20);
	padding: var(--space-24) var(--space-32);
	background: var(--surface);
	border-radius: var(--radius);
	box-shadow: var(--shadow-card);
}
.rv-stat__icon {
	display: grid;
	flex: none;
	place-items: center;
	width: var(--space-48);
	height: var(--space-48);
}
/* !! THE CAPS ARE EXPLICIT LENGTHS, NOT `max-height: 100%`, AND THAT
 * IS THE WHOLE FIX. Written as percentages first and measured: the
 * Yelp burst is 38x49 natively and rendered at 38x49 inside its 48px
 * box - one pixel of overflow, and proof the FILE was still setting
 * the size, which is the exact thing this box exists to prevent. The
 * percentage resolves against a grid area whose height the item does
 * not stretch to, so it never bit. A length always resolves. */
.rv-stat__icon > img {
	max-width: var(--space-48);
	max-height: var(--space-48);
	width: auto;
	height: auto;
	object-fit: contain;
}
.rv-stat__text   { display: grid; gap: var(--space-8); }
.rv-stat__figure {
	font-family: var(--font-heading);
	font-size: var(--type-h2);
	font-weight: var(--weight-semibold);
	line-height: 1;
}
.rv-stat__label { font-size: var(--type-small); color: var(--ink-muted); }

/* A section heading introduces the blocks under it, not the next
 * sentence. 10.1 names the equivalent section on every other page;
 * .rv-counts is this page's and is named here rather than by extending
 * that list, so section 10 stays untouched. The measure cap it also
 * needs comes free from 10.1a, which caps any h2 that is a band's
 * direct child. */
.rv-counts h2 { margin: 0 0 var(--space-32); }

/* ---- 16.5  THE PLATFORM BUTTONS  (576:135) ----
 *
 * Two amber pills side by side, 217 and 196 wide at 50 apart. BOTH ARE
 * .cta-button AND NEITHER IS .hp-btn-line: they are drawn at the
 * larger size and they are not paragraph-links. Same shape as the
 * About page's .ab-twodocs__btns, and the same three declarations.
 *
 * !! THE GOOGLE URL IS THE COPY DOCUMENT'S MAPS URL, NOT THE SHORTER
 * g.page LINK THE THEME ALREADY CARRIES, and that was checked rather
 * than assumed. hbchiropractic_socials() returns
 * https://g.page/huntington-beach-chiropractic?share and its docblock
 * says it points at the review profile deliberately. Resolved on
 * 2026-08-27: it 302s to a Google SEARCH panel for ludocid
 * 3480081101900209020, and the copy's Maps URL carries CID
 * 0x304bbb52ec4fdb7c - the same decimal, so the same listing, but a
 * different landing page. Gerek's instruction was to use the short one
 * only if it went to the same place; it does not, so the signed-off
 * copy stands. The Maps URL is now in the codebase three times -
 * here, in the post content, and in functions.php:614's sameAs. */
.rv-platforms {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-48);
	margin-block: var(--space-40);
}

/* ---- 16.6  WHY THE REVIEWS READ THE WAY THEY DO  (576:141) ----
 *
 * .itv-split with two restatements, both forced by the photograph.
 *
 * !! THE PHOTOGRAPH IS A PORTRAIT AND EVERY OTHER .itv-split PHOTO ON
 * THIS SITE IS A LANDSCAPE. Media 166 is 293x440; the component is
 * written for the 430x420 file on page 16. Both the column and the
 * slot's aspect-ratio have to be restated, and the column restatement
 * is the one that owes an undo - see 16b.
 *
 * !! THE FRAME INSETS THIS SECTION AND THE BUILD DOES NOT. 576:141
 * puts its copy at x301 and its photo at x846, so the group spans
 * x301-x1139 - 838 wide, centred, inside a content column that runs
 * x150-x1290. Every other heading on this page starts at x150,
 * including the one above it and the one below it, so the frame gives
 * this single section a left edge 151px further in than its
 * neighbours. The house rule is that everything shares one left edge -
 * it is the reason .itv-* exists, and 10.1 argues it at length - so it
 * is built on the full content column. If it reads wrong it is one
 * declaration to inset it. */
.rv-why { grid-template-columns: minmax(0, 1fr) minmax(0, 293px); }
.rv-why .hp-media { aspect-ratio: 293 / 440; }
/* The two cross-links stack rather than sitting side by side - 576:146
 * draws them at y0 and y60 in a 489-wide column, and at 48 tall that
 * is a 12px gap. .hp-btn-line's own 24 of block margin is removed
 * because the gap is the grid's here.
 *
 * !! THE FIRST LABEL IS THE FRAME'S SHORT ONE. The copy says "More
 * about Dr. Michael VanDerschelden, DC"; 576:147 draws "More about Dr.
 * Michael". Gerek chose short labels on buttons, 2026-08-27, on the
 * reasoning that a button is a signpost rather than a sentence and the
 * full name is already through the running copy. The second label
 * matches the copy unchanged. */
.rv-crosslinks { display: grid; justify-items: start; gap: var(--space-12); }
.rv-crosslinks .hp-btn-line { margin: 0; }


/* ==================================================================
 * 16b  REVIEWS - RESPONSIVE
 *
 * !! NO DESIGN EXISTS BELOW 1440px AND NONE IS COMING. Everything here
 * is a judgement call, written down so it can be argued with. The
 * three card counts, though, are not the theme's own - they are what
 * Gerek asked for on 2026-08-27: four across, 2x2 on a tablet, one per
 * row on a phone.
 *
 * <=1199   The review cards go 4 -> 2x2, one breakpoint above the
 *          tablet band, because that is where four of them stop being
 *          cards. Measured: at 1400 a card is 332 with a 268 quote;
 *          at 1200 it is 270 with 206, which sets the longest excerpt
 *          on eight lines about twenty characters wide. Two-up at the
 *          same width gives each quote 499. Same width the header's
 *          nav collapses at, for a related reason.
 *
 * 600-991  The stat cards and the split stack. Both are side-by-side
 *          layouts that run out of room here, which is where this site
 *          stacks them - .itv-split's own kit rule is at 991.
 *          The review cards STAY 2x2: this is the tablet band and a
 *          tablet has room for two. At 744, an iPad Mini in portrait,
 *          a card is 336 with a 272 quote - within four pixels of the
 *          desktop card.
 *
 * <600     One column, and MOST THINGS CENTRE, which is the standing
 *          rule for this site on a phone. The review quotes do not:
 *          they are running sentences and they keep their left edge,
 *          the same call 10b makes for the tiles, the cards and the
 *          rows.
 *
 * The 600-991 band in section 12 sets this page's card text sizes as
 * well - see the end of 12.1. That block is LAST in the file on
 * purpose and wins on order.
 * ================================================================== */

@media (max-width: 1199px) {
	.rv-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 991px) {
	/* !! THIS IS THE UNDO 16.6 OWES, AND IT IS THE TRAP THAT CAUGHT THE
	 * LAST TWO SESSIONS.
	 *
	 * A media query adds NO specificity. 10b's
	 * `@media (max-width:991px) { .itv-split { grid-template-columns:
	 * 1fr } }` scores (0,1,0) and so does 16.6's `.rv-why`, which is
	 * later in the file and therefore wins at EVERY width unless it is
	 * named again here. On the About build the identical shape left a
	 * copy column ZERO PIXELS wide at 390, and page 16 hit it on this
	 * very component. Every page-level grid-template-columns owes a
	 * line in every breakpoint the kit stacks it at.
	 *
	 * The photo's own cap is restated for the same reason: 10b sets
	 * `.itv-split .hp-media { width: min(430px, 100%) }` at (0,2,0),
	 * which would carry this 293-wide portrait to 430 - a 47% upscale
	 * on the only photograph on the page. */
	.rv-why { grid-template-columns: 1fr; }
	.rv-why .hp-media { width: min(293px, 100%); margin-inline: auto; }

	/* Three stat cards stop fitting well before the layout does. At 992
	 * a card is 298 wide and its label has 166px left after the icon
	 * and the padding, which sets "Google reviews - 5.0 star" on two
	 * lines; at 768 it is 92px and four lines. One column gives the
	 * label the whole card and every one of the three comes back to a
	 * single line. A full-width stat row is a normal shape - it is what
	 * .hp-stats__row already is - and it is the honest answer here
	 * rather than a 2 + 1 grid that strands the third card. */
	.rv-stats { grid-template-columns: 1fr; }
}

@media (max-width: 599px) {
	.rv-cards { grid-template-columns: 1fr; }

	/* MOST THINGS CENTRE - the standing phone rule. .rv-why is absent
	 * because it carries .itv-split, which 10b already centres, and
	 * .itv-notfor is in that list too. These two sections are this
	 * page's own and are not. */
	.rv-reviews, .rv-counts { text-align: center; }
	.rv-counts h2 { margin-inline: auto; }
	.rv-platforms { justify-content: center; }
	/* The stat card centres as a lockup - icon and text together - so
	 * the two cards with an icon and the one without each centre in
	 * their own box. They are objects, not sentences. */
	.rv-stat { justify-content: center; }

	/* !! THE QUOTES DO NOT CENTRE WITH THEM. A centred review of six
	 * lines gives the eye a new left edge on every line. The stars and
	 * the attribution go with the quote rather than with the section,
	 * so the whole card stays one left-ranged block - same call 10b
	 * makes for .ca-workup .hp-card and .ca-technique. */
	.rv-card { text-align: left; }

	/* .itv-split centres its column at this width, so the buttons that
	 * live in it centre with it. justify-items is what actually moves a
	 * grid item; text-align alone would leave the pills packed left
	 * inside a centred column. */
	.rv-crosslinks { justify-items: center; }
}


/* ==================================================================
 * 17  ARTICLES
 *
 * Page 19, /articles/. Figma frame 577:52, 1440x2918.
 *
 * !! THE FIRST PAGE ON THIS SITE WHOSE CONTENT IS A QUERY RATHER THAN
 * COPY, so most of this build is PHP - home.php, archive.php,
 * inc/articles.php and inc/article-card.php. Read home.php's header
 * first; it carries the architecture. This section is only the paint.
 *
 * !! IT DOES NOT RENDER THROUGH page-sections.php AND IT HAS NO
 * wp-block WRAPPERS. Every other page is post content: a stack of
 * wp:group blocks, each wrapping its children in a
 * .wp-block-group__inner-container that 10.1 dissolves with
 * display:contents. This page is a template, so its sections' children
 * are already direct children and there is nothing to dissolve.
 *
 * THAT SILENTLY DISABLES ANY KIT RULE WRITTEN THROUGH THAT WRAPPER, and
 * one of them matters here: 10.1's reading-measure cap is written
 * `.itv-prose p.wp-block-paragraph:not(.hp-btn-line)`, and WordPress is
 * what adds .wp-block-paragraph. Nothing on this page carries it, so
 * the cap never applies and a paragraph would run the full 1400. It is
 * restated by name in 17.6. Anything else borrowed from the kit has to
 * be checked the same way before it is trusted here.
 *
 * !! THERE IS NOT ONE FULL-BLEED CREAM BAND ON THIS PAGE. That has
 * never happened before - pages 12, 13, 15, 16, 17 and 18 all have two.
 * Sampled per gutter column on the render (frame x12, x58, x116, x139),
 * all four agreeing exactly: white 0-2362, navy 2362+. The cream a
 * centre-column sample reports at y703-1057 is the "What Goes Here"
 * PANEL - an object on the white ground, not a ground. That is 10.4's
 * warning, 15's, 16b's and 18's, in its fifth costume.
 *
 * NO DESIGN EXISTS BELOW 1440px. Section 17b at the end of this block
 * is the theme's own work.
 * ================================================================== */

/* ---- 17.1  the page's rhythm ----
 *
 * !! THERE IS ALMOST NOTHING TO STATE, AND THAT IS THE POINT. 10.1's
 * default gives every .itv-section half the rhythm on each side, so
 * 50 + 50 = 100 between any two of them. This page has NO colour change
 * inside its content, so it has no edge that owes the full pad - the
 * first page of the eight where the default is the whole answer.
 *
 * Two exceptions, both real:
 *
 *   - the closing section owes its half of the edge into the navy CTA,
 *     the same as every other page's last white section.
 *   - the panel's air is MARGIN rather than padding, exactly as
 *     .itv-panel's is and for the same reason: padding would sit inside
 *     the panel and push its own copy off its own edges. It pays half
 *     and its two neighbours pay the other half, which is 50 + 50 on
 *     both sides.
 *
 * The panel's neighbours are .itv-sections paying in PADDING, so the
 * adjacent-margin collapse the About build found cannot happen here -
 * padding does not collapse with margin. Checked rather than assumed,
 * because that trap cost a rebuild once. */
.ar-workup { padding-bottom: var(--section-pad); }

/* ---- 17.2  PAGE HERO  (577:121) ----
 *
 * !! THE H1 IS INSIDE THE PHOTOGRAPH. That is the About page's shape,
 * NOT .itv-hero's - 10.2 puts the H1 above a wide photo and five pages
 * depend on it, so it is left alone.
 *
 * !! AND IT IS NOT .ab-hero EITHER. That class is keyed to About's
 * file: its aspect (1140/307), its own measured contrast cliff at file
 * x620, and a square mobile companion file. This file is 1156x402 - a
 * different aspect (2.876 against 3.713) and a different scrim ramp -
 * and it has NO mobile variant. Same pattern, own numbers.
 *
 * !! THE WASH IS BAKED INTO MEDIA 170 AND MUST NOT BE BUILT AGAIN. The
 * frame draws it as a CSS gradient over the photo; the export already
 * carries it. Sampled at mid-height on the file: rgb(250,250,250) at
 * 10% and 20% across, 240 at 30%, and real photographic content only
 * past 50%. A CSS gradient on top would apply it twice.
 *
 * !! THE FILE ALSO CARRIES ITS OWN ROUNDED CORNERS, IN ALPHA. Corners
 * fully transparent, still ~5% opaque 20px in at the bottom - so its
 * baked radius is LARGER than the frame's 16. The slot is given the
 * file's radius rather than the frame's, or the two disagree and the
 * corner reads as a double edge. --radius-md (24) is the closest token
 * and matches within a pixel or two of the file.
 *
 * CONTRAST, measured on the composited file against --ink, inside the
 * frame's own text box (x46-x614 of the 1140 slot):
 *
 *   worst 8.85    p05 10.59    median 16.32    best 17.66
 *
 * THE CLIFF, minimum down the H1 band by file x:
 *
 *   x578 (50%)  9.60      x809 (70%)  4.78
 *   x693 (60%)  6.69      x867 (75%)  2.94
 *   x751 (65%)  7.48      x982 (85%)  1.66
 *
 * So text holds above 7:1 out to about 66% of the file. The frame's box
 * ends at 54%. THAT IS ABOUT TWELVE POINTS OF HEADROOM THE ABOUT HERO
 * DID NOT HAVE - 15.2 records its box running right up to its cliff -
 * and it is what lets the headline take 56% here instead of 54%, which
 * is worth two whole font sizes on a narrow laptop. */
.ar-hero {
	display: grid;
	isolation: isolate;
	/* The H1's size is a share of the PHOTOGRAPH, not of the viewport,
	 * because the scrim is - so the container is the section's content
	 * box, which is the photograph's width at every width. */
	container-type: inline-size;
	/* 10.2's opening: .site-main pays 48 and 24 more is the design's 72
	 * from the header's bottom edge. */
	padding-top: var(--space-24);
}
/* Both children take the same cell. Written as descendants rather than
 * with `>` for no reason other than symmetry with .ab-hero - here there
 * is no display:contents wrapper in between, so either would work. */
.ar-hero__photo,
.ar-hero__title { grid-area: 1 / 1; }
.ar-hero__photo {
	margin: 0;
	overflow: hidden;
	border-radius: var(--radius-md);
	aspect-ratio: 1156 / 402;
}
/* !! BOTH PATHS ARE NAMED, AND THAT IS NOT BELT-AND-BRACES. The hero is
 * art-directed, so the <img> sits inside a <picture> and is a GRANDCHILD
 * of the figure - `.ar-hero__photo > img` alone stops matching and the
 * photograph silently loses its width, its height and its object-fit.
 * 9.0 names both paths on .hp-media for the same reason. The bare `> img`
 * is kept so the slot still works if the <source> is ever removed. */
.ar-hero__photo > picture {
	display: block;
	width: 100%;
	height: 100%;
}
.ar-hero__photo > img,
.ar-hero__photo > picture > img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}
/* !! THE SIZE IS TIED TO THE PHOTOGRAPH, BECAUSE THE SCRIM IS.
 *
 * As the photograph narrows, the room for the headline narrows with it.
 * --type-h1 does the OPPOSITE: it is 46 at desktop and goes UP to 50
 * crossing 992 into 991 (see 1.8b and the known-open note). Left on
 * --type-h1 alone the headline would grow into the photograph exactly
 * where the photograph stopped being able to hold it.
 *
 * SOLVED FROM THE STRING, NOT ESTIMATED FROM ems. The binding line is
 * "Dr. Michael VanDerschelden, DC" - 30 characters, the longer of the
 * two the frame breaks to. Sora at 700 carries absolute tracking, so a
 * string's width is a*F + b rather than a clean multiple of the size;
 * 15.2 measured that family and its 29-character sample solves to
 * 16.14F - 14.5, which this line follows within a character.
 *
 *   need   16.6F - 15 <= 0.56W      ->   F <= 0.0337W + 0.9
 *   3.2cqw = 0.032W, inside it at every width with slack for a webfont
 *   substitution, and close to the frame's own 36/1140 = 3.16%.
 *
 * min() keeps --type-h1 as the ceiling, so this headline is never
 * LARGER than the rest of the site's. cqw and not vw: vw would include
 * the page gutter and the scrollbar, and the photograph stops growing
 * at the container while the viewport does not. */
.ar-hero__title {
	z-index: 1;
	align-self: center;
	justify-self: start;
	margin: 0;
	padding-left: 4%;
	max-width: 56%;
	/* !! 4.8cqw, RAISED FROM 3.2 ON 2026-08-27. The reasoning is 15.2a's
	 * and it is about all three overlay heroes at once: this cap never
	 * protected the contrast - `max-width: 56%` above does, and the
	 * cliff is at 66% - it only decided the line count, and it was
	 * holding this headline at 44.8px where every non-overlay page runs
	 * 50. Swept at 992, this page's narrowest overlay width: 46px sets
	 * three lines and 153px inside a 328px photograph. */
	font-size: min(var(--type-h1), 4.8cqw);
}

/* ---- 17.3  WHAT GOES HERE  (577:126) ----
 *
 * An inset rounded CREAM panel on the white ground - x258 w920 in a
 * 1440 frame, measured off the render. That is the FOURTH appearance of
 * the statement-panel inset and --container-statement (1130) already
 * names it: 920 of the frame's 1140 content column, scaled to this
 * site's 1400, is 1130. Not re-derived.
 *
 * Its air is margin rather than padding, exactly like .itv-panel - see
 * 17.1.
 *
 * !! IT IS NOT .itv-panel. That class is a NAVY panel with white
 * centred copy and it is used on four pages. This one is cream with ink
 * copy and a photograph behind it. Sharing the class would mean undoing
 * the gradient, the colour and the radius, which is more declarations
 * than writing it. */
.ar-goes-here {
	position: relative;
	isolation: isolate;
	overflow: hidden;
	width: calc(100% - (var(--container-pad) * 2));
	max-width: var(--container-statement);
	margin: calc(var(--section-pad) / 2) auto;
	padding: var(--space-48) var(--space-32);
	background: var(--surface-alt);
	border-radius: var(--radius);
	text-align: center;
}
/* The blob artwork. z-index:-1 inside the section's own stacking
 * context - isolation:isolate above - so it sits under the copy without
 * escaping into a neighbouring section, which is 9.6's lesson and
 * 10.1b's. object-fit:cover because the export is 1840x708 (2.6) and
 * the panel is 1130 wide at whatever height its copy needs. */
.ar-goes-here__bg {
	position: absolute;
	z-index: -1;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	pointer-events: none;
}
.ar-goes-here__inner > h2 { margin: 0 0 var(--space-24); }
.ar-goes-here__inner > p {
	max-width: var(--measure);
	margin: 0 auto var(--space-20);
}
.ar-goes-here__btn { margin: var(--space-32) 0 0; }

/* ---- 17.4  THE SIX CATEGORY CARDS  (577:135) ----
 *
 * Three across, 364 wide at a 24 gutter in the frame - which is the
 * same pitch as the article grid below them, so the two card types line
 * up down the page even though they are different components.
 *
 * !! --surface-alt WITH A SHADOW, AGAINST THE ARTICLE CARD'S WHITE WITH
 * A HAIRLINE. Both sit on the same white ground in the same section and
 * they must not read as the same thing: these six are a permanent
 * statement about what the practice writes about, and the ones below
 * them are the writing. The frame draws exactly this difference.
 *
 * !! IT IS NOT .hp-card. That component is 32 of padding, a WHITE fill,
 * --shadow-tile, and a numbered __num line; this is 28, cream,
 * --shadow-card and no number. Counted before it was written: reusing
 * it would have meant restating four of its five painted properties,
 * which is more declarations than the component below and leaves a
 * shared class carrying a page's exception. 10.6 reused .hp-card by
 * restating two; 16.4 wrote its own after restating four. This is the
 * second case.
 *
 * A card whose category has no posts is NOT a link - see home.php. It
 * still renders, still carries its icon and its description, and gains
 * its link on its own the moment something is filed under it. */
.ar-cats {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--space-24);
	margin: var(--space-40) 0 0;
	padding: 0;
	list-style: none;
	text-align: left;
}
.ar-cat {
	display: grid;
	gap: var(--space-12);
	align-content: start;
	padding: var(--space-32);
	background: var(--surface-alt);
	border-radius: var(--radius);
	box-shadow: var(--shadow-card);
}
/* !! THE BOX IS THE CSS, NOT THE FILE, AND THE FILES MAKE THAT MATTER.
 * All six icons are 224 tall and the frame draws them at 56 - a 4x
 * export - and their widths run 102 to 176 because the artwork is
 * different shapes.
 *
 * HEIGHT IS THE BINDING DIMENSION AND THE WIDTH FOLLOWS THE ARTWORK,
 * which is 9.7's reasoning for .hp-media--icon: a fixed-width box would
 * centre the narrow Y-Strap mark in whitespace and break the left edge
 * the card's text sets.
 *
 * !! AN EXPLICIT LENGTH, NOT max-height: 100%. 16.4 shipped the
 * percentage and it silently did not resolve - the file went on setting
 * the size and overflowed its box by a pixel. A length always
 * resolves. */
.ar-cat__icon {
	display: block;
	height: var(--space-64);
}
.ar-cat__icon > img {
	width: auto;
	height: 100%;
	max-height: var(--space-64);
	object-fit: contain;
}
.ar-cat__title {
	margin: 0;
	font-family: var(--font-heading);
	font-size: var(--type-h5);
	font-weight: var(--weight-semibold);
}
/* The card is not a link, so the anchor inside it is a normal one and
 * needs no colour of its own - section 02's --ink is right. It gets the
 * navy on hover from 02 as well. Only the underline is dropped: the
 * title is already a heading-weight line in a card, and an underline
 * under a two-line title in a tile reads as clutter rather than as
 * affordance. The hover restores it. */
.ar-cat__link { text-decoration: none; }
.ar-cat__link:hover { text-decoration: underline; }
.ar-cat__body {
	margin: 0;
	font-size: var(--type-small);
	color: var(--ink-muted);
}

/* ---- 17.5  THE FEEDS AND THE EMPTY STATE  (577:132, 577:160) ----
 *
 * The section heading, and then one block per category that has posts,
 * and then all articles. See home.php for why only the last one
 * paginates. */
.ar-feeds > h2 { margin: 0 0 var(--space-24); max-width: var(--measure); }
.ar-feeds__lede { margin: 0; max-width: var(--measure); }
/* Each feed is its own block with the page's rhythm inside the section
 * rather than between sections - they are all one argument, so a full
 * --section-pad between them would read as several. */
.ar-feed { margin-top: var(--space-64); }
.ar-feed__title { margin: 0 0 var(--space-12); max-width: var(--measure); }
.ar-feed__lede { margin: 0; max-width: var(--measure); color: var(--ink-muted); }

/* "View all", and the archive's "All articles" back link. Not a button:
 * the frame gives this page two amber pills and they are the closing
 * CTA. A third and fourth pill per category would make the amber mean
 * nothing. */
.ar-more { margin: var(--space-24) 0 0; }
.ar-more__link {
	font-weight: var(--weight-semibold);
	text-decoration: none;
}
.ar-more__link:hover { text-decoration: underline; }
.ar-more--back { margin: var(--space-24) 0 0; }

/* !! THE EMPTY STATE IS DRAWN IN THE FRAME (577:160) AND IT IS WHY THIS
 * PAGE COULD BE BUILT BEFORE A SINGLE ARTICLE EXISTS.
 *
 * White on the white ground with a hairline, which is where the article
 * card's own treatment comes from - a reader watching the page fill up
 * sees the same slot gain contents rather than a different component
 * appear. See section 08.
 *
 * It shows only when nothing is published and it disappears on its own
 * the moment the first article lands. Nothing to remember, nothing to
 * edit. */
.ar-empty {
	display: grid;
	gap: var(--space-8);
	place-content: center;
	/* !! NO min-height. The frame's box is 160 tall and that is what its
	 * two lines and its padding come to - so the number is a RESULT, not
	 * an input, and pinning it would burst the box the first time this
	 * sentence is reworded. --space-32 on each side reproduces it to
	 * within a few pixels and survives a longer line. */
	margin: var(--space-40) 0 0;
	padding: var(--space-32);
	background: var(--surface);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	text-align: center;
}
.ar-empty__title { font-size: var(--type-body); font-weight: var(--weight-semibold); }
.ar-empty__body  { font-size: var(--type-small); color: var(--ink-muted); }

/* ---- 17.6  READING IS NOT A WORKUP  (577:163) ----
 *
 * !! THE MEASURE CAP IS RESTATED HERE AND IT IS NOT BELT-AND-BRACES.
 * 10.1 writes it as `.itv-prose p.wp-block-paragraph:not(.hp-btn-line)`
 * and WordPress is what adds .wp-block-paragraph. This page is a
 * template, so nothing on it carries that class and the cap never
 * applied - the paragraph ran the full 1400. See the section header. */
.ar-workup p { max-width: var(--measure); }
/* Two amber pills. The frame's gap is 12 down and 50 across
 * (577:166) - not the single value .rv-platforms uses, so it is written
 * rather than borrowed. */
.ar-workup__btns {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-12) var(--space-48);
	margin-top: var(--space-40);
}

/* ---- 17.7  THE CATEGORY ARCHIVE ----
 *
 * Not drawn in any frame - there is no archive frame in the file - so
 * this is the theme's own, and it deliberately wears /articles/'s
 * clothes. A reader following "View all Conditions & Pain States" has
 * not left the index, they have gone one level into it. */
.ar-archive-head > h1 { margin: 0 0 var(--space-16); max-width: var(--measure); }
.ar-archive-head__lede { margin: 0; max-width: var(--measure); color: var(--ink-muted); }


/* ==================================================================
 * 17b  ARTICLES - RESPONSIVE
 *
 * !! NO DESIGN EXISTS BELOW 1440px AND NONE IS COMING. Everything here
 * is a judgement call, written down so it can be argued with.
 *
 * <=1199   The six category cards go 3 -> 2. Three of them at 1200 are
 *          270 wide with 206 of text inside the padding, which sets
 *          "Digital X-Ray & Posture Analysis" on three lines under an
 *          icon. The article grid follows section 08's own breakpoints,
 *          which are a component decision and not this page's.
 *
 * <=991    THE HERO STOPS BEING AN OVERLAY, and this is the one real
 *          judgement on the page. The scrim is a fixed SHARE of the
 *          file, so the text zone narrows exactly as fast as the
 *          photograph does: at a 992 viewport the photograph is 944 and
 *          56% of it is 529px, which still holds the headline at 30px;
 *          at 768 it is 403px and 23px, and at 600 it is 309px and
 *          18px. A page's H1 at 18px is not a headline. Below 992 it
 *          stacks - headline first, photograph under it - and the
 *          headline goes back to the site's own --type-h1.
 *
 *          The About page holds its overlay down to 600 because it has
 *          a SQUARE mobile file to switch to. This page has none - there
 *          is no -Mobile variant of anything in the Articles set - so it
 *          stacks earlier instead of shrinking further.
 *
 * <=599    One column everywhere and MOST THINGS CENTRE, the standing
 *          rule for this site on a phone. The card bodies and the
 *          article tiles keep their own left edge: they are running
 *          sentences in boxes of their own.
 * ================================================================== */

@media (max-width: 1199px) {
	.ar-cats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 991px) {
	/* !! THE UNDO 17.2 OWES. .ar-hero sets `display: grid` and both
	 * children take cell 1/1; unstacking means putting the section back
	 * to a block so they flow. A media query adds no specificity and
	 * .ar-hero is (0,1,0) either way, so this wins only because it is
	 * later in the file - which is exactly the trap that caught the
	 * About and Conditions builds. Stated so it is not "tidied" upward.
	 *
	 * The cqw cap comes off with the overlay. It exists to keep the
	 * headline inside a scrim that is no longer behind it, and left in
	 * place it would set the phone H1 at about 10px. */
	.ar-hero {
		display: flex;
		flex-direction: column;
		/* The query container comes off with the overlay - nothing under
		 * here is sized against the photograph any more, and leaving it
		 * would keep an inline-size container on a plain block for no
		 * reader to benefit from. */
		container-type: normal;
	}
	/* !! THE MARKUP ORDER IS PHOTO-THEN-TITLE AND THE STACK IS
	 * TITLE-THEN-PHOTO. The grid above put both in one cell so source
	 * order never showed; the moment they flow it does. The photograph is
	 * decorative here and the headline is what the reader came for, so
	 * the headline goes first and the photograph is ordered after it.
	 * Reordering visually rather than in the markup is safe in exactly
	 * this case: the two are not a reading sequence and a screen reader
	 * gets the alt text either way. */
	.ar-hero__photo { order: 2; }
	.ar-hero__title {
		max-width: none;
		margin: 0 0 var(--space-24);
		padding-left: 0;
		/* Back to the site's own H1. The cqw cap exists to keep the
		 * headline inside a scrim that is not behind it any more, and
		 * left in place it would set the phone H1 at about 10px. */
		font-size: var(--type-h1);
	}
}

@media (max-width: 599px) {
	.ar-cats { grid-template-columns: 1fr; }

	/* !! THE PHOTOGRAPH CHANGES SHAPE, AND SINCE 2026-08-27 IT ALSO
	 * CHANGES FILE.
	 *
	 * At its own 2.876 a 342px phone gets a 119px band. This first
	 * shipped as a 3/2 CSS crop of the wide file at object-position 72%,
	 * because there was no mobile export and the subject sits right of
	 * centre. Gerek uploaded media 268 - 1000x1000, the same scene shot
	 * tight - and asked for the treatment the other pages get, so the
	 * crop is gone and the file is swapped instead. 342px square at 390.
	 *
	 * !! THIS 599 AND THE ONE IN home.php's <source media> ARE ONE
	 * DECISION IN TWO PLACES AND MUST MOVE TOGETHER. See the warning
	 * there. Nothing errors if they drift - the picture just goes wrong.
	 *
	 * No object-position: the file and the box are both square, so
	 * object-fit:cover has nothing to crop and nothing to centre. The
	 * 72% existed only to rescue a crop that no longer happens. */
	.ar-hero__photo { aspect-ratio: 1 / 1; }

	/* MOST THINGS CENTRE - the standing phone rule. .itv-intro is absent
	 * because it is centred at every width already, and .ar-goes-here
	 * because it is too; naming either here would suggest the centring
	 * is a mobile decision, which for those two it is not. */
	.ar-hero, .ar-feeds, .ar-workup, .ar-archive-head { text-align: center; }
	.ar-feeds > h2, .ar-feed__title, .ar-archive-head > h1 { margin-inline: auto; }
	.ar-workup__btns { justify-content: center; }

	/* !! THE RUNNING COPY DOES NOT CENTRE WITH THEM. A centred paragraph
	 * of four lines gives the eye a new left edge on every line. Same
	 * call 10b makes, and the same exclusions. */
	.ar-feeds__lede, .ar-feed__lede, .ar-workup p, .ar-archive-head__lede {
		margin-inline: auto;
		text-align: left;
	}
	/* Each card is a box of its own; centring the sentence inside it
	 * just makes six ragged boxes. */
	.ar-cat { text-align: left; }
	/* 48 of inset on each side is a third of a 390px screen. Same call
	 * .itv-panel makes at this width. */
	.ar-goes-here { padding: var(--space-32) var(--space-24); }
}


/* ==================================================================
 * 18  Y-STRAP
 *
 * Page 14, /y-strap/. Figma frame 593:58, 1440x4199 - the tallest
 * frame in the file, and the second longest copy on the site behind
 * About.
 *
 * !! SIX OF ITS EIGHT SECTIONS ARE PURE KIT AND NEEDED NO CSS. The
 * AEO intro is .itv-intro, the statement panel is .itv-panel (its
 * FIFTH appearance, same x260 w920 inset), both running-copy sections
 * are .itv-prose, and "Who the Y-Strap Is Not For" is
 * .itv-notfor/.itv-rows with nothing added. None of them got a page
 * class, because a class that states nothing is worse than no class -
 * pages 12, 17 and 18 each dropped some for the same reason.
 *
 * What is here is three things: the hero, the icon split on the first
 * cream band, and the credential list's colour.
 *
 * !! THE GROUND MAP, sampled per gutter column on the render (frame
 * x12, x58, x116, x139), all four agreeing:
 *
 *   white      0 - 1561   hero, AEO intro, navy panel, "Will Not..."
 *   cream   1561 - 2102   What the Y-Strap Actually Does
 *   white   2102 - 2571   The Y-Strap Is One Tool
 *   cream   2571 - 3151   Who the Y-Strap Is Not For
 *   white   3151 - 3644   The Credentials Behind the Pull
 *   navy    3644 +        Final CTA + footer (footer.php)
 *
 * A MODE ACROSS COLUMNS GETS THIS PAGE WRONG IN TWO PLACES. Sampling
 * the frame CENTRE reports navy at y716-1021 (the statement panel) and
 * navy again at y2764-3016 (the disqualifier rows). Both are OBJECTS
 * on their ground, not grounds - and reading them as grounds loses
 * BOTH cream bands. Read runs down a single gutter column for the
 * ground, then sample inside the inset separately for the objects.
 * That is 10.4's warning in its sixth costume.
 *
 * NO DESIGN EXISTS BELOW 1440px. Section 18b is the theme's own work.
 * ================================================================== */

/* ---- 18.1  the page's rhythm ----
 *
 * Four colour changes inside the content, which is the shape pages 12,
 * 13, 15, 16 and 18 all have. 10.1's default gives every .itv-section
 * half the rhythm on each side, so 50 + 50 = 100 on a shared ground;
 * .itv-band takes the full pad on both sides on the class itself. What
 * is left is the section either side of each band paying its half of
 * that edge, and this page gets most of that for free:
 *
 *   .itv-prose already carries `padding-bottom: var(--section-pad)`
 *   from 10.1, and BOTH of this page's prose sections sit immediately
 *   above a band. So the bottom of each is already paid.
 *
 * Two edges are left. The section after each band owes its top:
 *   - .ys-onetool follows the "What the Y-Strap Actually Does" band
 *   - .ys-creds follows the "Who the Y-Strap Is Not For" band
 *
 * .ys-creds also closes the page into the navy CTA, and that edge is
 * the same full pad - but it carries .itv-prose, so 10.1 has already
 * paid its bottom. Only its top is stated here.
 *
 * The statement panel is absent from all of this because it is a
 * PANEL: its air is margin, not padding, exactly like .itv-credentials
 * and for the same reason. It pays half and its two neighbours - the
 * AEO intro above and a plain .itv-prose below - pay the other half in
 * PADDING, so the adjacent-margin collapse the About build found
 * cannot arise. Padding does not collapse with margin. Checked rather
 * than assumed, because that one cost a rebuild once. */
.ys-onetool { padding-top: var(--section-pad); }
.ys-creds   { padding-top: var(--section-pad); }

/* ---- 18.1a  THE IMAGING SECTION IS A SPLIT NOW  (593:142 / 898:92) ----
 *
 * Added 2026-09-03, when the frame grew a photograph this section never
 * had. It was one column of prose; 898:92 now puts a 500x500 photograph
 * beside it at x803 and narrows the copy to 592. That is 10b's
 * .itv-split shape exactly, so the section takes .itv-split rather than
 * growing a layout of its own.
 *
 * TWO THINGS THE SHARED COMPONENT DOES NOT GIVE, both this frame's own:
 *
 *   - 10b caps the media column at 430 and shapes it 430/420. This slot
 *     is 500 and SQUARE.
 *   - the column override is DESKTOP-ONLY and has to be. 10b stacks at
 *     991 and hands the photo `width: min(430px, 100%)`; section 18
 *     sits ~4000 lines below that rule, so an unguarded override would
 *     win the tie on order and unstack the phone. The aspect-ratio is
 *     deliberately NOT guarded - the photograph is square at every
 *     width, and the stacked rule sets width, not shape.
 *
 * !! .itv-prose STAYS ON THE SECTION AND 18.1 IS WHY.
 *
 * Both of this page's prose sections sit immediately above a band, and
 * 10.1's `.itv-prose { padding-bottom: var(--section-pad) }` is what
 * pays that ground-change edge. Swapping .itv-prose out for .itv-split
 * would have halved the gap above the band and quietly falsified 18.1's
 * arithmetic - with nothing looking broken enough to catch. The two
 * classes compose: .itv-prose sets no display, and its `max-width:
 * var(--measure)` on paragraphs is wanted here, since .itv-split__col
 * caps nothing and the grid hands the copy ~844 at 1392.
 *
 * !! THE EXPORT IS 364x364 IN A 500 SLOT - a 1.37x upscale. Nothing in
 * the CSS depends on the file's size, so a bigger export is a drop-in
 * and needs no change here. Raised rather than worked around. */
.ys-imaging .hp-media { aspect-ratio: 1 / 1; }
@media (min-width: 992px) {
	.ys-imaging { grid-template-columns: minmax(0, 1fr) minmax(0, 500px); }
}

/* ---- 18.2  PAGE HERO  (593:127) ----
 *
 * !! THE H1 IS INSIDE THE PHOTOGRAPH - the About and Articles shape,
 * not .itv-hero's. 10.2 puts the H1 ABOVE a wide photo and five pages
 * depend on that, so it is left alone.
 *
 * !! AND IT IS NEITHER .ab-hero NOR .ar-hero. Each of those is keyed
 * to its own file's aspect, its own measured scrim and its own mobile
 * companion. Same pattern, own numbers - and this file's numbers are
 * the tightest on the site.
 *
 * 593:127 contains ONE child in the node tree: the photograph. The H1
 * and the breadcrumb are visible in the render but are not text nodes.
 * That does NOT mean they are baked in, and it was checked: media
 * 179's left third is a clean scrim with no lettering in it. The
 * headline is real Figma text the metadata call did not expose.
 *
 * The breadcrumb is NOT BUILT. Standing decision from pages 13, 15,
 * 16, 17, 18 and 19: the BreadcrumbList JSON-LD at the foot of the
 * content is what engines read, and the visible trail came out on
 * request. This page's JSON-LD is already three levels deep and
 * correct.
 *
 * !! THE FILE IS 1196x462 AND THE PHOTOGRAPH INSIDE IT IS 1140x406.
 *
 * Measured on the alpha channel, not guessed: the opaque body runs
 * x28-1167 and y12-417, so the export carries a transparent margin of
 * 28 left and right, 12 top and 44 bottom, holding a very faint drop
 * shadow (peak alpha 27/255 at the bottom, 10 at the sides). The frame
 * places that file so its OPAQUE BODY lands on the content column -
 * measured on the render, the photograph's hard edges are x150 and
 * x1290, which is exactly the 1140 column every H2 below it starts on.
 *
 * So the margin has to come off, or the photograph sits 2.4% inside
 * every other left edge on the page - which is the 10.1a bug Gerek
 * found on a wide monitor, in a new costume.
 *
 * IT IS CROPPED RATHER THAN OVERHUNG, AND THAT IS A MEASURED CHOICE.
 * A negative margin on the figure would line the body up too, but the
 * slot then overhangs the section's 24px gutter by 10px and the page
 * scrolls sideways from about 1025 to 1469 - a whole laptop range,
 * invisible at 1400 and at 1920. So the img is oversized inside a
 * clipped figure instead: the arithmetic is exact, nothing overhangs
 * anything, and the only thing lost is a drop shadow at 10% alpha.
 *
 *   img width      1196/1140 = 104.9123% of the column
 *   left offset      -28/1140 =  -2.4561%
 *   top offset       -12/1140 =  -1.0526%
 *   figure aspect  1140 / 406
 *
 * Percentage margins resolve against the containing block's WIDTH in
 * both axes, which is what makes the vertical offset expressible at
 * all. Every number is one measurement over 1140.
 *
 * !! NO border-radius, AND ITS ABSENCE IS THE DECISION. The corners
 * are baked into the file: traced on the alpha, the arc solves to r =
 * 15.7-16 at all four corners, which is --radius exactly. Adding a
 * matching radius to the clip box would be a second curve over the
 * first, and a pixel of disagreement reads as a double edge - 17.2's
 * finding. The clip box stays square; the region it would round is
 * already transparent. */
.ys-hero {
	display: grid;
	isolation: isolate;
	/* The H1's size is a share of the PHOTOGRAPH, not of the viewport,
	 * because the scrim is. The section's content box IS the
	 * photograph's width here - see the crop above - so it is the right
	 * container to measure against. */
	container-type: inline-size;
	/* 10.2's opening: .site-main pays 48 and 24 more is the design's 72
	 * from the header's bottom edge. */
	padding-top: var(--space-24);
}
/* !! DESCENDANTS, NOT CHILDREN. 10.1 dissolves the section's inner
 * container with display:contents, so the H1 and the figure ARE grid
 * items - but they are not direct children in the element tree.
 * `.ys-hero > h1` matches nothing, and the first build of .ab-hero was
 * written that way and lost its headline. */
.ys-hero h1,
.ys-hero__photo { grid-area: 1 / 1; }
.ys-hero__photo {
	margin: 0;
	overflow: hidden;
	aspect-ratio: 1140 / 406;
}
/* !! BOTH PATHS ARE NAMED BELOW, AND THAT IS NOT BELT-AND-BRACES. The
 * hero is art-directed, so the <img> sits inside a <picture> and is a
 * GRANDCHILD of the figure - `.ys-hero__photo > img` alone stops
 * matching and the photograph silently loses its width, its margins
 * and its clip. 17.2 names both paths for the same reason and 9.0 does
 * it for .hp-media. The bare `> img` is kept so the slot still works if
 * the <source> is ever removed. */
.ys-hero__photo > picture {
	display: block;
	width: 100%;
	height: 100%;
}
.ys-hero__photo > img,
.ys-hero__photo > picture > img {
	display: block;
	width: 104.9123%;
	/* !! max-width: none IS LOAD-BEARING AND ITS ABSENCE IS SILENT.
	 * Section 02's `img, svg { max-width: 100% }` clamps any image to
	 * its container, which is right everywhere else on this site and is
	 * exactly wrong for the ONE image deliberately wider than its box.
	 * Left off, the width computes back to 100%, the two negative
	 * margins still apply, and the photograph renders 58px narrow and a
	 * pixel left of every other edge on the page - measured at 1280
	 * before this line existed. Nothing errors. */
	max-width: none;
	height: auto;
	margin: -1.0526% 0 0 -2.4561%;
}

/* !! THE SCRIM RUNS OUT BEFORE THE FRAME'S OWN TEXT BOX DOES, AND THAT
 * IS THE ONE NUMBER ON THIS PAGE THAT IS NOT THE FRAME'S.
 *
 * Measured on the composited file against --ink, down the frame's own
 * H1 band. Minimum contrast by file x:
 *
 *   x358 (30%)  17.66      x574 (48%)   7.21
 *   x478 (40%)  12.44      x598 (50%)   6.46
 *   x526 (44%)   9.28      x609 (51%)   6.14
 *   x550 (46%)   8.39      x657 (55%)   4.54
 *
 * It holds above 7:1 only to about 48% of the file. The frame runs the
 * headline to 51.05% of the photograph - measured off the render, line
 * two spans 4.39% to 51.05% - so as drawn, the last of "Huntington
 * Beach Chiropractic" sits where the scrim has already given way. The
 * band's worst reads 6.09 rather than the 8.85 the Articles hero
 * measured and the 7.96 About measured.
 *
 * 6.09 is not a failure; it clears AA for large text. But every other
 * hero on this site clears 7:1 at its WORST, and the fix is one
 * number, so the box is capped at 45.8% of the photograph instead:
 *
 *   cap 44%   worst 9.56    cap 48%   worst 7.30
 *   cap 46%   worst 8.39    cap 51%   worst 5.82
 *
 * Where the frame and contrast disagree, contrast wins. The cost is
 * 5 points of width and about 4px of headline; the Articles hero had
 * twelve points of headroom here and this one has none.
 *
 * !! SIZED IN cqw AGAINST THE PHOTOGRAPH, NOT IN vw.
 *
 * The scrim is a fixed SHARE of the file, so the safe zone narrows
 * exactly as fast as the photograph does. --type-h1 does the opposite:
 * it goes UP from 46 to 50 crossing 992 into 991. Left on --type-h1
 * the headline would grow into the photograph precisely where the
 * photograph stopped being able to hold it. vw would be wrong for a
 * second reason - it includes the page gutter and the scrollbar, and
 * the photograph stops growing at the container while the viewport
 * does not.
 *
 * SOLVED FROM THE STRING, NOT ESTIMATED FROM ems. Sora at 700 carries
 * absolute tracking, so a string's width is a*F + b rather than a
 * clean multiple of the size. Measured in the browser, on this site's
 * own stack at -0.5px tracking:
 *
 *   "Huntington Beach Chiropractic"   16.140F - 14.49   <- binds
 *   "The Y-Strap Adjustment at"       13.626F - 12.51
 *
 * which is the pair the frame breaks the headline to, and the longer
 * of the two is what has to fit. The TEXT box is 45.8% - 4.2% = 41.6%
 * of the column, so:
 *
 *   need   16.140F - 14.49 <= 0.416W   ->   F <= 0.02578W + 0.90
 *   2.5cqw = 0.025W, inside it at every width, with enough slack that
 *   a fallback face 10% wider still lands at 47.4% - contrast 7.6.
 *
 * At a 1400 column that is 35px against the frame's own 36 in its
 * 1140 one. min() keeps --type-h1 as the ceiling, so this headline is
 * never LARGER than the rest of the site's.
 *
 * 4.2% is the frame's own left inset: 49px into a 1140 photograph,
 * measured off the render.
 *
 * MEASURED ON THE BUILT PAGE, not just designed: the headline's right
 * edge lands at 43.3% of the photograph at 1200, 43.5% at 1399, 1400,
 * 1500, 1920 and 2560 - the string does not fill its box, so the cap is
 * a ceiling rather than a target. Contrast over the band the headline
 * actually occupies is worst 9.63, p05 11.80, median 17.86. The 45.8%
 * cap guarantees 8.39 even if a fallback face fills it completely. The
 * frame as drawn measures 6.36.
 *
 * !! max-width IS 45.8%, NOT 41.6%, AND THE DIFFERENCE IS THE PADDING.
 * This site is box-sizing: border-box, so max-width caps the padding
 * box - the 4.2% inset is INSIDE it. Written as 41.6% the text got
 * 460px where it needed 483, and the headline broke to THREE lines at
 * 1280 while every number above still said it fit. The cap is where
 * the text must END; the padding is where it starts. */
.ys-hero h1 {
	z-index: 1;
	align-self: center;
	justify-self: start;
	margin: 0;
	padding-left: 4.2%;
	max-width: 45.8%;
	/* !! 4.8cqw, RAISED FROM 2.5 ON 2026-08-27, THE DAY THIS PAGE
	 * SHIPPED. 2.5 was solved from the binding string on the assumption
	 * that the string's width was what kept the headline out of the
	 * failing scrim. It is not: `max-width: 45.8%` above is, and text
	 * cannot leave its box. Swept from 35px to 50px on the built page,
	 * the rightmost glyph moved between 35.7% and 45.6% and never
	 * reached the cap - it moves DOWN as the type grows, because a
	 * larger size breaks the line earlier.
	 *
	 * So the small type bought nothing and cost 15px. This hero was the
	 * smallest H1 on the site at 35px against the other pages' 50, which
	 * is what Gerek saw. 15.2a carries the full working.
	 *
	 * This page's overlay only exists at 1200 and up, where 4.8cqw is
	 * 55px and --type-h1 wins outright - so in practice this is
	 * var(--type-h1) with a guard. Four lines at 50px is 222px inside a
	 * 410px photograph at the narrowest overlay width, measured. */
	font-size: min(var(--type-h1), 4.8cqw);
}

/* !! THE PHONE HERO IS A DIFFERENT FILE, AND IT ARRIVED DURING THE
 * BUILD.
 *
 * At its own 2.808 aspect the wide file is a 122px strip in a 342px
 * phone column, and a three-line headline cannot sit over that at any
 * size - 10.2 rejected exactly this shape on page 13 and 15.2 on page
 * 12. This page shipped its first hour with the kit's dashed frame in
 * that slot, the way .ab-hero did on 2026-08-26. Gerek uploaded media
 * 270 - 1000x1000, the same scene shot tight - the same afternoon, so
 * the placeholder is gone and the file is swapped instead. 342px
 * square at 390.
 *
 * The undo is in 18b and it is bigger than the Articles one, because
 * this slot is not a plain box: 18.2 crops a transparent margin out of
 * the WIDE file with an oversized img and two negative margins, and
 * media 270 has no margin to crop. Every one of those declarations has
 * to be named again at <=599 or the square renders 5% too big and a
 * few pixels off. */

/* ---- 18.3  WHAT THE Y-STRAP ACTUALLY DOES  (594:61) ----
 *
 * The one genuinely new layout on the page: an icon rail on the left,
 * everything else on the right, with the H2 INSIDE the copy column
 * rather than above the pair. 594:62 draws the icon at x0 w90.7 h200
 * and the copy at x160.7 w979 inside a 1140 column - so a 91px track,
 * a 70px gutter, and the rest.
 *
 * !! IT IS NOT .itv-split. That component is
 * `minmax(0,1fr) minmax(0,430px)` with a PHOTOGRAPH on the right and
 * the heading OUTSIDE the pair. This is the mirror image and its fixed
 * column is 91px of line art. Sharing the class would mean undoing the
 * track order, the track sizes, the gap, the alignment and the heading
 * position, which is more declarations than writing it.
 *
 * !! AND IT IS NOT .hp-media--icon's job either - that class is a
 * 100px-tall icon slot inside a card. Here the icon is 200 tall and is
 * a layout column.
 *
 * !! THE GRID GOES ON THE KEPT WRAPPER, NOT ON THE SECTION. This is a
 * band, and 10.1 keeps a band's inner container as a real block to
 * carry the container and the gutter - see the note there, and the
 * 10.1a bug that made it necessary. The section itself is full-bleed
 * and must stay that way to paint its ground. */
.ys-does > .wp-block-group__inner-container {
	display: grid;
	grid-template-columns: 91px minmax(0, 1fr);
	gap: var(--space-72);
	/* The frame tops the icon and the H2 on one line. Without this the
	 * 200px rail centres against a 365px copy column and the drawing
	 * floats. */
	align-items: start;
}
/* !! HEIGHT IS THE BINDING DIMENSION AND THE CAP IS AN EXPLICIT
 * LENGTH. 9.7's rule for .hp-media--icon, restated by 16.4 and 17.4:
 * a percentage max-height on a grid item DOES NOT RESOLVE - the
 * Reviews build shipped one and the file went on setting its own size.
 *
 * Media 180 is 91x200 and the frame draws it at 90.7x200, so this is
 * the one icon on the site exported at its drawn size rather than 4x.
 * It still takes its box from here.
 *
 * The <img> carries width and height ATTRIBUTES as well. Without them
 * an image with `width: auto` off a fixed height computes to ZERO
 * WIDTH until it loads - a real layout shift, found on the Articles
 * build's six category icons, not a measurement artefact. */
.ys-does__icon {
	display: block;
	margin: 0;
	height: 200px;
}
.ys-does__icon > img {
	width: auto;
	height: 100%;
	max-height: 200px;
	object-fit: contain;
}
/* 10.1a caps a band's headings and copy at the reading measure through
 * `.itv-band > .wp-block-group__inner-container > h2`. Nothing here
 * matches it - the copy is one level further in, inside the column -
 * so both are restated. The frame's own column is 979 wide against
 * --measure's 960, so this is the frame's number as well as the
 * house's. */
.ys-does__col > h2 {
	margin: 0 0 var(--space-32);
	max-width: var(--measure);
}
.ys-does__col > p { max-width: var(--measure); }

/* ---- 18.4  THE TWO CROSS-LINKS  (594:77) ----
 *
 * .hp-btn-line paints a paragraph that is nothing but one CTA link as
 * the design's amber pill; the row just puts two of them side by side
 * and takes their block margins off.
 *
 * The gap is the frame's: 594:78 ends at x254 and 594:80 starts at
 * x304, so 50, and --space-48 is the token nearest it. .mh-btn-row
 * sets 12 for the same component on Magic Hug because THAT frame draws
 * 12; the two frames genuinely differ and each page follows its own.
 * Worth reconciling in a sweep, not worth inventing a house number
 * for here. */
.ys-btn-row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-48);
	margin-top: var(--space-40);
}
.ys-btn-row .hp-btn-line { margin: 0; }

/* ---- 18.5  THE CREDENTIALS ARE NOT A PANEL  (594:99) ----
 *
 * !! THIS IS THE TRAP ON THIS PAGE, AND NOTHING ERRORS WHEN YOU HIT
 * IT.
 *
 * 594:99 draws a heading, six bullets and a closing line ON THE WHITE
 * GROUND. Sampled white at the gutter and white inside the inset:
 * there is no navy credentials panel here, no .itv-credentials, no
 * portrait of Dr. Michael breaking any edge. Same as Reviews.
 *
 * .itv-creds IS the right component and it CANNOT be used unchanged:
 *
 *     .itv-creds > li { color: var(--on-invert); }     main.css:5165
 *
 * It has only ever lived inside .itv-credentials, so its colour is
 * hardcoded to white and nobody has needed it otherwise. On this
 * page's white ground that is an INVISIBLE LIST - the markup is right,
 * the CSS is right, and the page looks like it has a gap in it.
 *
 * !! RESTATED BY NAME FROM THE PAGE, NEVER BY EDITING THE COMPONENT.
 * Magic Hug's and Conditions Treated's panels are the same class and
 * repainting it would break both. That is the standing rule: where a
 * shared component needs to look different, the PAGE states the
 * difference. 10.10 promoted this list out of .mh-creds on exactly
 * that understanding.
 *
 * !! --ink, AND THE FRAME DRAWS --ink-muted. That looks like ignoring
 * the frame and is the opposite.
 *
 * Sampled on the render, every glyph in this list is rgb(92,91,87) -
 * #5c5b57, which is --ink-muted to the digit. But so is every other
 * running paragraph in the frame: the "One Tool" copy samples the same
 * value, and only the closing line under this list is rgb(20,20,19).
 * So muted is the frame's BODY colour, not a statement about the list.
 * The site's body colour is --ink, and has been since section 02
 * across all eight built pages. Following the frame here would make
 * this the one grey block on a page of black paragraphs, which reads
 * as a bug rather than as a hierarchy.
 *
 * It is also the better number: --ink is 18.43:1 on white against
 * --ink-muted's 6.80, which is under this site's own 7:1 floor. A
 * credential list is the most trust-bearing text on the page and is
 * the last place to spend contrast.
 *
 * !! THE MARKER MOVES WITH IT. .itv-creds > li::before is an 8px
 * --accent dot. Amber on navy is fine; amber on white measures 1.85:1,
 * and 1.2's standing rule is that amber is a SURFACE colour, not an
 * ink. It is decoration rather than information - the same argument
 * that let the Reviews stars ship - but the frame does not draw it
 * amber either: the bullets sample the same colour as their text. So
 * it tracks the text and the question does not arise.
 *
 * THE LIST IS SIX ITEMS HERE AND FIVE ON PAGES 12, 13, 16 AND 17. The
 * sixth is the "437 Google reviews... Individual results vary."
 * disclaimer, which on those pages sits OUTSIDE the list as its own
 * paragraph under the stat strip. This page has no stat strip, and the
 * approved copy puts the line inside the list. The copy wins.
 *
 * The section carries .itv-prose as well as this class, and that is
 * doing real work rather than being decorative: it pays the closing
 * edge into the navy CTA (10.1), sets the H2's 32px and caps the
 * closing paragraph at the reading measure. The list is exempt from
 * that cap by being a <ul>, which is right - it is scanned, not read.
 */
.ys-creds .itv-creds > li { color: var(--ink); }
.ys-creds .itv-creds > li::before { background: var(--ink); }


/* ==================================================================
 * 18b  Y-STRAP - RESPONSIVE
 *
 * !! NO DESIGN EXISTS BELOW 1440px AND NONE IS COMING. Everything here
 * is a judgement call, written down so it can be argued with.
 *
 * <=1199   THE HERO STOPS BEING AN OVERLAY.
 *
 *          !! THE ORIGINAL REASON FOR THIS BREAKPOINT NO LONGER HOLDS
 *          AND THE BREAKPOINT DOES. Written the same day, it was that
 *          2.5cqw put the headline at 29px by a 1200 viewport and 24 by
 *          992, and a page's H1 at 24px against a 22px body is not a
 *          headline. 15.2a then found that the cqw cap was never what
 *          kept this headline out of the failing scrim - max-width is -
 *          and raised it to 4.8cqw, which is 45px at 992. The number
 *          that forced 1199 is gone.
 *
 *          It stays at 1199 for a different and smaller reason, stated
 *          so the next person can overrule it: this page's box is
 *          41.6% of the photograph against Articles' 56%, so the same
 *          headline takes more lines in it. At 992 it is five lines and
 *          278px inside a 336px photograph - it FITS, but it is a dense
 *          block in the left third of a picture. Stacked reads better
 *          there. That is a judgement, not a measurement, and moving
 *          this to 991 to match Articles would not break anything.
 *
 *          1199 is the site's own breakpoint either way: it is where
 *          the nav collapses and where 10b already re-lays the sibling
 *          pages out.
 *
 * <=767    THE ICON SPLIT STACKS. 767 is where LAYOUT stacks on this
 *          site. The rail costs 161px of the column, so at 768 the
 *          copy still has 559 of 720 and reads; at 600 it would have
 *          391, which is about 35 characters a line at the 22px
 *          tablet body. Below 768 the icon goes above the copy rather
 *          than out - it is the page's only drawing and the H2 does
 *          not say what it says.
 *
 * <600     One column everywhere and MOST THINGS CENTRE, the standing
 *          rule for this site on a phone. 10b already names .itv-prose
 *          and .itv-notfor and reaches this page unchanged, so what is
 *          left here is this page's own three: the hero, the split and
 *          the button row. The hero's photograph is also replaced by
 *          the dashed slot - see 18.2.
 * ================================================================== */

@media (max-width: 1199px) {
	/* !! THE UNDO 18.2 OWES. .ys-hero sets `display: grid` and both
	 * children take cell 1/1; unstacking means putting the section back
	 * to a flow so they stack. A media query adds NO SPECIFICITY and
	 * .ys-hero is (0,1,0) either way, so this wins only because it is
	 * later in the file. Stated so it is never "tidied" upward - that
	 * exact move has now cost three sessions on this project, most
	 * recently on the About build where a page rule beat 10b's kit rule
	 * and a copy column measured ZERO PIXELS wide at 390. */
	.ys-hero {
		display: flex;
		flex-direction: column;
		/* The query container goes with the overlay. Nothing under here
		 * is sized against the photograph any more, and leaving an
		 * inline-size container on a plain block benefits no reader. */
		container-type: normal;
	}
	/* !! THE MARKUP ORDER IS PHOTO-THEN-SLOT AND THE STACK IS
	 * HEADLINE-FIRST. The grid put both in one cell so source order
	 * never showed; the moment they flow it does. The photograph is
	 * decorative here and the headline is what the reader came for.
	 * Reordering visually rather than in the markup is safe in exactly
	 * this case: the two are not a reading sequence and a screen reader
	 * gets the alt text either way. */
	.ys-hero__photo { order: 2; }
	.ys-hero h1 {
		max-width: none;
		margin: 0 0 var(--space-24);
		padding-left: 0;
		/* Back to the site's own H1. The cqw cap exists to keep the
		 * headline inside a scrim that is not behind it any more, and
		 * left in place it would set the phone H1 at about 9px. */
		font-size: var(--type-h1);
	}
}

@media (max-width: 767px) {
	/* !! THE UNDO 18.3 OWES, AND IT IS THE ONE THIS PAGE WAS MOST
	 * LIKELY TO SHIP WITHOUT. 18.3's track definition is (0,2,0) and
	 * nothing in the kit stacks this grid, so without this line the
	 * 91px rail and a 70px gutter survive to 390 and the copy column
	 * measures 181px. Written up front and measured, the way pages 18
	 * and 19 did it. */
	.ys-does > .wp-block-group__inner-container {
		grid-template-columns: 1fr;
		gap: var(--space-32);
	}
}

@media (max-width: 599px) {
	/* !! THE PHOTOGRAPH CHANGES SHAPE AND CHANGES FILE. See 18.2: at
	 * its own 2.808 a 342px phone gets a 122px band, so below 600 the
	 * <source> serves media 270 - 1000x1000, the same scene shot tight -
	 * and the slot goes square to match it.
	 *
	 * !! THIS 599 AND THE ONE IN THE PAGE CONTENT'S <source media> ARE
	 * ONE DECISION IN TWO PLACES AND MUST MOVE TOGETHER. Nothing errors
	 * if they drift - the picture just comes out the wrong shape, with
	 * the square file cropped into a letterbox or the wide one squeezed
	 * into a square. 17.2 carries the same warning for .ar-hero.
	 *
	 * !! AND EVERY DECLARATION 18.2 MADE ON THE IMG HAS TO BE UNDONE
	 * HERE, WHICH IS WHAT MAKES THIS UNLIKE THE ARTICLES SWAP.
	 *
	 * 18.2 does not simply fill the slot: it OVERSIZES the img to
	 * 104.9123% and pulls it back with two negative margins, because the
	 * wide file carries a 28px transparent margin that has to be cropped
	 * off. Media 270 has no such margin. Left in place, those three
	 * declarations would render the square 5% too large and about 8px up
	 * and to the left of its own box at 390 - a crop of the subject's
	 * head, not an obvious failure. max-width is restated for the same
	 * reason in reverse: 18.2 turns section 02's clamp OFF, and here the
	 * image is meant to fit.
	 *
	 * object-fit:cover has nothing to do here - a square file in a
	 * square box - and is set anyway so the slot survives a future file
	 * that is not exactly 1:1. */
	.ys-hero__photo { aspect-ratio: 1 / 1; }
	.ys-hero__photo > img,
	.ys-hero__photo > picture > img {
		width: 100%;
		max-width: 100%;
		height: 100%;
		margin: 0;
		object-fit: cover;
	}

	/* MOST THINGS CENTRE - the standing phone rule. .itv-intro,
	 * .itv-panel, .itv-prose and .itv-notfor are all absent because 10b
	 * already names them and reaches this page unchanged; .itv-intro
	 * and .itv-panel are centred at every width anyway. These three are
	 * this page's own. */
	.ys-hero, .ys-does__col { text-align: center; }
	.ys-hero h1, .ys-does__col > h2 { margin-inline: auto; }
	/* text-align does not move a flex item, so the row is centred on
	 * its own axis. Same call 17b makes on .ar-workup__btns. */
	.ys-btn-row { justify-content: center; }
	/* The drawing is 91px in a 342px column; centred it reads as the
	 * section's mark, left it reads as a stray. */
	.ys-does__icon { margin-inline: auto; }

	/* !! THE RUNNING COPY DOES NOT CENTRE WITH THEM. A centred
	 * paragraph of four lines gives the eye a new left edge on every
	 * line. Same call 10b makes, and the same exclusions - the button
	 * line is a pill, not a sentence, and stays centred with the
	 * heading. */
	.ys-does__col > p:not(.hp-btn-line) {
		margin-inline: auto;
		text-align: left;
	}
}


/* ==================================================================
 * 19  THE FIVE TEXT PAGES
 *     ACCESSIBILITY - CAREERS - ONLINE FORMS - PRIVACY POLICY - TERMS
 *
 * VISUAL SOURCE: five frames, ONE design.
 *   Accessibility   623:183      Careers          626:154
 *   Online Forms    627:149      Privacy Policy   629:222
 *   Terms           630:183
 *
 * Every one of them is the same three sections - Header/Nav, Page
 * Header, Body, Footer. No photographs. No bands, no panels, no cards,
 * nothing full-bleed, and no closing CTA drawn in any frame. There are
 * ZERO images across all five: checked the media library, no attachment
 * matches accessibility, careers, form, privacy or terms, and the
 * frames draw none. They are the first pages on this site with no
 * image at all. Do not invent a slot.
 *
 * !! THE CLASS PREFIX IS `.itv-doc*` AND IT BREAKS THE HOUSE HABIT ON
 * PURPOSE.
 *
 * Every other page here has its own prefix - .ca-, .mh-, .ct-, .wte-,
 * .ab-, .rv-, .ar-, .ys- - because every other page is its own design.
 * THESE FIVE ARE ONE DESIGN USED FIVE TIMES. Five identical prefixes
 * would be five copies of the rules below, and the next change would
 * have to be made five times, correctly, in five places.
 *
 * `.itv-*` is already this file's marker for "shared across pages"
 * (.itv-section, .itv-prose, .itv-band, .itv-panel), which is exactly
 * what this is.
 *
 * !! ADDING TO A SHARED NAMESPACE WIDENS THE REGRESSION CHECK. Nothing
 * below may change an existing .itv-* rule; every selector here is NEW.
 * Verified before shipping: `itv-doc` appears nowhere else in this
 * file, in any template, or in any of the nineteen pages' stored
 * content.
 *
 * !! MOST OF THIS ALREADY EXISTED AND ALMOST NONE OF IT IS WRITTEN
 * HERE. `.itv-section` + `.itv-prose` give the container, the centring,
 * the gutter, the half rhythm, the full pad into the footer, and the
 * --measure cap on running copy. Four things were missing, and only
 * those four plus the form wrapper are below:
 *
 *   19.1  the page header - H1, the "last updated" line, the lede
 *   19.2  the space ABOVE a section heading
 *   19.3  the bullet lists
 *   19.4  H3 (Privacy only, two of them)
 *   19.5  the Online Forms embed wrapper
 *
 * !! WHAT THESE FIVE LOOKED LIKE BEFORE THIS SECTION EXISTED: nothing.
 * Not "unstyled-ish" - measured on /accessibility/ at 1600, they had NO
 * page CSS at all. `.page-content` and `.prose`, which page.php puts on
 * the markup, appear NOWHERE in this stylesheet. Running copy set at
 * about 127 characters a line against the house measure of 90, and
 * every H2 had margin-top: 0.
 *
 * !! THE --measure CAP KEYS ON A CLASS WORDPRESS ADDS AT RENDER TIME.
 * 10.1 writes it as `.itv-prose p.wp-block-paragraph:not(.hp-btn-line)`.
 * The database holds a bare <p>; WordPress appends wp-block-paragraph
 * when it renders POST CONTENT. These five ARE post content, so it
 * applies - but if any of them ever moves into a PHP template the cap
 * dies silently, which is exactly what section 17 hit on Articles. So
 * every rule below that needs a width caps the ELEMENT, never the
 * WordPress class.
 * ================================================================== */

/* ---- 19.1  THE PAGE HEADER ----
 *
 * 623:134, 626:65, 627:66 and the two legal equivalents all draw the
 * same block at x150 w880:
 *
 *   breadcrumb      y64    ink-muted    NOT BUILT - see below
 *   H1              y96    ink
 *   "Last updated"  y148   ink-muted    Accessibility and Privacy only
 *   lede            y182   ink          up to 2 lines
 *
 * Colours are sampled off the render, not guessed: rgb(92,91,87) =
 * --ink-muted on the breadcrumb and the last-updated line, rgb(20,20,19)
 * = --ink on the H1 and the lede.
 *
 * !! THE VISIBLE BREADCRUMB IS NOT BUILT, and that is a standing
 * decision carried from pages 13, 14, 15, 16, 17, 18 and 19 rather than
 * a new one. The BreadcrumbList JSON-LD is what engines read, all five
 * pages already carry it, and every trail matches its frame exactly -
 * Home > Accessibility, Home > Careers, Home > Booking > Online Forms,
 * Home > Privacy Policy, Home > Terms. Leave the schema alone.
 *
 * !! THE FRAME'S H1 IS ~40px AND THIS SITE'S IS 50. THE SITE WINS - AND
 * SO DOES ITS 40px H2 AGAINST THE FRAME'S ~36.
 *
 * The frame runs these five utility pages at a smaller overall scale
 * than the nine designed pages. Following it would put a FOURTH H1 size
 * on this site, and a fourth H1 size is precisely what section 15.2a
 * closed on 2026-08-27: Gerek said the Y-Strap headline read small,
 * and the three overlay heroes turned out to be running 35-45 against
 * every other page's 50.
 *
 * NOTHING HERE SETS A FONT SIZE ON A HEADING. These pages already
 * render at 50 and 40 because the base ladder in 1.8b gives it to them.
 * Do not "correct" them to the frame.
 *
 * !! THE HEADER AND THE BODY ARE 100px APART, NOT THE FRAME'S 40.
 *
 * The frame puts 40 between the lede and the first H2 - LESS than the
 * 48 it puts between two copy blocks - so it does not treat the page
 * header as a section at all. The house rule does: two sections on a
 * shared ground pay half the rhythm each, 50 + 50. Every other page on
 * this site opens that way, and these five are reviewed alongside them.
 * It is the same call as the type sizes above, for the same reason -
 * the frame's tighter scale is a frame decision, not a site one.
 */
.itv-doc-head h1 { max-width: var(--measure); }

/* The "last updated" line is REAL COPY, not furniture - it is stored as
 * an ordinary <p> straight after the H1 on Accessibility and Privacy,
 * and Careers, Online Forms and Terms have none. Without a class it
 * sets as the page's first body paragraph, which is not what it is.
 * --type-small is 19 against the 22 body: the frame's ratio is 15/17
 * cap, which is 0.88, and 19/22 is 0.86. */
.itv-doc-head__updated {
	max-width: var(--measure);
	font-size: var(--type-small);
	color: var(--ink-muted);
}

/* The lede is the page's opening statement and the frame draws it ONE
 * STEP ABOVE body - 22 against a 17px body, a 1.29 ratio. --type-body-lg
 * is this site's one-step-up size and 24/22 is the nearest the scale
 * offers. It keeps --ink; only the line above it is muted.
 *
 * margin-bottom: 0 because the lede is the LAST thing in the header on
 * all five. Its own 16 would sit INSIDE the section's padding and make
 * the gap to the body 116 rather than the 100 the rhythm asks for. */
.itv-doc-head__lede {
	max-width: var(--measure);
	font-size: var(--type-body-lg);
	margin-bottom: 0;
}

/* ---- 19.2  THE SPACE BETWEEN COPY BLOCKS ----
 *
 * !! THIS IS THE SINGLE MOST VISIBLE FAULT THE FIVE PAGES HAD.
 *
 * Measured before this build: every H2 rendered `margin: 0 0 16` - ZERO
 * top margin, nine times on Accessibility and fourteen on Privacy. A
 * section heading sat exactly as far below the paragraph above it as
 * two paragraphs sit apart, so a 10,000-character privacy policy had no
 * visible structure at all.
 *
 * Nothing in the kit sets a top margin on a heading, and that is not an
 * oversight either: 10.1's `.itv-prose h2` is `margin: 0 0 32`, and on
 * every other page a heading opens a section that already has a hundred
 * pixels of section padding above it. These pages have fourteen
 * headings inside ONE section.
 *
 * THE NUMBER COMES OFF THE FRAME. 623:139's copy blocks sit at y0 h128
 * and y176 - 48 apart - on a 22px frame line, which is 2.18 lines. This
 * site's line is 22 x 1.375 = 30.25, so 2.18 lines is 66px, and
 * --space-64 is what that rounds to on the spacing scale.
 *
 * !! IT IS A MARGIN-TOP AND THE TWO MARGINS DO NOT ADD UP. Adjacent
 * vertical margins COLLAPSE, so the paragraph's 16 and this 64 resolve
 * to 64, not 80. Written on the heading rather than the paragraph
 * because that is the half that survives a paragraph being deleted off
 * the end of a block.
 *
 * 64 above and the kit's 32 below leave the heading twice as close to
 * what it introduces as to what it follows, which is what makes a
 * heading read as belonging downwards.
 *
 * !! EVERY RULE IN 19.2, 19.3 AND 19.4 IS WRITTEN AS A CHILD CHAIN
 * THROUGH .wp-block-group__inner-container, AND A DESCENDANT SELECTOR
 * HERE IS A REAL BUG RATHER THAN A STYLE PREFERENCE.
 *
 * It was written `.itv-section.itv-doc h2` first and that reached
 * INSIDE THE REVIEW WAVE FORM. The embed turned out not to be the
 * button 19.5 expected - it renders the whole registration form inline,
 * about 6,250px of a third party's markup - and that markup contains
 * its own headings, lists and paragraphs. Measured on the built page:
 * a descendant selector found SIX h2s inside a section whose own copy
 * has two, and twenty-eight <br>s in a page that stores none. Every one
 * of them would have taken this 64px margin, this list indent and this
 * measure cap.
 *
 * The page's own blocks are all DIRECT CHILDREN of the group's inner
 * container; nothing the widget injects ever is. So the child chain is
 * what separates "our copy" from "their DOM", and it is the same shape
 * 10.1a already uses for the bands.
 *
 * It settles the specificity question at the same time. Three classes
 * and an element beats 10.1's `.itv-prose h2` outright, so which rule
 * wins no longer depends on source order - the trap logged in sections
 * 10, 15 and 16b, in its fourth costume.
 */
.itv-section.itv-doc > .wp-block-group__inner-container > h2 {
	margin-top: var(--space-64);
}
/* The first heading in the body must not add 64 to the section's own
 * padding. On Online Forms it never matches - the form embed is the
 * first child there - which is correct: the embed opens that page's
 * body and the heading after it keeps the full 64. */
.itv-section.itv-doc > .wp-block-group__inner-container > h2:first-child {
	margin-top: 0;
}

/* ---- 19.3  THE BULLET LISTS ----
 *
 * Six lists across three pages - one on Accessibility, one on Terms,
 * four on Privacy - and the kit has no treatment for a plain content
 * list. `.itv-rows` is the navy row component and `.itv-creds` is the
 * credential list; neither is this. Left alone, these render as a
 * browser default: 1400px wide with 40px of padding, so a bullet's text
 * runs the whole container while the paragraph introducing it stops at
 * 960.
 *
 * The measure cap is the point of the rule. The rest comes off
 * 623:151 and 629:81: a 30px pitch on a 22px frame line is 8px between
 * items, which at this site's 30.25px line is 11 - --space-12. The
 * frame indents its text 24 into an 880 box, 856 of text; --space-32
 * into a 960 box gives 928, the same proportion to within half a
 * percent.
 *
 * !! max-width CAPS THE PADDING BOX - everything on this site is
 * border-box - so 960 here is outside edge to outside edge and the text
 * gets 928. That is what is wanted, and it is worth writing down
 * because the same arithmetic was got backwards on the Y-Strap hero on
 * 2026-08-27, where it failed by WRAPPING rather than by erroring.
 *
 * `disc` rather than a typed-in glyph: the frame draws a plain filled
 * bullet, and disc is a plain filled bullet. */
.itv-section.itv-doc > .wp-block-group__inner-container > ul {
	max-width: var(--measure);
	margin: 0 0 var(--space-16);
	padding-left: var(--space-32);
	list-style: disc;
}
.itv-section.itv-doc > .wp-block-group__inner-container > ul > li {
	margin-bottom: var(--space-12);
}
/* The last item's gap is the list's own 16, not 12 + 16. */
.itv-section.itv-doc > .wp-block-group__inner-container > ul > li:last-child {
	margin-bottom: 0;
}

/* ---- 19.4  H3  -  PRIVACY ONLY, TWO OF THEM ----
 *
 * 629:80 "Information you give us" and 629:86 "Information collected
 * automatically", each introducing a bullet list inside the "Information
 * We Collect" block. Nothing in the kit styles an H3 inside prose, and
 * no other page of the five has one.
 *
 * The frame gives an H3 the SAME 12px above it that it gives a
 * paragraph, because at the frame's scale an H3 is 20 against a 17px
 * body - a step so small it needs no extra air. This site's H3 is 30
 * against 22, and 16px above it would read as a paragraph set in bold.
 *
 * --space-32 is chosen to sit in a ladder rather than to match a
 * measurement:
 *
 *   64  before an H2   - a new part of the document
 *   32  after an H2, and before an H3
 *   16  after an H3, and between paragraphs
 *
 * So an H3 gets exactly half an H2's approach and binds downwards to
 * its list the way a paragraph binds to the next one. margin-bottom
 * stays at the base 16 and is not restated. */
.itv-section.itv-doc > .wp-block-group__inner-container > h3 {
	max-width: var(--measure);
	margin-top: var(--space-32);
}

/* ---- 19.5  THE ONLINE FORMS EMBED  (627:71) ----
 *
 * The frame draws an 880x420 box labelled "[FORM EMBED - New Patient
 * Registration Form, third-party Aloha/Genesis]". THE LABEL IS WRONG
 * AND SO IS THE BOX.
 *
 * !! IT IS NOT AN ALOHA/GENESIS FORM. IT IS REVIEW WAVE. The working
 * embed was read off the client's own live site at
 * hbchiropractic.com/online-forms/ on 2026-08-27 and carried over on
 * Gerek's say-so, same account:
 *
 *   <script src="https://cdn.reviewwave.com/js/reviewwave.js"
 *           data-id="6328801b1ffd011945529c501272177500d0"></script>
 *
 * There are TWO Review Wave scripts on that site and only this one is
 * the form. The other is chat_embed.js, data-id 4bb4d7e07667bb - the
 * site-wide chat widget, on every page there and on none here. It is
 * NOT part of this build and adding it is a separate decision.
 *
 * !! NO HEIGHT IS SET, AND THE 420 IN THE FRAME IS NOT WHAT THE FORM
 * NEEDS - IT NEEDS FIFTEEN TIMES THAT.
 *
 * The live page's own copy reads "Click the button below to start", so
 * this was expected to render a small control that opens the form
 * elsewhere. IT DOES NOT. Measured on the built page: the script
 * renders the ENTIRE registration form inline, `.rw-embed-wrap
 * .rw-form-wrap`, and it stands about 6,250px tall at a 1400 viewport.
 * It pulls a second file down with it, signature_pad.min.js, for the
 * consent signature.
 *
 * So a fixed slot of any height would have been wrong in both
 * directions. The wrapper caps the WIDTH to the measure - so the form
 * starts on the same left edge as the copy above it, which is what the
 * frame's 880 box was actually saying - and sets no height at all.
 *
 * !! NOTHING INSIDE IT IS STYLED, AND 19.2 HAD TO BE REWRITTEN TO KEEP
 * THAT TRUE. Six thousand pixels of a third party's markup carries its
 * own headings, lists and paragraphs, and a descendant selector on the
 * section reaches every one of them. See the note in 19.2.
 *
 * It is a third party's DOM, it carries its own consent step and its
 * own submit button - both the 3.6 document's build note and the
 * frame's label say so - and it will change without warning. Section
 * 02's `img, svg { max-width: 100% }` still reaches anything it
 * injects, which is the behaviour wanted here rather than the hazard it
 * was on the Y-Strap hero. */
.itv-doc__embed { max-width: var(--measure); }


/* ==================================================================
 * 19b  THE FIVE TEXT PAGES - RESPONSIVE
 *
 * !! IT IS THIS SHORT BECAUSE THERE IS NO LAYOUT TO STACK. These pages
 * are one column of running copy at every width: no grid, no split, no
 * panel, no photograph. 767 and 991 have nothing to do here and no
 * block was invented to fill them. 1199 has nothing either.
 *
 * What IS left is type and alignment at 599, which is where type
 * decisions live on this site.
 *
 * The body section is `.itv-prose`, so 10b's <=599 rules ALREADY reach
 * it: the section centres, its H2s centre, and its paragraphs stay
 * ranged left inside the centred column. That was checked on the
 * selector list, not assumed. Two things 10b cannot know about are
 * fixed below.
 * ================================================================== */

@media (max-width: 599px) {
	/* 1. THE PAGE HEADER IS NOT .itv-prose, so 10b never reaches it -
	 * and without this the H1 sat ranged left while every H2 under it
	 * centred. The H1 and the last-updated line centre with the
	 * headings; the LEDE DOES NOT. It is running copy - two lines at
	 * 1400 becomes five or six on a 390 phone - and a centred paragraph
	 * gives the eye a new left edge on every line. Same call 10b makes
	 * for .itv-prose's paragraphs and 10b's panel copy. */
	.itv-doc-head { text-align: center; }
	.itv-doc-head__lede { text-align: left; }

	/* !! THE LAST-UPDATED LINE NEEDS NOTHING HERE, AND IT WAS WRITTEN
	 * WITH A SIZE OVERRIDE FIRST, WHICH WAS WRONG.
	 *
	 * The worry was that --type-small would stay at 19 while the body
	 * dropped to 18 and leave the date line LARGER than the copy it is
	 * subordinate to. 1.8b's <=599 block already steps it - and steps
	 * --type-detail too, which is what the override reached for. On the
	 * rendered page that override measured 16px against an 18px body,
	 * one size smaller than intended.
	 *
	 * Left to the ladder it measures 17 against 18: still smaller, still
	 * --ink-muted, and it moves with the body size instead of against
	 * it. Recorded rather than deleted, because "small text needs its
	 * own mobile size" is the obvious thing to add back. */

	/* 2. THE BULLET LISTS MUST NOT CENTRE. They inherit text-align from
	 * .itv-prose, and a centred bullet list is the worst version of the
	 * centred-paragraph problem: the marker sits outside the box, so
	 * every item's text starts at a different distance from its own
	 * bullet. The list keeps its left edge and the BOX centres, which
	 * is exactly what 10b does with .itv-rows.
	 *
	 * Child chain, not a descendant selector, for the reason in 19.2:
	 * the Review Wave form injects lists of its own. */
	.itv-section.itv-doc > .wp-block-group__inner-container > ul {
		text-align: left;
		margin-inline: auto;
	}

	/* The form control is an object, so it centres with the headings -
	 * but only the WRAPPER is touched. Nothing reaches inside it. */
	.itv-doc__embed { margin-inline: auto; }
}


/* ==================================================================
 * 20  BOOKING - NEW PATIENT SPECIAL  (funnel step 1)
 *
 * VISUAL SOURCE: Figma 809:76, "(unverified) Booking - New Patient
 * Special (Funnel 1)". 1440 x 4033.
 *
 *   810:80   Section - Offer / Opt-In        navy panel, the form
 *   811:81   Section - Value Stack           navy BAND, white card
 *   811:165  Section - What Happens          white, 7 steps + photo
 *   812:83   Section - Booking Questions     cream BAND, 7 Q&A
 *   839:117  Section - Final CTA             footer.php, already built
 *
 * !! THIS IS THE FIRST PAGE ON THIS SITE WITH A WORKING FORM IN IT.
 * The markup and every line of its behaviour live in
 * inc/booking-form.php; this section styles what that shortcode
 * renders and nothing else. Read that file's header before changing
 * anything named .bk-form*.
 *
 * !! THE FINAL CTA IS NOT BUILT HERE AND MUST NOT BE. 839:117 draws an
 * H2, a subhead, an amber button, a "Call" button, a "Questions first?"
 * line and then the footer panel - which is footer.php's closing block,
 * unchanged, on all nineteen pages. Sampling the frame's ground down a
 * gutter column returns the same gradient as --gradient-navy-panel, the
 * same one the Value Stack band uses. So this page needs
 * _itv_cta_heading and _itv_cta_subhead set and NOTHING drawn. The copy
 * for both came out of the page body when it was set.
 * ================================================================== */

/* ---- 20.1  the rhythm ----
 *
 * Four grounds and three colour changes:
 *
 *   offer          WHITE   (the panel is navy, the SECTION is not)
 *   -------------------------------------------- ground change
 *   value stack    NAVY BAND
 *   -------------------------------------------- ground change
 *   what happens   WHITE
 *   -------------------------------------------- ground change
 *   FAQ            CREAM BAND
 *   -------------------------------------------- ground change
 *   closing CTA + footer   NAVY   (footer.php)
 *
 * .itv-band already pays the full pad on both sides at every ground
 * change, so only the two WHITE sections need their own edges named.
 *
 * !! THE OFFER SECTION PAYS NOTHING AT THE TOP. .site-main already
 * gives every page 48, and the frame starts this panel 20px under the
 * header - the tightest opening on the site, because the panel IS the
 * headline and there is no hero photograph above it to breathe against.
 * 48 is as close as the house rhythm gets without special-casing
 * .site-main, and closing the gap further would butt this navy panel
 * straight onto the header's own navy with no seam between them.
 *
 * (An earlier version of this note called that header STICKY. It is
 * not - .site-header is position:relative. Three other comments in
 * this file say sticky as well and all four are wrong; the header was
 * presumably meant to be and never became. Worth knowing before
 * anything is built that depends on it.) */
.bk-offer  { padding-top: 0; padding-bottom: var(--section-pad); }
.bk-steps  { padding-block: var(--section-pad); }

/* ---- 20.2  THE OFFER PANEL  (810:80) ----
 *
 * !! 810:80 IS EMPTY IN THE FIGMA NODE TREE. get_metadata returns the
 * frame with no children at all, so the layout below was measured off
 * the RENDER (1170x572) rather than read off nodes. Every number here
 * is a pixel measurement of that image, converted to a proportion:
 *
 *   panel            1140 wide, 542 tall, 48 of padding
 *   copy column       366   (35.1% of the 1044 content)
 *   photo gap         252   (24.1%)
 *   form card         426   (40.8%)
 *
 * !! THE PHOTO OVERLAPS THE CARD AND THAT IS THE DESIGN. The cutout is
 * 373 wide in a 252 column, so 121px of it sits BEHIND the form card.
 * That is why the card carries a z-index and the panel clips: without
 * the overlap the composition is three separated blocks, and with it
 * the doctor stands between the promise and the form.
 *
 * !! THERE IS A FLAT EXPORT OF THIS WHOLE PANEL AND IT IS DELIBERATELY
 * NOT USED. media 236, Hero-Blue-YStrap-Composite, is 1170x572 - the
 * entire panel INCLUDING the form, baked in as pixels. Using it would
 * make the fields an image of fields. media 235,
 * Hero-Background-Blue-Panel, is the navy alone at 1140x558 and is also
 * unused: the ground is a flat brand colour, and a 1140px raster
 * stretched to 1400 bands where a token does not. Only media 237, the
 * cutout, is real artwork that CSS cannot draw.
 */
.bk-offer__panel {
	display: grid;
	/* 35 / 24 / 41 - the frame's own proportions, not thirds.
	 *
	 * !! minmax(0, ...) ON EVERY TRACK, AND IT IS NOT DEFENSIVE
	 * BOILERPLATE - WITHOUT IT THIS LAYOUT SILENTLY LOSES ITS OVERLAP.
	 *
	 * Written as a bare `35fr 24fr 41fr` first. An fr track's automatic
	 * minimum is min-content, and the cutout in column 2 is a fixed
	 * 373px, so the track could not go below 373: the computed columns
	 * came back 406.6 / 373 / 476.3 instead of the intended 439 / 301 /
	 * 515. Column 2 had grown to fit the photograph exactly, which put
	 * the photo's right edge at 852 and the card's left edge at 852 -
	 * touching, with no overlap at all. The panel still looked
	 * deliberate, which is what makes it worth writing down.
	 *
	 * This is 10.1's `min-width: 0` note in its other costume: there a
	 * fixed width inside a column was a floor the column could not go
	 * below, and here it is the same floor seen from the track side.
	 * minmax(0, Nfr) says these tracks are PROPORTIONS, not content. */
	/* !! 43/16/41, NOT THE FRAME'S 35/24/41 - REBALANCED 2026-09-03.
	 *
	 * The middle track was the PHOTOGRAPH's column. Once 20.2 moved the
	 * photograph behind the panel, 24fr stopped being a picture and became
	 * 311px of nothing between the copy and the card, and the panel read
	 * off-balance - Gerek's word, and he is right: a gap that size is a
	 * column, and an empty column looks like a mistake.
	 *
	 * The 100px comes out of the GAP and goes to the COPY, which is the
	 * half of his two options that keeps the card where it is. At 1440 the
	 * content is 1296, so 35/24/41 resolved to 453.6 / 311 / 531.4 and
	 * 43/16/41 resolves to 557.3 / 207.4 / 531.4 - the copy gains 103.7 and
	 * the card does not move a pixel.
	 *
	 * The copy has no `--measure` cap, so the track widening actually
	 * widens the lines rather than just padding the column - checked
	 * before changing the number, because on most of this site it would
	 * not have.
	 *
	 * !! minmax(0, ...) ON EVERY TRACK, AND IT IS NOT DEFENSIVE
	 * BOILERPLATE - the note below is still live even though the fixed
	 * 373px cutout that first exposed it is gone. */
	grid-template-columns: minmax(0, 43fr) minmax(0, 16fr) minmax(0, 41fr);
	align-items: center;
	/* The containing block for the background photograph below. */
	position: relative;
	isolation: isolate;
	background: var(--navy);
	border-radius: var(--radius-md);
	padding: var(--space-48);
	/* Clips the cutout to the panel's corners, which is what lets it
	 * stand ON the bottom edge instead of hanging off it. */
	overflow: hidden;
}
.bk-offer__panel > .wp-block-group__inner-container { display: contents; }

/* The copy column. White on navy, so nothing here inherits --ink. */
.bk-offer__copy { color: var(--on-invert); }
.bk-offer__copy h1 { color: var(--on-invert); }
.bk-offer__copy p { color: var(--on-invert); }

/* !! THE "$50 DEPOSIT TO GET STARTED." LINE IS A PILL, NOT A HEADING.
 * White fill, --navy label - the inverse of every other pill on this
 * site, because this one sits ON navy. Sampled off the render: the
 * fill is #ffffff and the label is rgb(32,94,150), which is --navy to
 * the digit.
 *
 * !! THE SELECTOR CARRIES .bk-offer__copy AND IT IS LOAD-BEARING.
 *
 * Written as a bare `.bk-offer__pill` first, and it shipped WHITE TEXT
 * ON A WHITE PILL - completely invisible, and invisible in the one
 * place on the page that names the price. `.bk-offer__copy p` above
 * scores (0,1,1) against a bare class's (0,1,0), so the column's white
 * beat the pill's navy and nothing in either rule looked wrong on its
 * own. Caught by reading the computed colour off the built page, which
 * is the only way this kind of fault ever shows up.
 *
 * The trap in its fifth costume - sections 10, 15, 16b and 19 log the
 * other four. Any rule that recolours something INSIDE .bk-offer__copy
 * has to out-score that selector. */
.bk-offer__copy p.bk-offer__pill {
	display: inline-block;
	background: var(--surface);
	color: var(--navy);
	font-size: var(--type-h5);
	font-weight: var(--weight-semibold);
	padding: var(--space-8) var(--space-24);
	border-radius: var(--radius-pill);
}

/* The cutout. Bottom-anchored so the patient's table meets the panel's
 * lower edge, and z-index 0 so the card can cover its right side.
 *
 * !! THE WIDTH IS THE FILE'S OWN 373px AND IT IS NOT A PERCENTAGE.
 *
 * It was written `min(373px, 36%)` first - 36% being the cutout's share
 * of the panel's content in the frame - and it rendered 109px wide, a
 * thumbnail. A percentage width on a grid item resolves against ITS OWN
 * GRID AREA, not against the parent's content box, and column 2 is only
 * 301px at a 1400 viewport. 36% of the wrong box is a quarter of the
 * intended size, and it fails by looking deliberate.
 *
 * A flat 373 is also the right answer on its own terms: the file is 373
 * wide, so anything larger upscales it. It DELIBERATELY overflows its
 * column to the right - that overflow is the overlap the design is
 * built on - and the panel's overflow:hidden is what contains it. The
 * stacked rule at <=991 overrides this against a full-width column.
 *
 * A ~470-wide export of the same cutout would let it scale with the
 * panel instead of holding still; nothing here would have to change.
 * Same trade as the three parked exports in HANDOFF. */
/* !! THE PHOTOGRAPH IS THE PANEL'S GROUND NOW - 2026-09-03, media 237 -> 309.
 *
 * It was a 373x542 cutout standing in the middle column. The new export is
 * 1140x542 - the same height, three times the width - and its name says what
 * it is: Hero-Background-Activator-Photo. It is pre-darkened for type, the way
 * the homepage hero's file is.
 *
 * So it stops being a grid item and becomes the layer behind all three
 * columns. The middle track is NOT removed: it stays as an empty 24fr spacer,
 * which keeps the copy and the card exactly where the frame puts them instead
 * of re-deriving two new proportions.
 *
 * !! THE PANEL'S NAVY STAYS. The photograph covers it, so it is never seen -
 * but it is what the white copy sits on for the moment before the image
 * decodes, and if the file ever 404s the panel degrades to the old navy block
 * with readable type rather than to white-on-white.
 *
 * !! AUTO-PLACEMENT MOVES WHEN THIS LEAVES THE FLOW, AND IT TAKES THE CUE
 * WITH IT. The panel's children place as copy(auto), photo(col 2), cue(auto),
 * form(col 3). The cue only lands in column 3 because the photo had already
 * taken column 2; absolutely positioning the photo frees that track and the
 * cue drops into it, landing "Step 1 of 3" in the middle of the panel with
 * the card still on the right. The cue is pinned below for that reason. */
.bk-offer__photo {
	position: absolute;
	inset: 0;
	z-index: 0;
	width: auto;
	margin: 0;
	pointer-events: none;
}
.bk-offer__photo > img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The photo is z-index 0 and comes AFTER the copy in the DOM, so without
 * these the image paints over the headline. */
.bk-offer__copy,
.bk-offer__panel .form-cue,
.bk-offer__panel .bk-form { position: relative; z-index: 1; }
/* !! THE PANEL WAS TWO TALL ROWS AND THE CARD SAT IN THE SECOND ONE.
 *
 * Auto-placement put copy in row 1 column 1, the cue in row 1 column 3, and
 * the form in row 2 column 3 - so the grid measured 450 + 466 and the panel
 * came out 1013 tall, with the cue marooned halfway up an empty column and
 * the card pushed most of a screen below the headline it belongs to.
 *
 * **This predates the photograph swap.** The old 373x542 cutout stood in row 1
 * column 2 and was the tallest thing in that row, so the row had a reason to
 * be 542 and the shape read as deliberate. Taking the photograph out of the
 * grid did not cause the two rows - it removed the thing that was hiding them,
 * and the panel got SHORTER (1104 -> 1013) while looking worse.
 *
 * The cue belongs directly above the card, so the two are pinned to rows 1 and
 * 2 of the SAME column and the copy spans both. `align-items: center` then
 * centres the copy against the pair instead of against a row of its own. The
 * panel's height becomes the cue plus the card - about 598 - which is what the
 * frame draws. */
.bk-offer__copy { grid-row: 1 / span 2; }
.bk-offer__panel .bk-form { grid-row: 2; }

/* !! AND THE CUE IS AMBER HERE, WHICH 21.4 FORBIDS EVERYWHERE ELSE.
 *
 * Gerek's call 2026-09-03: white blended into the photograph. 21.4 sets the
 * cue `--navy` and its note explains why NOT amber - "amber on white is
 * roughly 1.9:1" - and that reasoning is exactly right on the checkout's WHITE
 * card, which is the other place this component lives. It does not transfer to
 * a dark photograph, where amber is one of the strongest things available.
 *
 * So the override is scoped to this panel and must stay that way. Do not lift
 * it to `.form-cue`: on page 26 that is amber on white and fails outright.
 *
 * !! IT WAS ALSO INVISIBLE BEFORE ANY OF THIS, and that is worth keeping.
 *
 * `.form-cue` is `--navy` by 21.4's contrast reasoning, which is right on the
 * white checkout card. This panel's ground WAS `--navy` too, so the cue was
 * navy on navy - a contrast of exactly 1.00, the same colour as the thing
 * behind it. Nothing looked broken; the heading simply was not there.
 *
 * Measured against the new photograph before and after: navy scores 1.45
 * average and 1.00 at its worst, white scores 8.94 and 2.95 - and AA wants
 * 3.0 for text this size. Sampled per pixel off the decoded image behind the
 * cue's own box, not judged by eye.
 *
 * The trap in its SIXTH costume - 20.2's own note above counts five, and the
 * pill two rules up is the one it was written for. Any rule that colours
 * something inside this panel has to answer what is now behind it. */
.bk-offer__panel .form-cue {
	grid-column: 3;
	grid-row: 1;
	color: var(--accent);
	/* The panel is a photograph now, and a photograph is not a flat colour -
	 * amber holds on the dark two-thirds and thins out where the ceiling
	 * blows out behind it. The shadow is what makes it hold everywhere
	 * without putting a plate behind the words. */
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}


/* ---- 20.3  THE FORM  (inc/booking-form.php) ----
 *
 * The white card. z-index 1 puts it over the cutout; the panel's
 * overflow:hidden is what keeps the pair inside the corners. */
.bk-form {
	position: relative;
	z-index: 1;
	grid-column: 3;
	background: var(--surface);
	border-radius: var(--radius-md);
	padding: var(--space-32);
	box-shadow: var(--shadow-card);
	/* !! EVERY CTA ON THIS PAGE SCROLLS HERE - see 20.7 - so the form
	 * is an anchor target and needs to land clear of the top of the
	 * window rather than flush against it. The header is NOT sticky
	 * (.site-header is position:relative, whatever three comments
	 * elsewhere in this file claim), so this is breathing room and not
	 * a clearance calculation. */
	scroll-margin-top: var(--space-48);
}
.bk-form__row { margin: 0 0 var(--space-16); }

/* !! --type-ui, NOT --type-body, AND NOT ANYTHING UNDER 16px.
 *
 * A form control is UI rather than running copy, so it takes the UI
 * size. The floor matters separately and for a reason that has nothing
 * to do with the design: iOS Safari ZOOMS THE PAGE when a focused input
 * is smaller than 16px, and it does not zoom back out. On the one page
 * on this site that has to work on a phone in a waiting room, that is a
 * broken layout the moment somebody taps a field.
 *
 * !! THE MARGIN IS ONE PIXEL AT THE ONE WIDTH THAT MATTERS. --type-ui
 * is 19 on desktop, and 1.8b's <=599 block steps it to 17 - measured on
 * the built page at 390. 17 still clears 16, so the zoom does not fire,
 * but ANY further step down here breaks it. If the ladder ever takes
 * --type-ui below 17, this property has to stop using it. */
/* !! THE FIELDS ARE FILLED, NOT WHITE, AND THAT IS GEREK'S CALL
 * 2026-08-27 RATHER THAN THE FRAME'S.
 *
 * The design draws white fields on a white card with a hairline border,
 * and on the built page that reads as one flat white rectangle with
 * faint lines in it - the four things a visitor has to fill in do not
 * announce themselves as fillable. Gerek asked for "a light gray, like
 * F2F2F2, just to make it pop".
 *
 * --surface-alt is #f5f4f1, three points off that and already this
 * site's off-white fill - the FAQ band, the notice below and the
 * statement panels all use it. A second near-white would be a new
 * value in a file whose section 01 is the only place a colour may
 * live, for a difference no eye can see. If the neutral grey ever
 * matters more than the palette does, it becomes a token in 01.
 *
 * The FOCUSED field goes white instead. The fill is what says "this is
 * a box you type in"; going lighter on focus is what says "this is the
 * one you are typing in now", and it costs nothing on top of the
 * border change the site's focus ring already makes. */
.bk-form__input {
	width: 100%;
	padding: var(--space-12) var(--space-16);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	background: var(--surface-alt);
	color: var(--ink);
	font-family: var(--font-body);
	font-size: var(--type-ui);
	line-height: var(--leading-body);
}
.bk-form__input::placeholder { color: var(--ink-muted); opacity: 1; }
.bk-form__input:focus {
	border-color: var(--navy);
	background: var(--surface);
}
/* The invalid state is a BORDER, not a colour on the text: the message
 * above the form already says what is wrong in words, and red text
 * inside a field would be the only red type on this site. */
.bk-form__input.is-invalid { border-color: var(--alert); }

.bk-form__submit { margin: var(--space-24) 0 var(--space-16); text-align: center; }
/* The button is a <button>, not an <a>, so it inherits none of the
 * anchor rules .cta-button was written against. Section 02's
 * `button { font: inherit }` plus .cta-button's own font-size get it
 * the rest of the way; these three lines are what a UA stylesheet
 * still puts on a submit control. */
.bk-form .cta-button {
	border-width: 2px;
	cursor: pointer;
	appearance: none;
}
.bk-form__privacy {
	margin: 0;
	color: var(--ink-muted);
	font-size: var(--type-xs);
	line-height: var(--leading-body);
	text-align: center;
}
/* The one sentence shown when a submission bounced. --alert is a
 * BORDER and a mark here rather than the type colour, for the same
 * reason as the field above. */
.bk-form__notice {
	margin: 0 0 var(--space-16);
	padding: var(--space-12) var(--space-16);
	border-left: 3px solid var(--alert);
	background: var(--surface-alt);
	color: var(--ink);
	font-size: var(--type-small);
	line-height: var(--leading-body);
}

/* ---- 20.4  THE VALUE STACK  (811:81) ----
 *
 * !! THE BAND IS NAVY, NOT CREAM, SO IT OVERRIDES .itv-band's GROUND.
 *
 * The gradient is not a new value. Sampled at the four corners of the
 * frame's render, the band runs #1f5d94 at the top right into #000102
 * at the bottom right with #253e7f lifting the bottom left - which is
 * --gradient-navy-panel's own description in 1.3 (#1f5c92 / #010204 /
 * #243e7e) to within two per channel. It is the same gradient the
 * statement panels use and the same one footer.php closes with. Do not
 * introduce a second navy ramp for this. */
.itv-section.bk-value { background: var(--gradient-navy-panel); }
.bk-value > .wp-block-group__inner-container > h2 {
	color: var(--on-invert);
	text-align: center;
	max-width: none;
	margin: 0 0 var(--space-48);
}

/* !! FROM HERE TO .bk-value__cta IS A SHARED COMPONENT, NOT PAGE 25's.
 *
 * PROMOTED OUT OF .bk-value* 2026-08-27, the same day it was written,
 * when Checkout turned out to draw the identical card - eight rows, a
 * divider, the total, the price and the terms, same copy, same
 * arithmetic. Exactly the move 10.1b made when a second page needed
 * the monogram, and made for the same reason: two copies of a
 * component are two places to change it and one place to forget.
 *
 * WHAT STAYED PAGE-SCOPED, and why the split is where it is:
 *   .bk-value       the BAND. Page 25 paints it navy; page 26 has no
 *                   band at all, the card sits on white.
 *   .bk-value__cta  the button under the card. Only page 25 has one -
 *                   on checkout the card is next to the payment form,
 *                   so a second button would compete with it.
 *
 * The two pages differ in exactly two properties and both are set on
 * the page, not here: the card's FILL (white on 25's navy band, cream
 * on 26's white page) and its WIDTH.
 *
 * 536 in a 1140 frame is 47%, but a percentage of this site's 1400
 * would run it to 658 and the rows would stop reading as a list and
 * start reading as a table. Capped at the frame's own width. */
.itv-value {
	width: min(536px, 100%);
	margin-inline: auto;
	background: var(--surface);
	/* The hairline and the money treatment below arrived on the checkout
	 * in round 2 and were scoped to `.ck-col`, which left the SAME
	 * component drawing two different ways on pages 25 and 26. Moved
	 * here in round 3 so there is one card again. What stays page-scoped
	 * is what genuinely differs: the fill and the width. */
	border: 1px solid var(--rule);
	border-radius: var(--radius-md);
	padding: var(--space-48);
	box-shadow: var(--shadow-card);
}
.itv-value > .wp-block-group__inner-container { display: contents; }

/* Each row is label left, price right, on one line. The frame runs a
 * 43px pitch on a 38px row - 5px of air - which at this site's 30.25px
 * line is 12px of padding split top and bottom. */
.itv-value__row {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: var(--space-16);
	padding-block: var(--space-8);
	margin: 0;
}
.itv-value__row > span:first-child { font-size: var(--type-body); }
/* The price is subordinate to the thing it prices - smaller and muted,
 * exactly as the frame draws it. It is also the half that must not
 * wrap: "$295 value" broken over two lines reads as two prices. */
/* !! THE PRICE IS NO LONGER SUBORDINATE, AND THAT IS THE POINT OF THE
 * CARD. It was --ink-muted at --type-small, "smaller and muted exactly
 * as the frame draws it" - correct transcription, and it made the
 * numbers the quietest thing in a component whose whole argument is the
 * numbers. Navy and bold since round 3.
 *
 * NAVY, NOT AMBER. Section 01 forbids amber as text and the numbers
 * agree: on the checkout's cream it measures 1.68:1. Navy is 6.15:1 on
 * that cream and 6.77:1 on this card's white, so it clears AA on both
 * grounds the component is used on.
 *
 * :last-child rather than a class because the markup is two bare spans
 * per row and the second is always the amount - which also means
 * "Priceless" gets the same treatment without being named, as the value
 * it is. */
.itv-value__row > span:last-child {
	font-size: var(--type-small);
	font-weight: var(--weight-bold);
	color: var(--navy);
	white-space: nowrap;
}

.itv-value__rule {
	border: 0;
	border-top: 1px solid var(--rule);
	margin: var(--space-24) 0;
}
.itv-value__total {
	margin: 0 0 var(--space-12);
	font-size: var(--type-body);
	font-weight: var(--weight-bold);
	text-align: center;
}
.itv-value__price {
	margin: 0 0 var(--space-16);
	font-size: var(--type-h3);
	font-weight: var(--weight-bold);
	line-height: var(--leading-heading);
	text-align: center;
}
/* !! THE SMALL PRINT IS THE MOST IMPORTANT COPY IN THIS CARD.
 *
 * "Your deposit today is $50. The full price is $349. The final payment
 * of $299 is due before or during your first appointment." It is what
 * stops "$50 deposit" reading as "$50 total", and this page takes
 * money. It is set small and muted because the frame sets it that way,
 * NOT because it is fine print to be got past: --ink-muted on white is
 * 7.1:1 and it is never allowed to go lighter or smaller than this. */
.itv-value__terms {
	margin: 0 0 var(--space-24);
	color: var(--ink-muted);
	font-size: var(--type-small);
	line-height: var(--leading-body);
	text-align: center;
}
.bk-value__cta { margin: 0; text-align: center; }

/* ---- 20.5  WHAT HAPPENS AT YOUR FIRST APPOINTMENT  (811:165) ----
 *
 * Seven steps in two rows of four, the eighth cell filled by a
 * photograph. The frame runs a 291px pitch on 267px cells, so the gap
 * is 24 and the columns are equal. */
.bk-steps__grid {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--space-48) var(--space-24);
	margin-top: var(--space-48);
}
.bk-steps__grid > .wp-block-group__inner-container { display: contents; }
.bk-step > .wp-block-group__inner-container { display: contents; }

/* !! THE NUMBERED BADGES ARE CSS, NOT THE SEVEN EXPORTS.
 *
 * media 238-244 are Step-Badge-1 through Step-Badge-7, seven 40x40
 * rasters of a navy circle with a digit in it. They are not used, and
 * declining them is the decision rather than an oversight:
 *
 *   1. The number stops being TEXT. Baked into a PNG it is invisible to
 *      a screen reader and to search, on a page whose whole argument is
 *      an ordered process. The site publishes an accessibility
 *      statement; this is exactly what it promises not to do.
 *   2. A 40x40 raster is soft on every 2x display, and these are flat
 *      colour and a digit - the one thing that costs nothing to draw.
 *   3. Seven files for seven digits is seven requests for a circle.
 *
 * The site's own precedent supports it: media 126-130, the process step
 * icons on the homepage and Adjustments, ARE used as images, because
 * those are drawings of an X-ray, PostureRay and a diagnosis. A digit
 * is not a drawing. */
.bk-step__badge {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	margin: 0 0 var(--space-12);
}
.bk-step__n {
	flex: 0 0 auto;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: var(--space-40);
	height: var(--space-40);
	border-radius: var(--radius-pill);
	background: var(--navy);
	color: var(--on-invert);
	font-size: var(--type-ui);
	font-weight: var(--weight-semibold);
	line-height: 1;
}

/* !! THE CHEVRON IS DRAWN, NOT TYPED, AND THAT IS AN ACCESSIBILITY
 * DECISION RATHER THAN A DRAWING ONE.
 *
 * media 246 exports it as a 49x98 raster and the frame itself draws it
 * as a text node containing the character ">". Both are wrong here for
 * the same reason: it means "and then", the numbers already say that,
 * and a screen reader that announces generated punctuation would read
 * "one greater-than consultation". Two borders rotated 45 degrees are
 * a shape with no text in them, so there is nothing to announce.
 *
 * It is pushed to the far end of the badge row, which is where the
 * frame puts it - x256 in a 267 cell, against the NEXT step rather
 * than against its own number. */
.bk-step__badge::after {
	content: "";
	margin-left: auto;
	width: 10px;
	height: 10px;
	border-top: 2px solid var(--rule);
	border-right: 2px solid var(--rule);
	rotate: 45deg;
}
/* The last step in each row points at nothing. :nth-child does not
 * reach these - each step is its own group - so the class is set in
 * the page content, on steps 4 and 7. */
.bk-step--last .bk-step__badge::after { content: none; }

.bk-step h3 {
	margin: 0 0 var(--space-12);
	font-size: var(--type-h5);
	max-width: none;
}
.bk-step p {
	margin: 0;
	color: var(--ink-muted);
	font-size: var(--type-small);
	line-height: var(--leading-body);
	max-width: none;
}

/* !! THE EIGHTH CELL IS GONE - 2026-09-03, and the frame is why.
 *
 * This section used to close with a photograph in the fourth slot of row 2,
 * reusing media 135 from Chiropractic Adjustments. `836:136` still EXISTS in
 * the frame's tree, named "Photo - Dr. Michael Reviewing X-Ray Findings
 * (reused...)", 267x222 - which is exactly why reading the tree is not enough
 * here. It renders EMPTY. The frame now closes the grid at seven steps and
 * lets the watermark hold that corner instead, and a screenshot is the only
 * thing that shows it.
 *
 * Same class of trap as `810:80` on this page: a node that exists, has a name
 * that describes a picture, and draws nothing.
 *
 * The rules and the figure both went rather than being hidden. The revert is
 * a figure.bk-steps__photo around media 135 in row 2 of .bk-steps__grid, with
 * `aspect-ratio: 267/222` and `object-fit: cover` - the file is 430x420 and
 * without the ratio it stands 313px tall in a 320px cell and sets the row's
 * height itself. */

.bk-steps__cta { margin: var(--space-48) 0 0; }

/* !! THE WATERMARK IS THE HOMEPAGE'S FILE, NOT A BOOKING EXPORT.
 *
 * There is no monogram in the booking set - media 233 is called
 * Logo-Mark but it is the 183x34 horizontal lockup, not the mark. What
 * the frame draws is the same bleeding "HB" every other page carries,
 * so this reuses media 103, home-Brand-Logo-Watermark-Background, which
 * 10.1b's note describes as "the whole mark" and therefore the one file
 * that can be cut to any page's frame by CSS.
 *
 * That means it takes the homepage's translate as well as its own
 * width - the per-page exports sit flush because they are pre-cropped;
 * this one has to be pushed off the edge to bleed. 9.6 already carries
 * `translate: 28% -50%`, so only the width is set here.
 *
 * The frame's mark is 653 wide inside a 1440 section, which is 45.3%.
 * min(653px, 45vw) is that proportion with the file's own width as the
 * ceiling. */
/* !! AND IT SITS IN THE BOTTOM-RIGHT CORNER, NOT CENTRED ON THE RIGHT EDGE.
 * Corrected 2026-09-03 on Gerek's eye.
 *
 * 9.6 anchors the mark `top: 50%` with a -50% pull, and 10.1b's (0,3,0) rule
 * re-states the translate as `0 -50%` - which also cancels 9.6's 28% of
 * horizontal bleed, so it was vertically centred AND flush to the right edge
 * with no bleed at all. Two rules, neither wrong on its own.
 *
 * The frame puts it in the corner and lets it run off two edges: 653x640.7 at
 * x866 y349.4 in a 1440x834 section, so 79 past the right edge and 156 past
 * the bottom. As fractions of the mark's own size that is 12.1% and 24.3%,
 * which is what the translate says - percentages so the bleed holds its
 * proportion as the width clamps down with the viewport.
 *
 * `top: auto` is required, not tidy: 9.6's `top: 50%` and a `bottom` together
 * would stretch the box between them. */
.bk-steps > .wp-block-group__inner-container > .hp-media--watermark {
	width: min(653px, 45vw);
	top: auto;
	bottom: 0;
	translate: 12% 24%;
}

/* ---- 20.6  BOOKING QUESTIONS, ANSWERED  (812:83) ----
 *
 * The cream ground is .itv-band's own default - sampled off the frame
 * at three points and it is #f5f4f1, which is --surface-alt exactly.
 * Nothing overrides it here.
 *
 * Two columns: the question at 364 and the answer at 728 in a 1140
 * frame, 48 apart. As proportions of the pair that is 1/3 and 2/3, and
 * the 48 is the house gutter, so it is written that way rather than as
 * three measured percentages. */
.bk-faq > .wp-block-group__inner-container > h2 {
	margin: 0 0 var(--space-32);
}
.bk-faq__item {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
	column-gap: var(--space-48);
	/* 16 above the rule and 16 below is the frame's 32 between items,
	 * with the hairline landing in the middle of it. */
	padding-block: var(--space-16);
}
.bk-faq__item > .wp-block-group__inner-container { display: contents; }
.bk-faq__item + .bk-faq__item { border-top: 1px solid var(--rule); }

.bk-faq__item h3 {
	grid-column: 1;
	margin: 0;
	font-size: var(--type-body);
	font-weight: var(--weight-bold);
	line-height: var(--leading-body);
	max-width: none;
}
/* !! EVERY PARAGRAPH OF THE ANSWER SHARES ONE GRID CELL, AND THAT IS
 * WHY THEY ARE NOT PLACED INDIVIDUALLY.
 *
 * Four of the seven answers are more than one paragraph - the
 * rescheduling one is four - and a flat grid would put each of them in
 * its own row, marching the second paragraph back under the question.
 * grid-column: 2 on all of them stacks them in the same column instead,
 * which is what the frame draws: one text node per answer, whatever the
 * copy's paragraph count. */
.bk-faq__item p {
	grid-column: 2;
	margin: 0 0 var(--space-12);
	color: var(--ink-muted);
	font-size: var(--type-body);
	line-height: var(--leading-body);
	max-width: none;
}
.bk-faq__item p:last-child { margin-bottom: 0; }
/* 12 rather than the house 16 between an answer's paragraphs: the frame
 * runs them as consecutive lines with no gap at all, and the rule above
 * each item is what separates one answer from the next. 16 made a
 * four-paragraph answer read as four answers. */


/* ==================================================================
 * 20b  BOOKING - RESPONSIVE
 *
 * 1199  the step grid goes 4 -> 2. Four columns of running text stop
 *       working before the layout does, the same measurement
 *       .ca-workup's tiles made at the same width.
 *
 *  991  the offer panel stacks. THE ORDER CHANGES WITH IT - see below.
 *
 *  767  the FAQ's two columns become one.
 *
 *  599  the step grid goes to one column, and most things centre.
 * ================================================================== */

@media (max-width: 1199px) {
	.bk-steps__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	/* The chevrons stop meaning anything the moment the row is not the
	 * reading order they point along. At two columns step 2 sits above
	 * step 3, so an arrow after 2 points at 3 across a gap it does not
	 * cross. Hidden rather than re-pointed. */
	.bk-step__badge::after { content: none; }
}

@media (max-width: 991px) {
	/* !! THE PANEL STACKS AND THE FORM MOVES UP PAST THE PHOTOGRAPH.
	 *
	 * The 3.6 document's build note is explicit: "[FORM - placed high
	 * on the page, above the fold at all breakpoints.]" Stacked in DOM
	 * order that is copy, photo, form - and the photograph is 542 tall,
	 * so it alone would push the form off a phone screen. Ordered, the
	 * page reads copy, form, photograph: the promise, the thing to do
	 * about it, and then the proof.
	 *
	 * The copy is NOT split to get the form higher still. It could be -
	 * headline, pill, form, then the two explaining paragraphs - and
	 * that needs the copy in two blocks with the form between them,
	 * which makes the page content harder to read than the gain is
	 * worth. Worth revisiting if the funnel underperforms on mobile. */
	.bk-offer__panel {
		grid-template-columns: 1fr;
		gap: var(--space-32);
		padding: var(--space-32);
	}
	.bk-offer__copy  { order: 1; }
	/* !! THE CUE HAS TO BE RELEASED HERE TOO, FOR THE REASON .bk-form
	 * ALREADY IS. 20.2 pins it to `grid-column: 3` so it does not fall
	 * into the track the photograph vacated. Stacked, the panel has ONE
	 * explicit column - and a child asking for column 3 makes the
	 * browser INVENT columns 2 and 3 to put it in. Measured at 390
	 * before this line: three tracks of 121/269/123 inside a 342 panel.
	 * The panel did not look obviously broken, which is the whole
	 * problem. */
	.bk-offer__copy  { grid-row: auto; }
	.bk-offer__panel .form-cue { grid-column: auto; grid-row: auto; order: 2; }
	.bk-form         { order: 3; grid-column: auto; }
	.bk-offer__panel .bk-form  { grid-row: auto; }
	/* !! .bk-offer__photo IS DELIBERATELY ABSENT NOW.
	 *
	 * It used to be the third stacked block, ordered below the form so a
	 * 542-tall cutout could not push the form off a phone. Since 20.2 made
	 * it the panel's BACKGROUND it is out of the flow at every width, so it
	 * has no order to take and nothing to push - and the rules that were
	 * here (width: min(373px, 80%), a negative bottom margin) would fight
	 * `inset: 0` and shrink the ground to a 373px strip in the corner.
	 * Removed rather than reset to auto, so there is nothing to re-break. */
	/* The card no longer overlaps anything, so it stops needing to win
	 * a stacking contest. */
	.bk-form { z-index: auto; }
}

@media (max-width: 767px) {
	/* One column. A 364px question column beside a 728px answer is a
	 * proportion, not a width - at 700 the pair is 233 and 466, and the
	 * question sets on four lines to hold a two-line answer. */
	.bk-faq__item { grid-template-columns: 1fr; }
	.bk-faq__item h3 { grid-column: 1; margin-bottom: var(--space-12); }
	.bk-faq__item p  { grid-column: 1; }
}

@media (max-width: 599px) {
	.bk-steps__grid { grid-template-columns: 1fr; }

	/* MOST THINGS CENTRE, the standing rule for this site on a phone -
	 * the headings and the objects, not the running copy. The step
	 * bodies, the FAQ answers and the offer paragraphs all keep their
	 * left edge: they are read, and a centred paragraph gives the eye a
	 * new left edge on every line. Same call 10b makes. */
	.bk-value, .bk-steps { text-align: center; }
	.bk-steps > .wp-block-group__inner-container > h2 { margin-inline: auto; }
	.bk-steps > .wp-block-group__inner-container > p,
	.bk-step p,
	.bk-faq__item p,
	.bk-offer__copy p { text-align: left; }
	/* A step is a box of its own; centring the sentence inside it just
	 * makes seven ragged boxes. */
	.bk-step { text-align: left; }
	.bk-steps__cta { text-align: center; }

	/* 48 of inset on each side is a quarter of a 390px screen. Same
	 * call .itv-panel makes at the same width. */
	.itv-value { padding: var(--space-24); }
	.bk-form { padding: var(--space-24); }

	/* !! THE VALUE ROWS STOP BEING TWO COLUMNS.
	 *
	 * "1st Full Body Adjustment with Extremities" is 41 characters. In
	 * a 390 viewport the card's content is 294, and with "$199 value"
	 * holding 90 of it on the same line the label gets 188 - four lines
	 * against the one it takes on a desktop, with the price floating
	 * beside the last of them. Stacked, the label gets the whole width
	 * and the price sits under it where it still reads as its price. */
	.itv-value__row {
		display: block;
		text-align: left;
		padding-block: var(--space-12);
	}
	.itv-value__row > span:last-child { display: block; }
}


/* ==================================================================
 * 21  CHECKOUT  (funnel step 2)
 *
 * VISUAL SOURCE: Figma 809:117, "(unverified) Checkout (Funnel 2)".
 * One body section, two columns, and a photograph behind the lower
 * half of it:
 *
 *   813:88   Value Stack Col   x150 w596   the page header + the card
 *   813:119  Payment Card      x810 w480   the form
 *   835:162  "image 23"        y512 h660   full-bleed, faded
 *
 * !! THERE IS NO CLOSING CTA ON THIS PAGE AND THAT IS DELIBERATE.
 * The frame goes Body -> Footer, and unlike the five text pages this
 * one is not merely undrawn: a "Book New Patient Appointment" button
 * under a payment form points back at step 1 of the funnel the visitor
 * is halfway through. Suppressed with _itv_cta_hide; see
 * hbchiropractic_cta_hidden() and the note in footer.php.
 *
 * !! THE PAYMENT FORM IS NOT BUILT HERE AND MUST NOT BE.
 *
 * It arrives as Upsell Plugin's shortcode - its author confirmed on
 * 2026-08-26 that checkout embeds on an existing page, stays on this
 * domain, is styleable with our own CSS, and keeps order data in this
 * site's own database. So anything hand-built would be thrown away the
 * day it is installed.
 *
 * And a page that LOOKS like a working payment form and is not one is
 * a hazard: someone eventually types a real card number into it. Until
 * the shortcode is configured, .ck-card holds a labelled placeholder
 * and NEVER a field. See hbchiropractic_checkout_slot().
 * ================================================================== */

/* ---- 21.1  THE SECTION, WHICH IS FULL-BLEED SO THE PHOTOGRAPH CAN BE
 *
 * The section itself paints nothing and lays nothing out. It exists to
 * be (a) full width, so the photograph in 21.5 can reach both edges,
 * and (b) the positioned ancestor that photograph resolves against.
 * The GRID is on the wrapper, which keeps the container.
 *
 * That is exactly the mechanism 10.1b's .itv-watermark describes, and
 * it is here for the same reason: a normal .itv-section is capped at
 * 1448 and centred, so `left: 0` inside one would be the CONTAINER's
 * left edge and the photograph would stop 236px short of the window at
 * 1920. Found on a wide monitor once already - see 10.1a.
 *
 * isolation keeps the z-index -1 below from escaping into the footer's
 * stacking context; overflow clips the photograph to the section. */
.itv-section.ck-body {
	max-width: none;
	padding-inline: 0;
	position: relative;
	isolation: isolate;
	overflow: hidden;
}

/* !! THE WRAPPER IS THE GRID AND IT MUST OUT-RANK 10.1.
 *
 * 10.1 dissolves every .itv-section's inner container with
 * display:contents, and that rule scores (0,2,0) - the same as
 * `.ck-body > .wp-block-group__inner-container`. Which one won would be
 * decided by source order alone, which is true today and is the kind of
 * thing that reverses the first time these blocks move. The extra class
 * settles it, the same way 10.1a writes the band's wrapper.
 *
 * 596 / 64 / 480 in a 1140 frame is 52.3%, 5.6%, 42.1% - two tracks
 * plus a real gap rather than three, because the middle one is empty
 * space and not a slot for anything.
 *
 * !! align-items: start IS LOAD-BEARING. The payment card is 1042 tall
 * against the left column's 722, and the default `stretch` would pull
 * the left column down to match - dragging the value card's cream fill
 * 320px further down the page than the design draws it. Both columns
 * start at the top and each ends where its own content does.
 *
 * !! minmax(0, ...) on both tracks, for the reason 20.2 documents at
 * length: an fr track will not shrink below its content, and this one
 * will hold a third party's form with its own minimum widths. */
/* !! grid-template-rows: auto 1fr, AND IT IS LOAD-BEARING.
 *
 * Without it the left column develops a dead zone that grows with the
 * payment form. The rows are implicit, the card SPANS both of them, and
 * when a spanning item is taller than the rows it covers the browser
 * shares the surplus out BETWEEN those rows. Row 1 holds nothing but
 * the page header, so it inflates; `align-items: start` then pins the
 * header to the top of a row several hundred pixels tall and the value
 * card, sitting in row 2, starts that far down the page.
 *
 * Measured before this line existed, with the real Upsell form in the
 * card at 1440: the header ended at 382 and the value column began at
 * 916 - 534px of nothing, on the half of the page that has to argue the
 * visitor into paying. It read as a layout mistake and it was one, but
 * nothing in the CSS looked wrong: the gap belonged to a row, not to a
 * margin on anything.
 *
 * `auto 1fr` says row 1 is exactly its content and row 2 absorbs
 * whatever the card has left over. The surplus still has to go
 * somewhere; this decides where. */
.itv-section.ck-body > .wp-block-group__inner-container {
	display: grid;
	grid-template-columns: minmax(0, 52fr) minmax(0, 42fr);
	grid-template-rows: auto 1fr;
	gap: 0 var(--space-64);
	align-items: start;
	width: 100%;
	max-width: calc(var(--container) + (var(--container-pad) * 2));
	margin-inline: auto;
	padding-inline: var(--container-pad);
}

/* !! THE THREE CHILDREN ARE PLACED EXPLICITLY, NOT AUTO-FLOWED.
 *
 * They are the page header, the value column and the payment card, in
 * that DOM order, and auto-placement would put them at (1,1), (1,2),
 * (2,1) - header top left, PAYMENT CARD top right, value column
 * underneath the header. That is almost right and completely wrong:
 * the card has to span both rows so the left column can run header
 * then values down the same track.
 *
 * The header is a SIBLING of the value column rather than nested
 * inside it, which is what lets 21b order the payment card above both
 * of them on a phone. Nested, it could not be. */
.ck-head { grid-column: 1; grid-row: 1; }
.ck-col  { grid-column: 1; grid-row: 2; }
.ck-card { grid-column: 2; grid-row: 1 / span 2; }
.ck-col > .wp-block-group__inner-container { display: contents; }

/* ---- 21.2  THE PAGE HEADER ----
 *
 * "Step 2: Complete Your Deposit" and the line under it. Same shape as
 * 19.1's document header - H1, then a lede one step above body - so it
 * takes the same treatment rather than a second opinion about what a
 * page opening looks like.
 *
 * !! IT IS NOT .itv-doc-head. That class carries a "last updated" line
 * and a --measure cap sized for a full-width reading column; this one
 * lives in a 596px grid track and needs neither. Same shape, different
 * container, so it gets its own two rules and no inheritance. */
.ck-head > .wp-block-group__inner-container { display: contents; }
.ck-head h1 { margin: 0 0 var(--space-16); max-width: none; }
.ck-head__lede {
	margin: 0;
	font-size: var(--type-body-lg);
	color: var(--ink-muted);
}
.ck-col > .wp-block-group__inner-container > h2 {
	margin: var(--space-48) 0 var(--space-24);
	max-width: none;
}

/* ---- 21.3  THE VALUE CARD ON THIS PAGE ----
 *
 * The component is shared - see the long note above .itv-value in
 * section 20. Only the things that differ per page are set here.
 *
 * CREAM, NOT WHITE. Sampled off the frame at three points and it is
 * rgb(245,244,241), which is --surface-alt exactly. On page 25 the card
 * is white because it sits on a navy band; here the page is white, so a
 * white card would be invisible and the shadow would be doing all the
 * work. Same reason the Order Summary inside the payment card is cream.
 *
 * The width cap comes off too: 536 is right for a card centred in a
 * band and wrong for one filling a 596px column. */
/* Only what genuinely differs on this page. The hairline, the shadow
 * and the money treatment are the shared component's now - see section
 * 20 - because they were never page-specific, they were just written
 * here first.
 *
 * !! THE SHADOW IS NOT RESTATED AND MUST NOT BE. 21.3 originally set
 * `box-shadow: none` here, reasoning that a cream card on a white page
 * does not need one. That was wrong for THIS page: 21.5's photograph
 * runs behind the lower half of this column, and over it the cream read
 * as a patch of the image rather than as a card. Round 2 put
 * --shadow-card back by restating it; round 3 deletes the restatement
 * instead, because the base component already carries exactly that
 * value. One place to change it. */
.ck-col .itv-value {
	width: 100%;
	background: var(--surface-alt);
	padding: var(--space-32);
}

/* ---- 21.4  THE PAYMENT CARD ----
 *
 * White, on a page that is white, so this one DOES need its shadow: it
 * is the only thing separating the form from the ground, and it is the
 * object the whole page exists to get somebody into.
 *
 * !! NOTHING INSIDE IT IS STYLED, THE SAME CALL 19.5 MAKES FOR THE
 * REVIEW WAVE FORM. What lands in here is a third party's markup.
 * Style the CARD; leave its contents to the plugin and to whatever CSS
 * is written against the real thing once it exists. The card fields in
 * particular are Stripe's, inside a cross-origin iframe - the plugin's
 * author confirmed they are styleable only to a degree and never from
 * here. */
.ck-card {
	background: var(--surface);
	border: 1px solid var(--rule);
	border-radius: var(--radius-md);
	padding: var(--space-32);
	/* !! --shadow-tile, NOT --shadow-card, AND THE TOP EDGE IS WHY.
	 * A white card on a white page has no fill to separate it, so the
	 * whole job falls to the edge - and --shadow-card is a single
	 * `0 2px 12px`, which is offset DOWNWARD. It draws the bottom and
	 * the sides and leaves the top edge with almost nothing on it,
	 * which is exactly where Gerek saw the card dissolve.
	 * --shadow-tile is two layers and the first is `0 1px 2px`, a tight
	 * contact shadow that renders on every edge including the top.
	 *
	 * The hairline is the same one the value card carries. The two are
	 * meant to read as equal-weight siblings, and equal WEIGHT is not
	 * equal TREATMENT here: the value card is cream on white, so its
	 * fill does half the separating and --shadow-card finishes it. This
	 * one has no fill contrast at all and needs the deeper shadow to
	 * arrive at the same place. */
	box-shadow: var(--shadow-tile);
}
/* ---- .form-cue - the "Step N of 3" heading on each funnel step ----
 *
 * !! IT IS NAVY, NOT AMBER, AND THAT IS A CONTRAST DECISION. Momma's
 * Chiro sets this cue in its CTA colour, which is a deep coral and
 * readable as text. HB's CTA colour is --accent, #fbb03c - amber on
 * white is roughly 1.9:1, nowhere near the 4.5:1 body text needs, and a
 * heading whose whole job is to be noticed must not be the hardest
 * thing on the page to read. --navy is the other brand colour, clears
 * 5.6:1, and is already what this site uses for emphasis.
 *
 * !! THIS REPLACED .ck-card__title, WHICH IS NOW GONE. Its 16px bottom
 * margin is carried over deliberately and is not a round number picked
 * twice: on checkout this heading sits directly above the payment
 * plugin's own "Billing Details" legend, and 24 put two headings a full
 * gap apart so the card opened with a stack of labels. */
.form-cue {
	display: flex;
	align-items: center;
	gap: var(--space-8);
	margin: 0 0 var(--space-16);
	color: var(--navy);
	font-family: var(--font-heading);
	font-size: var(--type-h3);
	font-weight: var(--weight-bold);
	line-height: var(--leading-heading);
	/* !! IF IT EVER DOES RUN OUT OF ROOM, THE STEP DROPS WHOLE.
	 * Without this the flex line squeezes both children instead, which
	 * is the failure below. A wrapped line is survivable; "Step 2 of
	 * 3" broken across two of them is not. */
	flex-wrap: wrap;
}

/* Context, not instruction - separated from the heading by SIZE rather
 * than by weight or colour, so it reads as a subtitle to the same
 * thought rather than as a second heading. */
.form-cue__step {
	font-size: var(--type-h4);
	/* !! NEVER BREAK THIS PHRASE. It is one piece of information -
	 * which step you are on, out of how many - and a line break inside
	 * it reads as a mistake no matter how tidy the wrap is. */
	white-space: nowrap;
}

/* !! STEP 2's CUE IS THE ONLY ONE IN A NARROW COLUMN - 2026-09-05.
 *
 * Gerek reported "- Step 2 of 3" breaking across two lines on a 13in
 * MacBook Pro and guessed a desktop would be fine. Measured: it is not.
 * The checkout card's column caps at 531px and stops growing - 1440,
 * 1600 and 1920 all give 531, because the container maxes at 1400 and
 * the card takes a fixed share of it. The cue needs 560 at h3/h4:
 * 362 for "Complete Your Deposit", 159 for the step, 23 for the arrow,
 * two 8px gaps. It has been 29px short on EVERY desktop width since it
 * shipped, and the laptop is only where it got noticed.
 *
 * !! THE OTHER TWO STEPS ARE FINE AND ARE DELIBERATELY NOT TOUCHED.
 * Measured at 1440: step 1 "Start Here" needs 351 in the hero panel,
 * step 3 "Pick Your Time" needs 426 across the full container. Both sit
 * on one line with room over. Scoping this to .ck-card leaves their
 * approved sizes alone; the alternative was shrinking two headings that
 * were never broken to keep three numbers matching.
 *
 * One step down the site's own scale - h3 to h4, h4 to h5 - measures
 * 494 and clears 531 by 37. It does NOT rescue every width: below about
 * 1366 the column is too narrow for one line at any size a heading can
 * respectably be (992 gives 327px, which cannot hold it at all). That
 * is what the nowrap and flex-wrap above are for - the heading wraps,
 * the step drops whole, and it reads as a deliberate two-line cue
 * rather than a broken one.
 *
 * Below 992 the columns stack and the card is full width - 877px at
 * 991 - so full size is correct again and this rule stops. */
@media (min-width: 992px) {
	.ck-card .form-cue       { font-size: var(--type-h4); }
	.ck-card .form-cue__step { font-size: var(--type-h5); }
}

.form-cue__arrow {
	display: inline-flex;
	animation: hb-cue-bob 2.4s ease-in-out infinite;
}

.form-cue__arrow svg {
	display: block;
}

@keyframes hb-cue-bob {
	0%, 100% { transform: translateY(0); }
	50%      { transform: translateY(5px); }
}

/* !! ANYONE WHO HAS ASKED THEIR SYSTEM FOR LESS MOTION GETS NONE. Not a
 * nicety on a page like this - vestibular disorders are common enough
 * that a looping animation can genuinely make somebody unable to use a
 * booking form, and the cue still does its job in colour and type with
 * the movement switched off. */
@media (prefers-reduced-motion: reduce) {
	.form-cue__arrow {
		animation: none;
	}
}

/* !! THE PLACEHOLDER IS SHARED, NOT CHECKOUT'S.
 *
 * Promoted from .ck-slot to .itv-slot 2026-08-27, an hour after it was
 * written, when the Booking Calendar needed the same box for its
 * scheduler embed. Three uses now - the checkout form, the checkout's
 * trust badges, the calendar's scheduler - and the next unbuilt embed
 * will be the fourth. Same move as .itv-value above and as 10.1b's
 * monogram before it.
 *
 * Borrows 9.0's dashed-frame language - dashed --rule on --surface-alt,
 * --ink-subtle text - WITHOUT being .hp-frame, which is an image slot
 * whose label is 13px uppercase eyebrow type and wrong for two
 * sentences of explanation. Same visual family, different job.
 *
 * !! IT IS FOR THINGS THAT ARE MISSING, NOT FOR THINGS THAT ARE
 * OPTIONAL. A page a real customer will see must never show one - see
 * the note on the thank-you receipt in section 23. */
.itv-slot {
	border: 1px dashed var(--rule);
	border-radius: var(--radius);
	background: var(--surface-alt);
	color: var(--ink-subtle);
	padding: var(--space-24);
	text-align: center;
}
.itv-slot p {
	margin: 0 0 var(--space-12);
	font-size: var(--type-small);
	line-height: var(--leading-body);
}
.itv-slot p:last-child { margin-bottom: 0; }

/* ---- 21.4a  THE CARD'S OWN COPY, WHICH IS NOT THE PLUGIN'S ----
 *
 * !! THE ORDER SUMMARY, THE DEPOSIT TERMS AND THE SECURITY LINE ARE
 * PAGE CONTENT, NOT PART OF THE SLOT, AND THAT IS DELIBERATE.
 *
 * The obvious build puts everything inside .ck-form and lets Upsell
 * Plugin render its own summary and its own button. It probably will.
 * But until it is installed that would leave this page showing a
 * dashed box and nothing else - and the copy it would be hiding is the
 * most important on the page:
 *
 *   "Deposit of $50 goes toward the full price of $349. Final payment
 *    of $299 due before or during the first appointment."
 *
 * That sentence is what stops "$50" reading as the total. On a page
 * that takes money it is the one thing that must be visible whether or
 * not a plugin is configured. So it is ours, it renders today, and if
 * the plugin later repeats it the duplicate is a five-minute deletion
 * rather than a fact the visitor never saw.
 *
 * Same for the security line and the badge slot. The FIELDS are the
 * plugin's; the promises around them are the practice's. */
/* !! THE ORDER SUMMARY BLOCK IS GONE, AND THIS IS THE DELETION THE
 * NOTE ABOVE PREDICTED.
 *
 * It said, before the plugin existed: "if the plugin later repeats it
 * the duplicate is a five-minute deletion rather than a fact the
 * visitor never saw." The plugin does repeat it. Its Payment Info
 * fieldset renders a real order table - line item, quantity, price,
 * subtotal, total - directly above its own submit button, from the
 * actual order rather than from copy somebody typed. Ours sat BELOW
 * that button restating the same two numbers, which by then reads as a
 * second charge rather than as reassurance.
 *
 * So `.ck-summary` and its four children came out of page content on
 * 2026-08-28 and their rules came out with them. The plate was
 * `.ck-summary`, `__title`, `__item`, `__note`, `__total`; grep will
 * find them in this file's history and in revisions 281 and 283 of page
 * 26, which are the only other places they ever appeared.
 *
 * !! WHAT DID NOT GO WITH IT IS THE DEPOSIT SENTENCE, AND THAT WAS THE
 * WHOLE RISK IN REMOVING THE BLOCK. "$50 goes toward the full price of
 * $349" is the line that stops fifty dollars reading as the total, and
 * `.ck-summary__note` was one of the two places it appeared. The other
 * is `.itv-value__terms` in the left-hand value card, which says the
 * same thing in more words and is ABOVE the form rather than below the
 * button. Checked on the rendered page before deleting, not assumed:
 * the sentence still renders, and on a phone the value card is ordered
 * ahead of the payment card, so it is still read before anything is
 * typed.
 *
 * The security line stays page content for the reason the original
 * note gives - the FIELDS are the plugin's, the promises are the
 * practice's - but it now renders inside the trust row below, as that
 * shortcode's content. */
/* !! `.ck-method` IS GONE FROM THIS PAGE AND ITS RULE WITH IT.
 * "Payment Method: Pay with Credit/Debit Card" was page content written
 * when the card held a placeholder and nothing else. The plugin now
 * renders its own gateway row, labelled "Pay with Credit Card", inside
 * the Payment Info fieldset ABOVE the button - so ours sat below the
 * button restating a choice the visitor had already been given. Flagged
 * at the end of round 1, removed here. Same reasoning as .ck-summary. */
/* Centred and quiet, under the plugin's submit button. --ink-muted on
 * white is 7.1:1, so "small print" here is a size decision and never a
 * contrast one. */
.ck-secure {
	margin: var(--space-16) 0;
	font-size: var(--type-detail);
	color: var(--ink-muted);
	line-height: var(--leading-body);
	text-align: center;
}

/* ---- 21.4b  THE TRUST ROW ----
 *
 * Four card marks, a padlock and the security sentence, in place of the
 * dashed "trust badges go here" slot this card carried until
 * 2026-08-28. The marks are drawn in inc/trust-badges.php - see that
 * file for why they are drawn rather than downloaded.
 *
 * The row is the last thing above the fold of the card and the last
 * thing somebody looks at before typing a card number, so it is quiet
 * on purpose: no box, no fill, just a hairline separating it from the
 * button and the marks at a size that reads as information rather than
 * as decoration.
 *
 * SIZE LIVES HERE, NOT IN THE SVG. The partial ships no width, height
 * or fill attributes, so the whole row resizes from this one height and
 * recolours from the tokens below. */
.ck-trust {
	margin-top: var(--space-32);
	padding-top: var(--space-24);
	border-top: 1px solid var(--rule);
}
.ck-trust__cards {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	align-items: center;
	gap: var(--space-12);
	margin: 0;
	padding: 0;
	list-style: none;
}
/* !! list-style: none ON THE UL IS NOT ENOUGH ON ITS OWN HERE.
 * Section 02's base sets a marker on `li` for running copy, and a
 * flex-item li keeps it. Cleared on the item, at the same place the
 * size is set, so the two cannot drift apart. */
.ck-trust__card {
	margin: 0;
	padding: 0;
	list-style: none;
	line-height: 0;
}
.ck-trust__card::marker { content: ""; }
.ck-trust__card > svg {
	display: block;
	height: var(--space-32);
	width: auto;
}

/* !! THE NUMBERS INSIDE THE MARKS ARE USER UNITS, NOT PIXELS, WHICH IS
 * WHY THEY ARE NOT TOKENS. Everything below styles content inside a
 * `viewBox="0 0 40 26"`, so a font-size of 9 means nine units of a
 * 40-unit-wide plate and scales with the height set above - it does not
 * mean nine CSS pixels and it has no relationship to the type ladder.
 * Putting --type-xs on one of these would set the word at 15/26ths of
 * the plate's height. Section 01's rule is about design values; these
 * are drawing coordinates and belong with the drawing. */

/* The plate every mark but American Express sits on: white, with the
 * site's own hairline rather than a grey of its own. */
.ck-trust__plate {
	fill: var(--surface);
	stroke: var(--rule);
	stroke-width: 1;
}
.ck-trust__plate--amex { fill: var(--brand-amex); stroke: none; }

.ck-trust__word {
	font-family: var(--font-body);
	font-weight: var(--weight-bold);
	text-anchor: middle;
}
.ck-trust__word--visa {
	fill: var(--brand-visa);
	font-size: 9px;
	font-style: italic;
	letter-spacing: .5px;
}
.ck-trust__word--amex {
	fill: var(--on-invert);
	font-size: 7.5px;
	letter-spacing: .4px;
}
/* Anchored at the start rather than the middle: this is the one mark
 * whose word shares its plate with a shape, so it is set from the left
 * edge and the disc keeps the right. */
/* !! THE WORD AND THE BALL SHARE THIS PLATE AND THEY COLLIDED.
 * At 5.6 the word measured 30 units from x=4, ending at 34, while the
 * ball spanned 28.6-37.4 - five units of overlap, drawn every time.
 * Caught by measuring getBBox() rather than by looking, because at
 * 32px tall on screen it reads as slightly heavy letter-spacing. */
.ck-trust__word--discover {
	fill: var(--ink);
	font-size: 4.9px;
	text-anchor: start;
	letter-spacing: -.1px;
}
.ck-trust__disc-ball { fill: var(--brand-discover); }
.ck-trust__mc-a  { fill: var(--brand-mastercard-a); }
.ck-trust__mc-b  { fill: var(--brand-mastercard-b); }
.ck-trust__mc-ab { fill: var(--brand-mastercard-ab); }

/* The lock sits ON the sentence's first line rather than above it, so
 * the two read as one statement. Baseline nudge instead of
 * vertical-align, which on an inline SVG aligns the BOX and leaves the
 * glyph looking high. */
.ck-trust__note {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--space-8);
	margin: var(--space-20) 0 0;
	text-align: left;
}
.ck-trust__lock {
	flex: 0 0 auto;
	width: var(--space-16);
	height: var(--space-16);
}

/* ---- 21.5  THE PHOTOGRAPH BEHIND THE LOWER HALF  (835:162) ----
 *
 * media 234, Body-Adjustment-Video-Still, 1440x660, behind the bottom
 * of the section and bleeding to both edges.
 *
 * !! THE FILE IS NOT FADED - THE FADE IS OURS. Checked rather than
 * assumed: the export's mean luminance is 102 with true blacks and true
 * whites in it, so Figma applies the transparency and CSS has to. The
 * value was solved from the frame's own render, where the visible band
 * samples between 190 and 222 over white.
 *
 * It is decorative, so it is aria-hidden with an empty alt in the page
 * content and pointer-events:none here - it must never sit between a
 * cursor and the payment card. */
.ck-photo {
	position: absolute;
	z-index: -1;
	left: 0;
	right: 0;
	bottom: 0;
	margin: 0;
	pointer-events: none;
	opacity: .45;
}
.ck-photo > img {
	width: 100%;
	height: auto;
	display: block;
}


/* ---- 21.6  THE PLUGIN'S FORM, WHICH THIS FILE USED TO REFUSE TO
 *            STYLE ----
 *
 * !! 21.4 SAYS "NOTHING INSIDE IT IS STYLED". THAT WAS WRITTEN BEFORE
 * THE FORM EXISTED AND IT NO LONGER HOLDS. The reasoning was sound at
 * the time - do not style markup you have not seen - but the markup
 * arrived on 2026-08-28 and it is Bootstrap 4 at its loosest. Measured
 * on the real rendered page at 1440, the form ran 1457px tall inside a
 * 446px card: nine billing fields at 94px each, where the input itself
 * is 38.
 *
 * The card fields remain Stripe's, inside a cross-origin iframe, and
 * nothing here reaches them. Everything below is the plugin's own
 * chrome around them.
 *
 * WHERE THE 94 CAME FROM, and what each rule takes back:
 *
 *   label   24  + 8 margin      -> a smaller label, tighter to its field
 *   input   38                  -> unchanged in height, but see below
 *   group   24 margin           -> the field-to-field gap
 *
 * !! THE INPUTS WERE NOT ACTUALLY OVERSIZED - THEY WERE BROKEN.
 * Bootstrap sets `height: calc(1.5em + .75rem + 2px)`, which computes
 * to 38, and the plugin then sets `padding: 15px 22.4px` on the same
 * element. With border-box that leaves 38 - 2 - 30 = SIX pixels of
 * content box for a 20px line. The text is not centred in those inputs,
 * it is overflowing a box far too small for it, and it only looks
 * roughly right by accident. `height: auto` hands the sizing back to
 * the padding, which is why the rule below sets both.
 *
 * THE HOUSE REFERENCE IS inc/booking-form.php's OWN FORM, measured the
 * same way on page 25: 52px inputs at 19px with 12/16 padding and no
 * visible labels at all. That density cannot be copied literally here -
 * this form has nine labelled fields where that one has four unlabelled
 * ones - so what is matched is its RHYTHM: one clear gap between
 * fields, a label that sits on its field rather than floating above it,
 * and no dead space inside the controls.
 *
 * EVERY SELECTOR IS SCOPED UNDER .ck-card. The plugin's classes are
 * generic - .form-group, .row, .form-control, legend - and half of them
 * are Bootstrap's. Unscoped, these rules would reach the Review Wave
 * embed on /online-forms/ and any other third-party markup this site
 * ever hosts. .ck-card exists on page 26 alone. */

.ck-card .upsell-checkout-form { margin-bottom: 0; }

/* The plugin opens the form with a 10px spacer div carrying no class,
 * no id and no content but whitespace. :empty does NOT match it - a
 * text node of spaces is still a child - which is why the first attempt
 * at this rule silently did nothing. Matched on "a div with no class
 * attribute at all", which in this form is only ever that one. */
.ck-card .upsell-checkout-form > div:not([class]) { display: none; }

/* "Billing Details" and "Payment Info". Shipped at 32px, which is
 * --type-h2 territory for a heading inside a card whose own title is
 * --type-h5. Brought down to the same weight and colour .ck-summary
 * used for the same job before it was removed. */
.ck-card .upsell-checkout-form legend {
	position: relative;
	width: auto;
	/* !! text-align: left, AND IT IS THE SAME MISS AS THE GREY.
	 * The bundled bootstrap carries `.upsell legend { width: 100%;
	 * text-align: center }`. Round 2 answered the width and not the
	 * alignment, so these two headings have been sitting centred over a
	 * card whose title, every label and every field are left-aligned -
	 * and the accent rule under them inherited that, landing wherever
	 * the shrunk box happened to start. Left, like everything else in
	 * the card. */
	text-align: left;
	margin-bottom: var(--space-12);
	padding: 0 0 calc(var(--space-8) / 2);
	border: 0;
	/* !! background: none IS THE WHOLE FIX FOR THE GREY.
	 * The plugin's bundled bootstrap.css carries
	 * `.upsell legend { background: #e9ebee }`, a pale grey plate behind
	 * the words. On a white card it reads as accidentally selected text
	 * rather than as a heading, which is exactly how Gerek described it.
	 * Round 1 restyled the type here and never set a background, so the
	 * grey survived underneath. */
	background: none;
	font-family: var(--font-heading);
	font-size: var(--type-eyebrow);
	/* !! !important, AND IT IS NOT OPTIONAL. The plugin prints an inline
	 * <style> block in the body carrying
	 * `.upsell legend { font-weight: normal !important }` - along with
	 * the same for label, .form-control and .btn. An author !important
	 * can only be answered by another author !important, so round 1's
	 * semibold was silently ignored and these headings have been
	 * rendering at 400 since the day the plugin went in. */
	font-weight: var(--weight-semibold) !important;
	letter-spacing: var(--tracking-eyebrow);
	text-transform: uppercase;
	line-height: var(--leading-heading);
	color: var(--navy);
}
/* The accent rule under the label.
 *
 * !! AMBER AS A RULE, NEVER AS THE WORDS. Section 01 says it outright -
 * amber is a surface colour, used as a fill, a border or a rule, and
 * never as text on a light ground. Measured, that is not a style
 * preference: #fbb03c on this card's white is 1.85:1 and on the cream
 * value card 1.68:1, both far under AA. Navy carries the words at
 * 6.77:1 and the amber carries the eye.
 *
 * 3px because 1 is a hairline and disappears next to 13px uppercase,
 * and because the kit already treats 2-3px as a line weight rather than
 * a spacing token - see .hp-eyebrow--pill's 2px border and .cta-button's
 * note about why its ring is 2 and not 1. */
.ck-card .upsell-checkout-form legend::after {
	content: "";
	position: absolute;
	left: 0;
	bottom: 0;
	width: var(--space-32);
	height: 3px;
	border-radius: var(--radius-pill);
	background: var(--accent);
}
.ck-card .upsell-checkout-form fieldset { margin-bottom: var(--space-20); }
.ck-card .upsell-checkout-form fieldset:last-of-type { margin-bottom: var(--space-16); }

/* The field gap. 24 -> 12, which is the one number that does most of
 * the work: it applies nine times in the billing section alone. */
.ck-card .upsell-checkout-form .form-group { margin-bottom: var(--space-12); }

.ck-card .upsell-checkout-form .form-group > label {
	display: block;
	/* Half a step. A label this small sits ON its field; a full 8
	 * floats it and reads as a gap the field-to-field 12 has to beat. */
	margin-bottom: calc(var(--space-8) / 2);
	font-size: var(--type-eyebrow);
	/* Same inline `.upsell label { font-weight: normal !important }` as
	 * the legend above. These have been rendering at 400 since round 1. */
	font-weight: var(--weight-semibold) !important;
	letter-spacing: var(--tracking-eyebrow);
	text-transform: uppercase;
	line-height: var(--leading-heading);
	color: var(--ink-muted);
}

/* !! --type-ui, NOT --type-detail, AND THE DIFFERENCE ONLY SHOWS ON A
 * PHONE. iOS Safari zooms the whole page when a focused field is under
 * 16px and does not zoom back out; a checkout that jumps on first tap
 * loses people at the worst possible moment.
 *
 * Both tokens are 17px on a desktop, so this looked settled and was
 * not: at <=599 the token block steps --type-detail down to 16 and
 * --type-ui to 17. Measured at 390 with --type-detail the inputs came
 * out at exactly 16 - which happens to clear the threshold, by nothing.
 * --type-ui is the token sections 19 and 20 already use for the same
 * reason on the contact and booking forms, and it keeps the one pixel
 * of margin 20.4 documents.
 *
 * The LABELS may go smaller because nothing focuses them. */
.ck-card .upsell-checkout-form .form-control,
.ck-card .upsell-checkout-form select.form-control,
.ck-card .upsell-checkout-form input[type="text"],
.ck-card .upsell-checkout-form input[type="email"],
.ck-card .upsell-checkout-form input[type="tel"],
.ck-card .upsell-checkout-form input[type="password"] {
	height: auto;
	min-height: 0;
	padding: var(--space-8) var(--space-12);
	font-family: var(--font-body);
	font-size: var(--type-ui);
	line-height: var(--leading-heading);
	color: var(--ink);
	/* !! --ink-subtle, NOT --rule, AND 1.4.11 IS WHY.
	 * --rule is the site's hairline for dividing CONTENT, and against
	 * this card's white it measures 1.38:1. A form field is a user
	 * interface component, so its boundary owes 3:1 under WCAG 1.4.11,
	 * and the fill below cannot cover for it either - cream on white is
	 * 1.10:1. Measured, the pair identified the field by almost nothing.
	 * --ink-subtle is 3.41:1 on the card's white and 3.10:1 against the
	 * field's own cream, so it clears on both sides of the line it
	 * draws. It is also, plainly, the "firmer border at rest" this was
	 * asked for - the fill alone was the first attempt and the numbers
	 * sent it back. */
	border: 1px solid var(--ink-subtle);
	border-radius: var(--radius-nested);
	/* !! A FILL, NOT A BORDER, IS WHAT MAKES A FIELD READ AS FILLABLE.
	 * White fields inside a white card are gaps in a sheet of paper -
	 * the hairline alone was doing all the work and losing. This is the
	 * house answer rather than a new one: .bk-form__input in section 20
	 * already rests on --surface-alt with a --rule hairline and goes to
	 * --surface on focus, and it has since the booking form was built.
	 * The checkout now behaves the same way, so a visitor moving from
	 * /booking/ to /booking-checkout/ meets one form, not two.
	 *
	 * It is also the same --surface-alt the order table sits on, which
	 * gives the card a single language for "recessed surface". */
	background-color: var(--surface-alt);
}
/* !! opacity: 1 IS LOAD-BEARING. Firefox applies its own opacity to
 * placeholders, so a colour set without it renders lighter than asked
 * and quietly drops below AA. Same line .bk-form__input carries. */
.ck-card .upsell-checkout-form .form-control::placeholder {
	color: var(--ink-muted);
	opacity: 1;
}
/* Hover goes to brand rather than merely darker - at rest the border is
 * already --ink-subtle, so a "darker grey" step would be invisible. */
.ck-card .upsell-checkout-form .form-control:hover:not(:focus) {
	border-color: var(--navy);
}
/* Focus lifts the field OUT of the card - fill to pure white, brand
 * border, brand ring. The fill change is the part that reads at a
 * glance; the ring is what a keyboard user needs. */
.ck-card .upsell-checkout-form .form-control:focus,
.ck-card .upsell-checkout-form .StripeElement--focus {
	border-color: var(--navy);
	background-color: var(--surface);
	box-shadow: none;
	outline: 2px solid var(--navy);
	outline-offset: 1px;
}

/* Bootstrap's .row is a negative-margin gutter system and the form
 * nests one per field group. Tightening the gutter here rather than
 * per-column keeps the two-up pairs aligned with the full-width fields
 * above and below them. */
.ck-card .upsell-checkout-form .row {
	margin-right: calc(var(--space-8) * -1);
	margin-left: calc(var(--space-8) * -1);
}
.ck-card .upsell-checkout-form .row > [class*="col-"] {
	padding-right: var(--space-8);
	padding-left: var(--space-8);
}

/* !! PAIRING COMPANY WITH COUNTRY WAS TRIED HERE AND REVERTED. IT SAVES
 * NOTHING, AND THE MEASUREMENT IS WHY.
 *
 * Both ship as col-md-12 and take a full line each, which looks like
 * two short fields wasting two lines next to a first/last pair that
 * already proves the column takes two. Setting both to 50% with :has()
 * does make them half-width - and the form got no shorter: 443px of
 * billing rows against 444 before.
 *
 * The reason is the plugin's own row grouping. Company lives in the
 * SAME .row as firstName and lastName, so at half width it does not
 * pair with country at all - it wraps to a second line underneath the
 * name pair, orphaned, with half the row empty beside it. Country then
 * sits alone in its own .row at half width with the other half empty.
 * Two ragged half-lines instead of two clean full ones, for zero pixels.
 *
 * Fields cannot be re-grouped from CSS because the grouping is a DOM
 * parent, and reordering them with `order` would break tab order on a
 * payment form. So they stay full width, and the line that actually
 * came out is the Company field itself - see the note on the checkout
 * labels mu-plugin in HANDOFF.md. */

/* The order table above the submit button. Four rows at 22px with 12 of
 * padding is a data table sized like running copy; it is a receipt. */
.ck-card .upsell-checkout-form .table {
	margin-bottom: var(--space-16);
	font-size: var(--type-detail);
	/* !! separate, NOT collapse, OR THE RADIUS DOES NOTHING.
	 * With border-collapse: collapse the cell borders win the corners
	 * and a border-radius on the table is simply ignored. Separate
	 * borders plus zero spacing keeps the layout identical and lets the
	 * corners round. `overflow: hidden` then clips the first and last
	 * rows to the curve so the fill does not square them off again. */
	border-collapse: separate;
	border-spacing: 0;
	border: 1px solid var(--rule);
	border-radius: var(--radius-nested);
	overflow: hidden;
	background: var(--surface-alt);
}
/* .table-bordered draws a box round every cell. Two columns and four
 * rows of that is a spreadsheet pasted into a card. Every vertical
 * comes out and the horizontals stay, which is enough to keep the
 * figures tracking across to their labels. */
.ck-card .upsell-checkout-form .table td,
.ck-card .upsell-checkout-form .table th {
	padding: calc(var(--space-12) / 2) var(--space-8);
	line-height: var(--leading-heading);
	border: 0;
	border-bottom: 1px solid var(--rule);
	vertical-align: middle;
}
.ck-card .upsell-checkout-form .table thead th {
	font-size: var(--type-eyebrow);
	font-weight: var(--weight-semibold) !important;
	letter-spacing: var(--tracking-eyebrow);
	text-transform: uppercase;
	color: var(--ink-muted);
	background: none;
}
/* The line item needs no rule under it - the subtotal below it is
 * already a change of voice. */
.ck-card .upsell-checkout-form .table tbody td { border-bottom: 0; }
.ck-card .upsell-checkout-form .table td:last-child,
.ck-card .upsell-checkout-form .table th:last-child { text-align: right; }
/* The total is the number somebody checks before typing a card in, so
 * it gets the rule above it and the weight. */
.ck-card .upsell-checkout-form .table tfoot tr:last-child td {
	border-top: 1px solid var(--rule);
	border-bottom: 0;
	font-size: var(--type-ui);
	font-weight: var(--weight-bold) !important;
	color: var(--ink);
}
.ck-card .upsell-checkout-form .table tfoot tr:first-child td {
	border-bottom: 0;
	color: var(--ink-muted);
}

/* The gateway block: one radio and the Stripe mount. It carried its own
 * inset on top of the fieldset's. */
.ck-card .upsell-payment-gateway { padding: 0; }
.ck-card .upsell-payment-gateway-selected { margin-bottom: 0; }
.ck-card .upsell-checkout-payment-section .form-group:last-child { margin-bottom: 0; }

/* ---- 21.6a  THE SUBMIT BUTTON ----
 *
 * It shipped as Bootstrap's `.btn-success` - #28a745, a 10px radius and
 * 32px type. Green means go on a payment form, but this site has one
 * primary action colour and it is the amber pill in section 02. A
 * checkout button that does not look like every other CTA on the site
 * is the one place that inconsistency actually costs something.
 *
 * !! WHY THIS IS NOT JUST `.cta-button` ADDED TO THE SELECTOR LIST.
 * That was the first instinct and it is wrong here. `.cta-button` is
 * `display: inline-block` with `white-space: nowrap`, sized to its
 * words - right for a link in a band, wrong for the submit control of a
 * form, which should span the card and be the widest thing in it. So
 * the FINISH is taken from the same tokens the pill uses - the fill,
 * the rim, the gloss, the lift, the radius - and the BOX is this
 * button's own. Change the pill's tokens and this follows; change its
 * box model and this deliberately does not.
 *
 * !! TWO PROPERTIES NEED !important AND THEY ARE BOTH THE PLUGIN'S
 * DOING. Its inline <style> block carries `.upsell .btn` with
 * `font-weight: normal !important` and, stranger,
 * `border-style: inherit !important`. The second one matters more than
 * it looks: this treatment paints its rim through `border-box`, so a
 * border forced to `inherit` (which resolves to none from the form)
 * takes the entire gradient ring away and leaves a flat lozenge. */
.ck-card .upsell-checkout-form .btn-success {
	position: relative;
	isolation: isolate;
	display: block;
	width: 100%;
	padding: var(--space-8) var(--space-20);
	background:
		var(--accent-fill) padding-box,
		var(--accent-rim) border-box;
	border: 2px solid transparent !important;
	border-style: solid !important;
	color: var(--accent-contrast);
	font-family: var(--font-body);
	font-size: var(--type-ui);
	font-weight: var(--weight-semibold) !important;
	line-height: 1.4;
	text-decoration: none;
	border-radius: var(--radius-pill);
	box-shadow: var(--accent-lift);
	transition: box-shadow .18s ease, transform .18s ease;
}
/* The gloss, exactly as 02 draws it: it covers the top half and stops,
 * so the highlight has an edge rather than fading the whole pill out. */
.ck-card .upsell-checkout-form .btn-success::before {
	content: "";
	position: absolute;
	inset: 2px 2px 50%;
	z-index: -1;
	border-radius: var(--radius-pill) var(--radius-pill) 0 0;
	background: var(--accent-gloss);
	pointer-events: none;
}
.ck-card .upsell-checkout-form .btn-success:hover {
	box-shadow: 0 6px 12px rgba(255, 139, 0, 0.42), 0 0 26px rgba(251, 176, 60, 0.55);
	transform: translateY(-1px);
	filter: brightness(1.06);
	color: var(--accent-contrast);
}
/* Same as .cta-button:active above, and this is the button where it
 * matters most - the one taking the deposit. */
.ck-card .upsell-checkout-form .btn-success:active {
	transform: translateY(0);
	filter: brightness(.92);
	box-shadow: 0 1px 3px rgba(255, 139, 0, .35);
}
.ck-card .upsell-checkout-form .btn-success:focus-visible {
	outline: 2px solid var(--navy);
	outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
	.ck-card .upsell-checkout-form .btn-success { transition: none; }

	/* Movement only - the darkening stays, as above. */
	.ck-card .upsell-checkout-form .btn-success:hover,
	.ck-card .upsell-checkout-form .btn-success:active {
		transform: none;
	}
}

/* The two consent rows. Their labels are the wording supplied by
 * mu-plugins/itv-upsell-checkout-labels.php and they wrap to two lines
 * in a 382px column, so the saving here is leading rather than size. */
.ck-card .upsell-checkout-e-mark-section,
.ck-card .upsell-checkout-terms-section {
	margin-bottom: var(--space-12);
}
.ck-card .upsell-checkout-e-mark-section .form-group,
.ck-card .upsell-checkout-terms-section .form-group {
	margin-bottom: 0;
}
.ck-card .upsell-checkout-e-mark-section .d-inline-block,
.ck-card .upsell-checkout-terms-section .d-inline-block {
	font-size: var(--type-xs);
	line-height: var(--leading-heading);
	color: var(--ink-muted);
}
/* Bootstrap's .row gutter puts a half-gutter under each consent row on
 * top of the fieldset's own margin. */
.ck-card .upsell-checkout-e-mark-section .row,
.ck-card .upsell-checkout-terms-section .row { margin-bottom: 0; }


/* ==================================================================
 * 21b  CHECKOUT - RESPONSIVE
 *
 *  991  the two columns stack, AND THE PAYMENT CARD GOES FIRST.
 *  599  the cards lose half their inset.
 *
 * Nothing at 1199 or 767. Two tracks of 52/42 hold their proportions
 * all the way down to the stack, and neither column contains a grid of
 * its own to break.
 * ================================================================== */

@media (max-width: 991px) {
	/* !! THE PAYMENT CARD IS ORDERED ABOVE THE VALUE STACK, WHICH
	 * REVERSES THE READING ORDER THE DESIGN DRAWS.
	 *
	 * In DOM order the stack is page header, "New Patient Appointments
	 * Include", the eight-row value card, THEN the form. That card is
	 * 523 tall in the frame and taller on a phone, so the form would
	 * start most of a screen below the fold - on the one page where the
	 * visitor has already decided to pay. The same argument the 3.6
	 * document makes for /booking/ - "[FORM - placed high on the page,
	 * above the fold at all breakpoints]" - applies with more force
	 * here, because this visitor is further down the funnel.
	 *
	 * The page header stays first: it is two lines, and "Step 2:
	 * Complete Your Deposit" is what tells somebody the form below it
	 * is the right one.
	 *
	 * The value stack keeps its place UNDER the form rather than being
	 * hidden. It is the reassurance - what the $50 is buying - and it
	 * is what someone scrolls back to when they hesitate. */
	.itv-section.ck-body > .wp-block-group__inner-container {
		grid-template-columns: 1fr;
		gap: var(--space-32);
	}
	.ck-head { grid-column: 1; grid-row: auto; order: 1; }
	.ck-card { grid-column: 1; grid-row: auto; order: 2; }
	.ck-col  { grid-column: 1; grid-row: auto; order: 3; }

	/* Stacked, the column is far taller than 660px, so the photograph
	 * covers a smaller share of it and reads heavier against the copy
	 * running over it. Anchored to the bottom it still does its job -
	 * grounding the end of the page rather than decorating the middle. */
	.ck-photo { opacity: .35; }
}

@media (max-width: 599px) {
	/* !! THIS LINE LOOKS REDUNDANT AND IS NOT. 20b ALREADY DROPS
	 * .itv-value's padding to 24 at this width - but that rule scores
	 * (0,1,0) and 21.3's `.ck-col .itv-value` scores (0,2,0), and a
	 * media query adds NO specificity. So on this page the desktop 32
	 * would beat the mobile 24 and the card would keep a 32px inset on
	 * a 390px screen. Restating it at (0,2,0) inside the breakpoint is
	 * what makes the shared rule reach this page.
	 *
	 * This is the trap sections 10, 15, 16b, 19 and 20 all log, in the
	 * form the file's own header warns about: a page rule written later
	 * beats a kit rule written inside a breakpoint. It is the first
	 * time it has been created BY sharing a component rather than by
	 * overriding one.
	 *
	 * 32 of inset on each side of a 390 screen leaves 262 for an
	 * eight-row price list. Same call .itv-panel makes at this width. */
	.ck-col .itv-value { padding: var(--space-24); }
	.ck-card { padding: var(--space-24); }

	/* The value rows go one-per-line here through the SHARED rule in
	 * 20b, which now reaches this page too - "1st Full Body Adjustment
	 * with Extremities" against "$199 value" on one 262px line is four
	 * lines of label. Nothing to add. */
}


/* ==================================================================
 * 22  BOOKING CALENDAR  (funnel step 3)
 *
 * VISUAL SOURCE: Figma 809:158. The simplest page on the site:
 *
 *   813:225  Section - Page Header   H1 + two lines
 *   813:229  Section - Body          one 1140x560 scheduler embed
 *
 * That is the whole page. There is no closing CTA - suppressed with
 * _itv_cta_hide, see section 21's note - because a "Book New Patient
 * Appointment" button under a booking calendar points at the thing the
 * visitor is already doing.
 *
 * !! THE EMBED IS NOT BUILT AND THE PLATFORM IS NOT CHOSEN. The 3.6
 * document is unusually explicit: the embed carries its own two-step
 * flow, its own fields, its own validation, its own no-availability
 * message and its own confirm button, and "do not rebuild, restyle, or
 * duplicate any of it in the page". So this section sizes a box and
 * stops. See hbchiropractic_scheduler_slot().
 * ================================================================== */

/* The page header runs the FULL container, not the reading measure.
 * 813:226 and 813:227 are both w1140 - the whole content column - and
 * that is right here rather than sloppy: the H1 is one line and the two
 * lines under it are short. Capping them at --measure would leave a
 * 960px header over a 1400px scheduler and read as a mistake. */
.bc-head h1 { margin: 0 0 var(--space-16); max-width: none; }
.bc-head p  { margin: 0 0 var(--space-12); max-width: none; }
.bc-head p:last-child { margin-bottom: 0; }
/* The second line is the fallback - "if what's available doesn't work,
 * call our office" - so it is quieter than the promise above it
 * without being hidden. */
.bc-head__fallback { color: var(--ink-muted); font-size: var(--type-small); }

/* The box. A minimum height rather than a fixed one: the frame draws
 * 560, and a scheduler that wants more must be allowed to have it
 * rather than being scrolled inside a box we chose. Third-party embeds
 * grow when they open a second step, which is exactly what this one
 * does. */
.bc-embed {
	min-height: 560px;
	margin-top: var(--space-48);
}
/* The placeholder fills the box while there is no scheduler in it. */
.bc-embed > .itv-slot {
	display: flex;
	flex-direction: column;
	justify-content: center;
	min-height: 560px;
}


/* ==================================================================
 * 22b  BOOKING CALENDAR - RESPONSIVE
 *
 * !! ONE RULE, AND THE ABSENCE OF THE REST IS THE POINT. There is no
 * layout here to stack - a header and one full-width box - and the
 * scheduler inside will do its own responding, because it has to work
 * on the platform's own terms at every width.
 * ================================================================== */

@media (max-width: 599px) {
	/* 560px of empty box is a whole phone screen of nothing while the
	 * embed loads. The box still has to reserve space so the page does
	 * not jump when it arrives, but not that much. */
	.bc-embed, .bc-embed > .itv-slot { min-height: 420px; }
}


/* ==================================================================
 * 23  THANK YOU  (funnel step 4)
 *
 * VISUAL SOURCE: Figma 809:199.
 *
 *   814:85   Page Header            H1 + a checkmark + one line
 *   838:87   Next Steps, two cards  425 / 24 / 691
 *   814:152  Where to Find Us       address, map, reschedule note
 *
 * !! NO RECEIPT BLOCK IS DRAWN ANYWHERE ON THIS FRAME, and the copy is
 * a complete thank-you without one. If the payment plugin turns out to
 * expose order details, hbchiropractic_thankyou_receipt() is the hook -
 * and until then it renders NOTHING rather than a placeholder, because
 * a real customer reaches this page seconds after paying and a dashed
 * developer's box would read as something having gone wrong with the
 * money. That is the opposite call from sections 21 and 22 and the
 * reason is written up on the function.
 * ================================================================== */

/* ---- 23.1  the page header ----
 *
 * The headline and the checkmark sit on one row - 814:88 is w782 with
 * 814:86 at x800 - so the mark follows the words rather than being
 * pinned to the far edge. Wrapping is allowed: at this site's 50px H1
 * the headline is wider than the frame's, and a checkmark that drops to
 * its own line under the words is better than one squeezed against
 * them. */
.ty-head__row {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: var(--space-24);
	margin-bottom: var(--space-24);
}
.ty-head__row h1 { margin: 0; max-width: none; }
/* The mark is media 232, 56x56, and it is DECORATIVE: the headline
 * already says "Your Appointment Is Confirmed", so the image repeats it
 * rather than adding to it. Empty alt and aria-hidden in the markup. */
.ty-head__check { flex: 0 0 auto; margin: 0; width: 56px; }
.ty-head__check > img { width: 100%; height: auto; display: block; }
.ty-head__lede { margin: 0; font-size: var(--type-body-lg); max-width: none; }

/* ---- 23.1a  THE SCHEDULER  (funnel step 3, merged in here) ----
 *
 * The Review Wave booking widget, moved onto this page on 2026-08-29
 * when the funnel went from four steps to three. It was page 27's whole
 * reason to exist; page 27 is now retired and section 22 is dead code
 * kept for the revert.
 *
 * !! ONE SCRIPT TAG IS THE ENTIRE EMBED. It injects its own
 * `.rw-embed-wrap.rw-sched-wrap` in place, about 780px tall, carrying
 * its own date strip, its own fields and its own confirm button. The
 * same call section 22 made and 19.5 makes for the Review Wave form on
 * /online-forms/: style the BOX and leave the vendor's markup alone.
 * Nothing below reaches inside `.rw-` anything.
 *
 * The embed pattern follows page 20's, which is the one already proven
 * with this vendor on this site - a plain container div around the
 * script, in page content. The theme's own `[itv_scheduler_slot]` and
 * ITV_SCHEDULER_SHORTCODE were built for a platform that had not been
 * chosen; they still exist and still work, and are the revert path if
 * this ever needs to move back behind a constant.
 *
 * SIZED AS THE RECEIPT'S SIBLING. Both are cards on the same page doing
 * the two halves of one job - book the time, here is what you paid - so
 * they take the same width, the same corner and the same edge. A
 * calendar that sat full-bleed above a 75% receipt would read as two
 * unrelated components. */
.ty-sched__title {
	margin: var(--space-48) 0 var(--space-12);
	max-width: none;
}
.ty-sched__note {
	margin: 0 0 var(--space-24);
	max-width: none;
	color: var(--ink-muted);
	font-size: var(--type-small);
}
.ty-sched {
	width: 75%;
	margin-inline: auto;
	padding: var(--space-24);
	background: var(--surface);
	border: 1px solid var(--rule);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-card);
	/* The widget is ~780 once it mounts. Holding the box open means the
	 * page does not jump when the vendor's script lands, and a blocked
	 * or slow script leaves a card-shaped gap rather than a collapsed
	 * one. Section 22 held 560 for the same reason. */
	min-height: 560px;
}
/* The vendor draws its own surface, so it does not need ours behind it
 * as well - but it renders inside a form element that carries an inline
 * height, and giving it a width here keeps it off the card's padding. */
.ty-sched > .rw-embed-wrap { width: 100%; }

/* ---- 23.1a-i  THE SCHEDULER'S GROUND ----
 *
 * `.ty-schedband` is the group block that holds the heading, the phone
 * fallback line and the card. It is NOT a new component and it brings
 * NO new colour: the block carries `.itv-section.itv-band`, and the
 * tint, the full bleed, the 100/100 rhythm and the kept inner wrapper
 * all come from 10.1 unchanged. --surface-alt is the same ground every
 * other band on this site paints with.
 *
 * WHY IT EXISTS. The page was white, the scheduler card is white, and
 * the vendor's widget draws its own white surface inside that. Three
 * whites separated by one hairline read as a single flat sheet, and
 * nothing on it said the receipt was further down. The tint gives the
 * card a ground to sit on and gives the page a visible seam that reads
 * as "this section ends here and there is more below it".
 *
 * !! REUSING .itv-band IS WHAT KEEPS THE CARD FROM MOVING, and that is
 * why it was chosen over a band of this page's own. A band caps its
 * INNER wrapper at the same `--container + 2 * --container-pad` that
 * every .itv-section caps itself at, so `.ty-sched`'s 75% still
 * resolves against the same 1392 it resolved against on the white
 * ground. The card stays sibling-matched to the receipt, which is the
 * whole argument 23.1a makes for sizing the two together.
 *
 * The one thing the band changes is the title's air. 23.1a gives it 48
 * of its own because on a white page it had none; inside the band the
 * section's own 100 is that air, and 48 more on top of it is 148.
 */
.ty-schedband .ty-sched__title { margin-top: 0; }

/* One line introducing the receipt below it. */
.ty-receipt__intro {
	margin: var(--space-48) 0 0;
	max-width: none;
	color: var(--ink-muted);
	font-size: var(--type-small);
	text-align: center;
}

/* ---- 23.1b  THE RECEIPT ----
 *
 * `[itv_thankyou_receipt]` wraps whatever ITV_THANKYOU_SHORTCODE holds
 * in `.ty-receipt`. Today that is the plugin's `[upsell_current_orders]`
 * table, narrowed to the one order just paid for by
 * mu-plugins/itv-upsell-order-received.php.
 *
 * !! THIS WRAPPER HAD NO RULES AT ALL UNTIL 2026-08-28. The slot was
 * built before the plugin existed and shipped empty on purpose, so the
 * first real order rendered a bare Bootstrap table - grey #ececec
 * header cells, 22px type, full 1344px bleed - against a page where
 * everything else is a card. It was the only thing on the site that did
 * not look like the site.
 *
 * 75% and centred at desktop, per Gerek 2026-08-28. It is four columns
 * of short values; run to the full container it sets the date and the
 * price a metre apart.
 *
 * !! THE HEADER CELLS CARRY AN INLINE style ATTRIBUTE AND THEREFORE
 * NEED !important. The plugin writes `style="background-color: #ececec;"`
 * on every <th> in the view itself, and an inline declaration outranks
 * any selector in this file no matter how specific. !important on the
 * background is the only way to reach it without editing plugin source.
 *
 * !! THE BAR FOR USING IT, WHICH IS NARROWER THAN IT LOOKS.
 * 10.1b's note is the house position - it out-ranks two rules at once
 * by specificity precisely so it does not have to reach for this. The
 * difference in the Upsell cases is that specificity CANNOT win: an
 * inline style attribute is a higher origin, and an author !important
 * can only be answered by another author !important. Nothing else in
 * this file may use it.
 *
 * There are nine declarations, all of them on that footing, all inside
 * the checkout and the receipt, and each one carries a comment naming
 * the third-party rule it is answering:
 *
 *   background-color (1)  this rule - the inline style on every <th>
 *   font-weight      (5)  the plugin's inline <style> block, which sets
 *                         `.upsell legend`, `.upsell label` and
 *                         `.upsell .btn` to normal !important
 *   border, border-style (2)  the same block's
 *                         `border-style: inherit !important` on .btn,
 *                         which would otherwise take the CTA ring off
 *                         the submit button
 *   text-align       (1)  Bootstrap's `.text-right` UTILITY class on the
 *                         receipt's footer label, which ships
 *                         `!important` by design, as utilities do
 *
 * This said "the only !important in this stylesheet" until 2026-08-28
 * and that stopped being true the moment the plugin's inline block was
 * found. The count is stated here so the next person can check it
 * rather than trust it. */
.ty-receipt {
	width: 75%;
	/* 16, not 32: `.ty-receipt__intro` sits directly above and carries
	 * the separation from the scheduler. */
	margin: var(--space-16) auto 0;
	padding: var(--space-24);
	background: var(--surface);
	border: 1px solid var(--rule);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-card);
	/* !! THE CARD TAKES THE SCROLL THE TABLE GAVE UP, AND IT HAS TO.
	 * Putting the table back to `display: table` above removes the
	 * overflow-x that .table-responsive was carrying, and four columns
	 * of a receipt do not fit a phone: measured at 390 the table wants
	 * 555px inside a 294px card. Without this line it simply spills out
	 * of the card and over the page. With it the card clips at its own
	 * rounded edge and the table scrolls inside it, which is what
	 * .table-responsive was reaching for and put in the wrong place. */
	overflow-x: auto;
}

/* ---- 23.1c  THE CARD'S LABEL, AND WHAT IT SAYS WITH NO ORDER ----
 *
 * Both elements are printed by hbchiropractic_thankyou_receipt(), not
 * authored into the page, so this section styles markup the theme
 * itself emits. There is no third-party rule to answer here and nothing
 * below reaches for !important.
 *
 * !! AN <h2> STYLED AS A LABEL, DELIBERATELY, AND GETTING THERE COSTS
 * FOUR OVERRIDES. It is a real heading - it heads the receipt, it sits
 * at the level of the page's other section headings, and somebody
 * navigating by heading should be able to land on the receipt. It
 * should not LOOK like one: "Your Receipt" labels a card, it does not
 * open a section of the page. The base rule in 02 gives every h2 Sora,
 * bold, --type-h2 and --ink, so family, size, weight and colour are all
 * reset; what is left is the site's existing small-label pattern, the
 * same declarations .ca-technique__eyebrow carries.
 */
.ty-receipt__label {
	margin: 0 0 var(--space-16);
	max-width: none;
	font-family: var(--font-body);
	font-size: var(--type-eyebrow);
	font-weight: var(--weight-semibold);
	line-height: var(--leading-body);
	letter-spacing: var(--tracking-eyebrow);
	text-transform: uppercase;
	color: var(--ink-muted);
	/* !! THE LABEL IS INSIDE THE CARD'S SCROLLPORT, SO IT SCROLLS WITH
	 * THE TABLE UNLESS IT IS PINNED. 23.1b gives `.ty-receipt` its own
	 * `overflow-x: auto` so four columns of a receipt can scroll inside
	 * the card instead of over the page. This label sits in that same
	 * scrolling box, so dragging the table sideways carried it off with
	 * the columns: measured at 390 the card scrolls 51px and the label
	 * went from x=41 to x=-10, i.e. off the left edge, leaving the
	 * receipt with no name on it exactly while somebody is reading it.
	 *
	 * `sticky` rather than moving the label out of the card, because
	 * out of the card it is no longer inside the border and the radius
	 * that make the card a card, and the label is what names it.
	 * Horizontal-only: no `top`, so vertical scrolling is untouched. */
	position: sticky;
	left: 0;
}

/* The empty state. See hbchiropractic_thankyou_receipt_empty() for the
 * copy and for why the plugin's own sentence is not used.
 *
 * !! --ink, NOT --ink-muted, AND IT IS NOT A DEFAULT. `.ty-receipt__
 * intro` above the card is muted because it captions something the
 * visitor can already see. This sentence is the ONLY content the card
 * has when it fires, and it is the one asking them to go and find an
 * email or pick up the phone. The quietest type on the page is the
 * wrong place for the only instruction on it. */
.ty-receipt__empty {
	margin: 0;
	max-width: none;
	font-size: var(--type-detail);
	color: var(--ink);
}
/* !! .table-responsive IS ON THE TABLE ITSELF, NOT ON A WRAPPER.
 * Bootstrap means it for a parent div; applied to the <table> it sets
 * `display: block`, which drops the element out of table layout - the
 * cells stop sharing column widths and the footer rows stop lining up
 * with the body. Put back to `table` here, and the horizontal scroll it
 * was reaching for is given to the card instead, where it belongs. */
/* !! border-collapse: separate IS LOAD-BEARING FOR THE RADIUS, and it
 * is the same trap 21.6's order table hit. With `collapse` the cell
 * borders own the corners and a border-radius on the table is simply
 * ignored - no error, no warning, just square corners inside a card
 * with 24px ones. Separate borders plus zero spacing keeps the layout
 * identical and lets the corners round; `overflow: hidden` then clips
 * the header and footer fills to that curve so they do not square it
 * off again from the inside.
 *
 * --radius-nested, not --radius-md: this is a panel INSIDE a card that
 * already carries the large radius, and nesting the same value makes
 * the inner corner look wrong against the outer one.
 *
 * !! color: var(--ink) IS NOT DECORATION. The plugin's bundled
 * Bootstrap sets a body colour of #212529 and the table cells were
 * inheriting it, so every figure on this receipt was rendering in
 * Bootstrap's near-black rather than the site's. Measured, not
 * assumed - the checkout had the identical fault from a different
 * stylesheet, see round 2. */
.ty-receipt .upsell-order-list-table {
	display: table;
	width: 100%;
	margin: 0;
	font-size: var(--type-detail);
	color: var(--ink);
	border-collapse: separate;
	border-spacing: 0;
	border: 1px solid var(--rule);
	border-radius: var(--radius-nested);
	overflow: hidden;
	background: var(--surface-alt);
}
/* Horizontal separators only. The view ships .table-bordered, which
 * draws a box round all sixteen cells - a spreadsheet pasted into a
 * card, which is what this looked like. Every vertical comes out; the
 * horizontals stay, because with four columns they are what keeps a
 * figure tracking back to its label. */
.ty-receipt .upsell-order-list-table th,
.ty-receipt .upsell-order-list-table td {
	padding: var(--space-12);
	line-height: var(--leading-heading);
	border: 0;
	border-bottom: 1px solid var(--rule);
	vertical-align: top;
	text-align: left;
	color: var(--ink);
}
/* Money right, everything else left. This lands correctly on all three
 * row types by luck of the markup rather than by accident of it: the
 * price is the last cell of the head and of the body, and the footer's
 * value cell is a colspan that also ends at the right edge - so one
 * rule puts every figure on the same axis. */
.ty-receipt .upsell-order-list-table th:last-child,
.ty-receipt .upsell-order-list-table td:last-child { text-align: right; }

/* !! background-color STILL NEEDS !important HERE. The view writes
 * `style="background-color: #ececec"` on every <th>, and an inline
 * declaration outranks any selector regardless of specificity. The
 * header now takes the table's own fill instead. */
.ty-receipt .upsell-order-list-table thead th {
	background-color: transparent !important;
	font-size: var(--type-eyebrow);
	font-weight: var(--weight-semibold);
	letter-spacing: var(--tracking-eyebrow);
	text-transform: uppercase;
	color: var(--ink-muted);
	white-space: nowrap;
	/* The view's own 2px bootstrap header border, replaced with the
	 * site's hairline. */
	border-bottom: 1px solid var(--rule);
}
/* No rule under the line item - the subtotal beneath it is already a
 * change of voice, and a receipt with one purchase does not need a
 * grid to be read. */
.ty-receipt .upsell-order-list-table tbody td { border-bottom: 0; }
.ty-receipt .upsell-order-list-table tfoot td {
	font-size: var(--type-ui);
	font-weight: var(--weight-semibold);
	border-bottom: 0;
}
.ty-receipt .upsell-order-list-table tfoot tr:first-child td {
	color: var(--ink-muted);
}
/* The total is the one line somebody actually reads on this page, so it
 * gets the rule above it and the weight - the same treatment 21.6 gives
 * the checkout's Total, so the number looks the same on the page that
 * asks for it and the page that confirms it. */
.ty-receipt .upsell-order-list-table tfoot tr:last-child td {
	border-top: 1px solid var(--rule);
	font-size: var(--type-h5);
	font-weight: var(--weight-bold);
	color: var(--ink);
}
/* !! THIS RULE EXISTED SINCE ROUND 2 AND HAD NEVER ONCE APPLIED.
 * The view marks the footer's label cell `.text-right`, and that is a
 * Bootstrap UTILITY class - the whole point of which is that it ships
 * `text-align: right !important`. Specificity cannot touch it, so the
 * round 2 version of this line was decorative: the labels went on
 * being right-aligned inside column one, stranded halfway across a
 * 960px table with their own figures at the far edge.
 *
 * It was invisible at the width it was written at. On a phone column
 * one is narrow, so "Order Total" right-aligned in it looks left
 * aligned, and round 3's 390 measurement recorded exactly that and
 * read it as correct. It only shows on a desktop.
 *
 * Left, so the labels line up under Date Purchased while the figures
 * line up under Purchase Price - which is what makes the subtotal and
 * the total read as the same column as the line item they sum. */
.ty-receipt .upsell-order-list-table tfoot .text-right { text-align: left !important; }
/* An empty div the view prints for digital downloads there are none of. */
.ty-receipt .upsell-order-item_digital-files:empty { display: none; }

/* ---- 23.2  the two cards  (838:87) ----
 *
 * 425 / 24 / 691 in a 1140 frame. The narrow card is the one with the
 * single action in it and the wide one is the one with the reading in
 * it, which is why they are not halves.
 *
 * !! align-items: stretch IS THE DEFAULT AND IS WANTED HERE, unlike
 * 21.1 where it had to be turned off. The frame draws both cards 412
 * tall with their buttons on one baseline, and two cards of different
 * heights side by side would read as one of them being unfinished. */
.ty-cards {
	display: grid;
	grid-template-columns: minmax(0, 425fr) minmax(0, 691fr);
	gap: var(--space-24);
}
.ty-cards > .wp-block-group__inner-container { display: contents; }

/* !! THE FRAME PUTS 50 BETWEEN THIS PAGE'S SECTIONS AND THE HOUSE PUTS
 * 100, AND THE HOUSE WINS - the same call the five text pages made in
 * section 19 and for the same reason. Measured off 809:199: the header
 * ends at 353 and the cards start at 403, the cards end at 839 and
 * "Where to Find Us" starts at 889. Fifty every time, on one white
 * ground.
 *
 * The house rule is 50 + 50 on a shared ground, which is 100, and it is
 * what every other page on this site does between two white sections.
 * These three are .itv-section and take it from 10.1 with nothing
 * added here. Shipping this one page at half the site's rhythm would
 * make it the odd page out for a reason no visitor would ever see. */

/* The flex row inside the header has to survive WordPress's group
 * wrapper, or the heading and the checkmark stack. */
.ty-head__row > .wp-block-group__inner-container { display: contents; }

/* Both cards are columns with the button pushed to the bottom by
 * margin-top:auto on the button line - that is what puts the two
 * buttons on one baseline when the copy above them is different
 * lengths, and it survives the copy changing. */
.ty-card {
	display: flex;
	flex-direction: column;
	border-radius: var(--radius-md);
	padding: var(--space-40);
	overflow: hidden;
}
.ty-card > .wp-block-group__inner-container {
	display: flex;
	flex-direction: column;
	flex: 1;
}
.ty-card h2 { margin: 0 0 var(--space-16); font-size: var(--type-h3); max-width: none; }
.ty-card p  { margin: 0 0 var(--space-16); max-width: none; }
/* !! .ty-card .ty-card__cta, NOT .ty-card__cta, AND IT IS THE WHOLE
 * REASON THE BUTTONS LINE UP.
 *
 * Written as a bare `.ty-card__cta` first and the auto margin never
 * applied: `.ty-card p` above scores (0,1,1) and a bare class scores
 * (0,1,0), so the paragraph rule's `margin: 0 0 16` won and both
 * buttons sat directly under their last line of copy instead of at the
 * foot of the card. Measured: 875 and 864, eleven pixels apart, with 56
 * and 67 pixels of unused card below them. It looked like a rounding
 * problem and was a specificity one.
 *
 * The trap in its sixth costume - 10, 15, 16b, 19, 20 and 21 log the
 * others. This file's own header warns about it and it is still the
 * single most common way a rule here fails. */
.ty-card .ty-card__cta { margin: auto 0 0; }

/* THE NAVY CARD, with the photograph bleeding off its right edge.
 *
 * media 230, Card-Adjustment-Photo-Faded, 253x407 - which is the card's
 * full height and 60% of its width. It is positioned rather than in
 * flow so the copy can run over its left edge, exactly as the frame
 * draws it, and it is faded into the navy rather than sitting on it.
 *
 * !! media 231, Card-One-Thing-Left-Composite, is 425x412 - this WHOLE
 * CARD as one flat image, copy and button included. Not used, for the
 * same reason 20.2 declines the offer panel's composite: the heading
 * would stop being a heading and the button would stop being a button.
 */
.ty-card--do {
	position: relative;
	isolation: isolate;
	background: var(--gradient-navy);
	color: var(--on-invert);
}
.ty-card--do h2, .ty-card--do p { color: var(--on-invert); }
.ty-card__photo {
	position: absolute;
	z-index: -1;
	top: 0;
	right: 0;
	bottom: 0;
	width: 60%;
	margin: 0;
	pointer-events: none;
}
.ty-card__photo > img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
	/* The export is a full-strength photograph; the fade is ours, the
	 * same finding as 21.5. A mask rather than a flat opacity, because
	 * the frame blends it INTO the navy from the left rather than
	 * dimming the whole thing evenly. */
	opacity: .55;
	-webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 45%);
	mask-image: linear-gradient(90deg, transparent 0%, #000 45%);
}
/* The copy has to clear the photograph. 55% keeps every line off the
 * subject at the frame's own proportion.
 *
 * !! THE BUTTON IS EXCLUDED, BECAUSE IT IS NOT COPY. The CTA line is a
 * <p> and was being capped with the rest: at this card's width 55% is
 * 234px holding a 356px button, so the pill overflowed its own
 * paragraph. Nothing clipped it today - the card's overflow:hidden is
 * on a box the button happens to fit inside - but a slightly longer
 * label or a narrower card and it would have. A button is an object;
 * it takes the width it needs. */
.ty-card--do h2,
.ty-card--do p:not(.ty-card__cta) { max-width: 55%; }

/* THE WHITE CARD. A hairline rather than a shadow: it sits beside a
 * navy block, and a drop shadow next to a solid colour reads as the
 * white card floating and the navy one not. */
.ty-card--before {
	background: var(--surface);
	border: 1px solid var(--rule);
}
.ty-card--before ul {
	margin: 0 0 var(--space-24);
	padding-left: var(--space-24);
	list-style: disc;
	max-width: none;
}
.ty-card--before li { margin-bottom: var(--space-8); }
.ty-card--before li:last-child { margin-bottom: 0; }

/* ---- 23.3  where to find us  (814:152) ---- */
.ty-find > .wp-block-group__inner-container > h2 { margin: 0 0 var(--space-24); }
.ty-find__address {
	margin: 0 0 var(--space-32);
	font-size: var(--type-body-lg);
	max-width: none;
}
/* The map is the footer's component at a different shape. .map-embed
 * already carries the iframe sizing and the radius; only the aspect and
 * the ground differ, because the footer's is a translucent white on
 * navy and this one sits on the page. 1140x360 is 19:6. */
.ty-map {
	aspect-ratio: 19 / 6;
	background: var(--surface-alt);
	margin-bottom: var(--space-32);
}
.ty-find__note {
	margin: 0;
	color: var(--ink-muted);
	font-size: var(--type-small);
}


/* ==================================================================
 * 23b  THANK YOU - RESPONSIVE
 * ================================================================== */

@media (max-width: 991px) {
	/* The cards stack. The navy one keeps its photograph but the copy
	 * stops being capped at 55%, because at one column the card is
	 * twice as wide and the photograph is a strip down the side of a
	 * much longer block of text. */
	.ty-cards { grid-template-columns: 1fr; }
	.ty-card--do h2,
	.ty-card--do p:not(.ty-card__cta) { max-width: 70%; }

	/* The receipt takes the whole column once the page stacks. 75% of a
	 * phone is not a card, it is a column of wrapped cells with two
	 * margins doing nothing. Its own inset drops with it for the same
	 * reason .ck-card's does at this width. */
	.ty-receipt { width: 100%; padding: var(--space-16); }
	/* Same call as the receipt: 75% of a phone is not a card. The
	 * min-height goes too - the widget is taller when its own columns
	 * stack, so holding a floor that is now below its real height only
	 * risks a gap. */
	.ty-sched { width: 100%; padding: var(--space-16); min-height: 0; }
	.ty-sched__title { margin-top: var(--space-40); }

	/* !! THE nowrap ON THE HEADERS COMES OFF HERE, AND IT IS THE
	 * DIFFERENCE BETWEEN A RECEIPT AND A SCROLLBAR.
	 *
	 * Four headers held on one line each give the table a 555px
	 * min-content width. Inside a 294px card at 390 that is a
	 * horizontal scroll to reach the price - on the one page where the
	 * price is the whole point, and on the device most of these buyers
	 * are holding. Letting "Date Purchased" and "Purchase Price" take
	 * two lines each costs one row of header height and fits the table
	 * in the card.
	 *
	 * The card keeps its overflow-x regardless: it is the backstop for
	 * a long product name, which is content nobody here controls. */
	.ty-receipt .upsell-order-list-table thead th { white-space: normal; }
	.ty-receipt .upsell-order-list-table th,
	.ty-receipt .upsell-order-list-table td {
		padding: var(--space-8) calc(var(--space-8) / 2);
	}

	/* !! THE FOOTER FIGURE GOES BACK TO THE LEFT HERE, AND WITHOUT THIS
	 * THE TOTAL IS OFF-SCREEN.
	 *
	 * 23.1b right-aligns the last cell of every row so the subtotal and
	 * the total line up under Purchase Price with the line item they
	 * sum. That is right on a desktop and actively harmful here: the
	 * table is 375px of content inside a 262px window, so the right
	 * edge - and with it both figures - sits outside the card until the
	 * visitor scrolls sideways. Caught in a 390 screenshot showing
	 * "Subtotal" and "Order Total" with nothing beside them.
	 *
	 * The footer's value cell is a colspan that starts at column two,
	 * so left-aligning it parks the figure immediately after its own
	 * label, where it is visible without scrolling. Column alignment is
	 * what is given up, and at a width where the table scrolls anyway
	 * there is no column to align to. */
	.ty-receipt .upsell-order-list-table tfoot td:last-child {
		text-align: left;
	}
}

@media (max-width: 767px) {
	/* !! THE PHOTOGRAPH COMES OUT BELOW HERE.
	 *
	 * It is 253px of a person mid-adjustment behind 70% of a card's
	 * width. On a phone the card is about 342 wide, so the image would
	 * be a 205px strip with three lines of white text over the top of
	 * it - and the copy is the part that matters, because it is the
	 * one instruction left on the page. Removed rather than shrunk; the
	 * navy card still reads as the card with the job in it. */
	.ty-card__photo { display: none; }
	.ty-card--do h2,
	.ty-card--do p:not(.ty-card__cta) { max-width: none; }
	.ty-card { padding: var(--space-32); }
	/* A 19:6 map is a 60px-tall letterbox on a 342px screen. 4:3 is a
	 * map somebody can actually read a street off. */
	.ty-map { aspect-ratio: 4 / 3; }
}

@media (max-width: 599px) {
	.ty-card { padding: var(--space-24); }

	/* !! THE BUTTONS INSIDE A CARD WRAP HERE, OR THEY GET CUT IN HALF.
	 *
	 * .cta-button carries `white-space: nowrap`, which is right for a
	 * pill in a header or a footer row with a whole viewport to sit in.
	 * Inside a card it is not. Measured at 390: "See what your first
	 * visit looks like ->" renders 377px wide in a 292px card, and
	 * .ty-card's overflow:hidden - which is there to clip the
	 * photograph to the corner radius - CLIPPED THE BUTTON. Its right
	 * edge and the last of its label were simply gone. "Complete Your
	 * Intake Form ->" was losing 8px the same way.
	 *
	 * Full width and wrapping is the standard phone treatment and it
	 * cannot overflow anything. Two lines of button is better than one
	 * line of half a button.
	 *
	 * .bk-value__cta is here too, and it belongs to page 25 rather than
	 * this one. Same card, same phone, same rule - its "Get Started -
	 * $50 Deposit" measured 295 in a 294 box, one pixel from the same
	 * fault. Split across two sections it would be found once and fixed
	 * once. */
	.ty-card__cta a.cta-button,
	.bk-value__cta a.cta-button {
		display: block;
		white-space: normal;
		text-align: center;
	}

	/* MOST THINGS CENTRE on a phone, the standing rule - the headings
	 * and the objects, not the running copy. The card bodies and the
	 * bullets keep their left edge: they are read, and a centred list
	 * of three instructions has no edge to read back to. */
	.ty-head__row { justify-content: center; }
	.ty-find { text-align: center; }
	.ty-find__note { text-align: left; }
	.ty-card--before ul { text-align: left; }
}



/* ===================================================================
 * 24  CONTACT
 *
 * VISUAL SOURCE: Figma frame 870:88, "(unverified) Contact".
 *
 * !! THE FRAME IS NAMED "(unverified)" AND IT IS THE ONLY ONE ON THIS
 * SITE THAT IS. Every other page was built from a frame the designer
 * had signed off. This one carries the marker in its own name, and it
 * had no node ID recorded anywhere in the handoff chain until Gerek
 * supplied the URL on 2026-08-28 - the build notes said twice that it
 * was the one undesigned page. Treat the layout below as built to the
 * frame but expect a review pass, and read the divergences at the foot
 * of this banner before assuming any of them is a bug.
 *
 * FOUR SECTIONS in the frame - Header/Nav, Page Header, Body, Final
 * CTA + Footer. Two of those are global furniture this page does not
 * build: the header comes from header.php and the closing navy region
 * comes from footer.php on all nineteen pages. So the page's own CSS
 * is the middle two, and they are:
 *
 *   24.1  the page header      870:181
 *   24.2  the two-column body  875:92
 *   24.3  the info column      870:186
 *   24.4  the message card     870:195
 *   24.5  the week strip       876:92
 *
 * !! THE CLOSING CTA IS NOT BUILT HERE AND ITS COPY IS NOT IN THIS
 * FILE. 872:91 draws "Ready to Book?", the two buttons and the
 * "Questions first?" line. That is hbchiropractic_cta() on every page.
 * Its two lines come from _itv_cta_heading / _itv_cta_subhead on the
 * page, set to the frame's wording. Building it again here would put a
 * second navy CTA under the first.
 *
 * ---- DECISIONS THAT DIFFER FROM THE FIGMA FRAME ----
 *
 * 1. THE VISIBLE BREADCRUMB IS NOT BUILT. 870:182 draws "Home ·
 *    Contact" above the H1. Standing decision carried from pages 13,
 *    14, 15, 16, 17, 18 and 19: the BreadcrumbList JSON-LD at the foot
 *    of the content is what engines read, and the visible trail came
 *    out on request. This page's JSON-LD already carries Home >
 *    Contact and is correct. Leave the schema alone.
 *
 * 2. THE SITE'S TYPE LADDER WINS. The frame runs this page at the same
 *    smaller scale as the five utility pages - H1 36, H2 32, body 17 -
 *    against this site's 50 / 40 / 22. Following it would put a fourth
 *    H1 size on the site, which is precisely what 15.2a closed on
 *    2026-08-27 after Gerek said the Y-Strap headline read small. Same
 *    call section 19 made, for the same reason.
 *
 * 3. THE MAP IS AN EMBED, NOT A PICTURE. 869:170 places a flat PNG
 *    screenshot of Google Maps. A screenshot of a map is not a map: it
 *    does not pan, it cannot be opened for directions, and it goes
 *    stale silently. [itv_map] already exists, already renders a live
 *    iframe from hbchiropractic_maps_url(), and the footer and the
 *    Thank You page both use it. Reused rather than reinvented.
 *
 * 4. THERE ARE NO PHOTOGRAPHS ON THIS PAGE, and that is Gerek's call
 *    2026-08-28 rather than an omission. The media library has 123
 *    attachments and not one of them is a contact, exterior, office or
 *    reception photograph - checked before building. The frame draws
 *    none either. Do not invent a slot; the second page on this site
 *    with no image, after the five text pages.
 *
 * 5. THE OPEN-DAYS CARD IS FOUR DAY NAMES, NOT ONE LABEL. 876:93 draws
 *    Monday / Tuesday / Wednesday / Thursday as four columns sharing
 *    one line of hours. hbchiropractic_hours() stores that as a single
 *    "Monday – Thursday" row, which is right for the footer's list.
 *    The helper now carries an optional `days` array; see its note.
 * ================================================================== */

/* ---- 24.1  THE PAGE HEADER  (870:181) ----
 *
 * H1 and one line of lede. No image, no eyebrow, no breadcrumb - so
 * there is almost nothing to write: .itv-section already gives the
 * container, the centring, the gutter and the half rhythm.
 *
 * The lede is --ink-muted where the H1 is --ink, sampled off the
 * render (rgb(92,91,87) on 870:184), and capped at --measure so it
 * does not run the full 1400 as a single line. */
.cn-head { padding-top: var(--space-24); }
.cn-head h1 { margin: 0; }
.cn-head > p {
	max-width: var(--measure);
	margin: var(--space-16) 0 0;
	color: var(--ink-muted);
}

/* ---- 24.2  THE TWO-COLUMN BODY  (875:92) ----
 *
 * !! THE COLUMN RATIO IS THE FRAME'S, RE-DERIVED FOR A WIDER
 * CONTAINER RATHER THAN COPIED.
 *
 * The frame lays 596 of info beside 480 of card with a 64 gap, inside
 * its own 1140 content column. Copying 480 straight across would leave
 * the card at 34% of this site's 1400 instead of the frame's 42%, and
 * the card would read as an afterthought beside a very wide copy
 * column. So the RATIO moves, not the pixel value: 596:480 across
 * (1400 - 64) puts the card at 530.
 *
 * minmax(0, 1fr) on the info column rather than 1fr, because a grid
 * item's default min-width is auto and the map iframe inside it would
 * otherwise refuse to shrink below its own intrinsic width. */
/* !! THE INNER CONTAINER HAS TO BE DISSOLVED HERE TOO, AND FORGETTING
 * IT IS A SILENT FAILURE RATHER THAN AN ERROR.
 *
 * WordPress wraps a group block's children in an extra
 * .wp-block-group__inner-container. 10.1 already dissolves that for
 * .itv-section, which is why .cn-head and .cn-body are fine - but
 * .cn-grid and .cn-info are groups that are NOT sections, so their
 * wrappers survive. The grid then has exactly ONE child, both columns
 * render at the full 798, and the card stacks under the copy looking
 * for all the world like a breakpoint that fired early. Measured on
 * the built page before this rule existed: grid-template-columns
 * computed to "798px 530px" and both children were 798 wide.
 *
 * This is the same bug the build notes list as a gotcha that already
 * bit once. It is on the two grid PARENTS only; adding it to a wider
 * selector would dissolve wrappers other pages rely on. */
.cn-grid > .wp-block-group__inner-container,
.cn-info > .wp-block-group__inner-container {
	display: contents;
}

.cn-grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 530px;
	gap: var(--space-64);
	align-items: start;
}
.cn-body > .cn-hours { margin-top: var(--space-64); }
/* The section before the closing CTA pays the full pad into it, the
 * same as .itv-prose does on the five text pages. */
.cn-body { padding-bottom: var(--section-pad); }

/* ---- 24.3  THE INFO COLUMN  (870:186) ----
 *
 * Two H2s, a phone number, a paragraph, an address and the map. The
 * H2s are the kit's; only the three page-scoped blocks are here.
 *
 * !! THE SECOND H2 NEEDS SPACE ABOVE IT AND THE FIRST MUST NOT HAVE
 * ANY. "Visit Us" opens a new subject and the frame gives it the same
 * 20px gap as everything else in the column, which reads as cramped at
 * this site's 40px H2. The first-child guard is what keeps that space
 * off the top of the column - the same shape 19.2 uses on the text
 * pages. */
.cn-info > * { margin-block: 0; }
.cn-info > * + * { margin-top: var(--space-20); }
.cn-info h2 { margin-top: var(--space-48); }
.cn-info h2:first-child { margin-top: 0; }

/* The phone is the first thing the page is for, so it is set larger
 * than the copy around it and it is a real tel: link at every width -
 * the standing rule from section 04's header note. */
.cn-info__phone {
	font-family: var(--font-heading);
	font-size: var(--type-h4);
	font-weight: var(--weight-semibold);
	line-height: var(--leading-heading);
}
.cn-info__phone a { color: var(--ink); text-decoration: none; }
.cn-info__phone a:hover,
.cn-info__phone a:focus-visible { text-decoration: underline; }

.cn-info__lede { color: var(--ink-muted); max-width: var(--measure); }

/* The address is three lines held together by <br>, not three
 * paragraphs: it is one block of information and a screen reader
 * should not hear three separate items. --leading-body rather than the
 * default so the three lines sit as a block. */
.cn-info__address { line-height: var(--leading-body); }

/* !! THE MAP KEEPS THE FRAME'S SHAPE, NOT ITS PIXELS. 869:170 is
 * 596x314, which is 1.9:1. aspect-ratio holds that at every width
 * instead of the iframe's own 600x400 attributes, which would go 3:2
 * and eat 100px more vertical on a phone.
 *
 * .map-embed is the shared component the footer and the Thank You page
 * both use; the class attribute on [itv_map] is what scopes these two
 * overrides to this page. */
.cn-map {
	margin-top: var(--space-24);
	border-radius: var(--radius-md);
	overflow: hidden;
}
.cn-map iframe {
	display: block;
	width: 100%;
	height: auto;
	aspect-ratio: 596 / 314;
	border: 0;
}

/* ---- 24.4  THE MESSAGE CARD  (870:195) ----
 *
 * !! THE FIELD TREATMENT IS SECTION 20'S, RESTATED RATHER THAN
 * SHARED, AND THE NEXT FORM SHOULD END THAT.
 *
 * .bk-form__input and .cn-form__input are now the same six
 * declarations twice. That is one duplication, which is cheaper than
 * editing a live funnel page's markup to introduce a shared class -
 * but it is exactly the shape that commit 19196fc promoted out of the
 * .ca- namespace ("before a third page needs them"). IF A THIRD FORM
 * IS EVER BUILT, promote both to .itv-field* and delete these.
 *
 * Everything the two forms disagree about is deliberate and is
 * documented in inc/contact-form.php's header - different guard
 * bucket, optional phone, a textarea, no CRM leg, and a delivery
 * failure the visitor is actually told about.
 *
 * The card is white on white with a hairline and a lift, per the
 * frame: --shadow-tile is this file's reading of its 0 16px 24px
 * rgba(13,23,41,0.1), and it is already what the tiles elsewhere use. */
.cn-form {
	background: var(--surface);
	border: 1px solid var(--rule);
	border-radius: var(--radius-md);
	padding: var(--space-32);
	box-shadow: var(--shadow-tile);
	/* The success and error notices land here, and #message is the
	 * redirect target, so the card needs to arrive clear of the top of
	 * the window rather than flush against it. */
	scroll-margin-top: var(--space-48);
}
.cn-form__title {
	margin: 0 0 var(--space-20);
	font-family: var(--font-heading);
	font-size: var(--type-h2);
	font-weight: var(--weight-bold);
	line-height: var(--leading-heading);
	letter-spacing: var(--tracking-heading);
	color: var(--ink);
}
.cn-form__row { margin: 0 0 var(--space-16); }

/* --type-ui, NOT --type-body, and not anything under 16px. A form
 * control is UI rather than running copy, and iOS Safari zooms the
 * page when a focused input is smaller than 16px and does not zoom
 * back out. See 20.3's note - the same one-pixel margin applies here,
 * because 1.8b steps --type-ui to 17 at <=599. */
.cn-form__input {
	width: 100%;
	padding: var(--space-12) var(--space-16);
	border: 1px solid var(--rule);
	border-radius: var(--radius);
	background: var(--surface-alt);
	color: var(--ink);
	font-family: var(--font-body);
	font-size: var(--type-ui);
	line-height: var(--leading-body);
}
.cn-form__input::placeholder { color: var(--ink-muted); opacity: 1; }
.cn-form__input:focus {
	border-color: var(--navy);
	background: var(--surface);
}
.cn-form__input.is-invalid { border-color: var(--alert); }

/* The message box. 870:203 is 140 tall against the 50 of a single
 * field; resize:vertical because a visitor writing more than four
 * lines should be able to see what they wrote, and horizontal resize
 * would break the card. */
.cn-form__input--message {
	min-height: 140px;
	resize: vertical;
}

/* The submit button runs the full width of the card in the frame
 * (870:205), which is not what .cta-button does anywhere else on this
 * site - so the width is set here rather than by adding a modifier to
 * the shared component. The three UA-reset lines are 20.3's, for the
 * same reason: this is a <button>, not an <a>. */
.cn-form__submit { margin: var(--space-24) 0 var(--space-12); }
.cn-form .cta-button {
	display: block;
	width: 100%;
	border-width: 2px;
	cursor: pointer;
	appearance: none;
	text-align: center;
}
.cn-form__note {
	margin: 0;
	color: var(--ink-muted);
	font-size: var(--type-xs);
	line-height: var(--leading-body);
	text-align: center;
}

/* !! THIS FORM HAS TWO NOTICE TONES AND THE BOOKING FORM HAS ONE.
 *
 * A booking form that fails sends the visitor back to a form; a
 * contact form that SUCCEEDS has to say so, because nothing else on
 * the page changes and the visitor has no other way to know the
 * message went. Both are a left rule and a tinted ground rather than
 * coloured type, which is 20.3's call: --alert and --success are marks
 * here, not text colours. */
.cn-form__notice {
	margin: 0 0 var(--space-16);
	padding: var(--space-12) var(--space-16);
	background: var(--surface-alt);
	color: var(--ink);
	font-size: var(--type-small);
	line-height: var(--leading-body);
}
.cn-form__notice--error   { border-left: 3px solid var(--alert); }
.cn-form__notice--success { border-left: 3px solid var(--success); }

/* ---- 24.5  THE WEEK STRIP  (876:92) ----
 *
 * !! IT IS A SEVEN-COLUMN GRID AND THAT IS WHAT MAKES THE WIDTHS COME
 * OUT RIGHT WITHOUT A SINGLE MAGIC NUMBER.
 *
 * The frame draws 648 + 156 + 156 + 156 with 8px gaps inside its 1140
 * column. 648 is not an arbitrary width: it is the open-days card
 * holding FOUR day names, and 648/4 = 162 against the closed cards'
 * 156. So the strip is seven equal columns - four for Monday through
 * Thursday, one each for Friday, Saturday and Sunday - and the wide
 * card spans four of them. The ratio then holds at any container width
 * instead of being re-derived, and adding or removing an open day is a
 * change to the span, not to four pixel values.
 *
 * The three treatments are read off the frame and driven by the DATA
 * rather than by weekday, in the shortcode: a row whose only range is
 * "Closed" gets the flat grey card, a single open day gets the
 * outlined one, and a spanning row gets navy. Change the hours and the
 * treatment follows. */
.cn-hours {
	display: grid;
	grid-template-columns: repeat(7, minmax(0, 1fr));
	gap: var(--space-8);
	align-items: stretch;
}
.cn-day {
	display: flex;
	flex-direction: column;
	gap: var(--space-8);
	padding: var(--space-16);
	border-radius: var(--radius);
	font-size: var(--type-detail);
	line-height: var(--leading-body);
}
.cn-day__days,
.cn-day__name {
	margin: 0;
	font-weight: var(--weight-semibold);
}
.cn-day__hours { margin: 0; }

/* The open block. Navy ground, lit edge, four day names spread across
 * the card with the shared hours centred beneath them. */
.cn-day--span {
	grid-column: span 4;
	background: var(--navy);
	border: 2px solid var(--navy-lit);
	color: var(--on-invert);
	box-shadow: var(--shadow-card);
}
.cn-day--span .cn-day__days {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-8);
}
/* Each day name takes an equal share, so the four sit on the same
 * rhythm as the three cards beside them rather than bunching left. */
.cn-day--span .cn-day__days > span { flex: 1 1 0; min-width: 0; }
.cn-day--span .cn-day__hours { text-align: center; }

/* Closed. The flat card - no border, no lift, muted type: it is the
 * one state that should not draw the eye. */
.cn-day--closed {
	background: var(--surface-alt);
	color: var(--ink-muted);
}

/* Open, but not the main block - Saturday's "By appointment only".
 * Outlined rather than filled, so it reads as available without
 * competing with the four days that keep normal hours. */
.cn-day--limited {
	background: var(--surface);
	border: 2px solid var(--navy);
	box-shadow: var(--shadow-card);
}
.cn-day--limited .cn-day__name  { color: var(--ink); }
.cn-day--limited .cn-day__hours { color: var(--ink-muted); }


/* ===================================================================
 * 24b  CONTACT - RESPONSIVE
 * ================================================================== */

/* The card is 530 and the info column needs about 460 before the
 * address starts wrapping mid-line, so the two columns stop fitting
 * well before the container does. 1199 is where the rest of this site
 * stacks its two-column panels (10.9, 11.3, 13, 14) and there is no
 * reason for this page to pick a different number. */
@media (max-width: 1199px) {
	.cn-grid {
		grid-template-columns: minmax(0, 1fr);
		gap: var(--space-48);
	}
	/* !! THE CARD IS CAPPED AND CENTRED RATHER THAN LEFT TO RUN FULL
	 * WIDTH. A message form at 1100px puts a 4-line textarea across
	 * the whole page and the Submit button with it, which reads as a
	 * different component from the one above 1199. --container-panel
	 * is what the other stacked panels on this site cap to. */
	.cn-form {
		max-width: var(--container-panel);
		margin-inline: auto;
		width: 100%;
	}
	/* The copy column takes the same cap so the two stacked blocks
	 * share a left and right edge instead of one being wider than the
	 * other. */
	.cn-info {
		max-width: var(--container-panel);
		margin-inline: auto;
	}
	/* !! THE MAP STOPS GROWING WITH THE PAGE, AND THIS IS A REAL BUG
	 * THAT THE STACK CREATES RATHER THAN A PRECAUTION.
	 *
	 * Above 1199 the map fills the info column, which is what the frame
	 * draws - 596 of map in a 596 column. The moment the columns stack,
	 * that column becomes the whole page: measured at 1100, the map went
	 * to 1052 wide and, at its 1.9:1, 554 tall. That is half the visible
	 * page given to a locator, and it reads as a hero photograph on the
	 * one page that deliberately has no photographs.
	 *
	 * 640 is a little over the frame's own 596 - enough that the street
	 * names stay readable, not so much that it takes over. It is left
	 * aligned rather than centred because the address directly above it
	 * is, and the two are one block of information. */
	.cn-map { max-width: 640px; }
	.cn-body > .cn-hours { margin-top: var(--space-48); }
}

/* The strip's seven columns stop working before the layout does: at
 * 991 each closed card is about 130 wide and "By appointment only"
 * breaks onto three lines inside it. The open block goes full width
 * and the three single days share a row beneath it. */
@media (max-width: 991px) {
	.cn-hours { grid-template-columns: repeat(3, minmax(0, 1fr)); }
	.cn-day--span { grid-column: span 3; }
}

@media (max-width: 767px) {
	/* Two of the three closed cards fit on a phone-ish width; the
	 * third wraps under them, which is why the open block spans two
	 * rather than three here. */
	.cn-hours { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.cn-day--span { grid-column: span 2; }
	/* The four day names stop being one row and become 2x2. Below this
	 * width four names across a phone is about 78px each, which breaks
	 * "Wednesday" in half. */
	.cn-day--span .cn-day__days > span { flex: 1 1 40%; }
	.cn-day--span .cn-day__hours { text-align: left; }
}

@media (max-width: 599px) {
	/* One column. The map keeps its shape - it is the one thing on the
	 * page that has to stay legible at 390, and 1.9:1 is already the
	 * shallowest ratio on this site. */
	.cn-hours { grid-template-columns: minmax(0, 1fr); }
	.cn-day--span { grid-column: span 1; }
	/* The card loses half its padding, matching what 20b does to the
	 * booking form at the same width. */
	.cn-form { padding: var(--space-24); }
	/* The H2 above "Visit Us" keeps space, but 48 is a lot of empty
	 * navy-free page on a phone between two short blocks. */
	.cn-info h2 { margin-top: var(--space-32); }
}


/* ===================================================================
 * 12. THE TABLET BAND
 *
 * Everything above treats "tablet" as the tail of desktop or the head
 * of mobile, and it is neither.
 *
 * !! THE FLOOR IS 600, NOT 768. An iPad Mini in portrait is 744 CSS
 * pixels and an iPad Air is 820 - the two most common tablets sit on
 * OPPOSITE sides of 767, so a band starting at 768 dresses one as a
 * tablet and the other as a phone. Built at 768 first and measured at
 * 744: phone header, 34px headline, 406px video, while 768 got 260/50/
 * 624. That step change across 24 pixels of screen was the bug.
 *
 * 600 is the floor because that is where the type ladder already
 * switches (see <=599), so layout and type now change at the same place.
 *
 * The header is the one thing that CANNOT simply move down with the
 * rest: below 768 it is a two-row bar, because a single row cannot hold
 * the logo, the phone, the button and the toggle. The logo still grows
 * in both layouts - in the two-row one it has a row to itself, which is
 * why 260 fits there at widths where a single row could not take it.
 *
 * These blocks are LAST in the file on purpose. They score the same as
 * the max-width blocks above and win on order.
 * =================================================================== */

/* ---- 12.1 the whole band, 600-991 ---- */
@media (min-width: 600px) and (max-width: 991px) {
	/* !! THE LOGO WAS 114px FROM 768-991 AND 215 BELOW IT.
	 *
	 * The <=991 rule shrank it to 114 for a bar that also had to carry
	 * the full navigation, then the nav moved into the hamburger at
	 * 1199 and nothing ever gave the width back. The mark ended up at
	 * less than half the size of the one in the panel behind the same
	 * hamburger. Measured at 768: the row used 518 of 768, with 170
	 * spare - the space was simply going unclaimed.
	 *
	 * 260 matches the panel logo exactly, which is what Gerek asked
	 * for: open the menu and the mark does not change size. It fits the
	 * two-row header below 768 as well - that row carries only the mark
	 * and a 46px toggle, so at 640 it has 650px to place 260 in. */
	.site-header__logo img { width: 260px; }

	/* The 72/48 indent is asymmetric because at desktop the right edge
	 * of this column fades into the photograph and needs no matching
	 * margin. Stacked there is no fade to sit against, so the asymmetry
	 * just reads as copy pushed off-centre. */
	.hp-hero__col {
		width: 100%;
		padding-left: var(--space-48);
		padding-right: var(--space-48);
	}
	/* THE VIDEO TAKES THE COLUMN. It was pinned to min(406px, 100%) -
	 * the width it needs BESIDE the copy at desktop - and left-aligned,
	 * so on a 720px panel it sat 406 wide starting 96px in: neither
	 * full nor centred. 16:9 throughout, so widening only scales it. */
	.hp-frame--hero-video, .hp-media--hero-video {
		width: 100%;
		margin-inline: auto;
	}

	/* ---- the footer stacks across the WHOLE band ----
	 *
	 * It used to go two-up from 768: hours beside the map. There is not
	 * enough panel for that with the hours written out in full. To keep
	 * "By appointment only" (172px) on one line the hours column needs
	 * 356 minimum, and 479 to also hold "7:00am - 11:00am, 2:00pm -
	 * 6:00pm"; against a 630px panel at 768 that leaves the map 119.
	 * Two columns and a readable hours list do not both fit until about
	 * 950, which is nearly desktop.
	 *
	 * So the tablet footer is one centred column, as it already was
	 * below 768 - and the map gets to be BIGGER as a result, not
	 * smaller, because it no longer shares a row. This is the opposite
	 * of the first attempt, which squeezed the hours to 299 and made
	 * every long line wrap.
	 *
	 * Mirrors the <=767 rules deliberately rather than widening them,
	 * so the phone footer keeps its own block and stays readable. */
	.site-footer__grid {
		grid-template-columns: 1fr;
		gap: var(--space-32);
		justify-items: center;
	}
	.site-footer__grid > * { min-width: 0; max-width: 100%; }
	.site-footer__brand { grid-column: auto; }
	.site-footer__hours { justify-items: center; text-align: center; }
	/* Centred as a BLOCK, not cell by cell: max-content shrinks the grid
	 * to the pairs themselves and the auto margins centre the result.
	 * text-align returns to left inside it, because centring each cell
	 * of a two-column list leaves the values on a ragged edge. */
	.site-footer__hours dl {
		width: max-content;
		max-width: 100%;
		margin-inline: auto;
		text-align: left;
	}
	/* 300 -> 380. The map had to be small while it shared a row; it no
	 * longer does, and Gerek asked for it bigger. */
	.site-footer__visit {
		justify-items: center;
		text-align: center;
		width: min(380px, 100%);
		justify-self: center;
	}

	/* ---- component text ----
	 *
	 * Not the running copy - body is already 22 here. These are the
	 * smaller sizes INSIDE tiles, cards and diagrams, set off the
	 * detail/ui/xs tokens, which never moved when the body did.
	 * Measured at 768 against a 22px body: tile text 17, card text 19,
	 * diagram labels 15 - small enough that Gerek read them as still
	 * being at the phone size.
	 *
	 * Scoped to the components rather than lifted at the token, because
	 * --type-detail also drives the footer hours, and the whole point of
	 * the condensing above is to make that block SHORTER. */
	.hp-photo__body { font-size: 20px; }   /* "Digital X-ray Review" etc */
	.hp-card__body  { font-size: 20px; }   /* the numbered cards */
	.hp-steps--ours .hp-step { font-size: 20px; }  /* the green steps */
	.hp-steps:not(.hp-steps--ours) .hp-step { font-size: 18px; } /* "other chiropractors" */

	/* The same call for the Chiropractic Adjustments page - section 10.
	 * Its technique-card body and its condition chips are set off
	 * --type-small and --type-ui, which are 19 at every width above 599
	 * and read as phone type inside a card on a tablet. The stat label
	 * in the credentials panel is --type-xs, 15, which is a caption on a
	 * desktop and illegible on a tablet held at arm's length. */
	.ca-technique__body { font-size: 20px; }
	.ca-chip            { font-size: 20px; }
	.itv-credentials .hp-stat__label { font-size: 17px; }
	/* Same call for the Magic Hug page - section 11. Its credential
	 * list is --type-small for the reason in 10.10 and reads as phone
	 * type inside a panel on a tablet, exactly like the two above. */
	.itv-creds { font-size: 20px; }
	/* Same call for the Reviews page - section 16. Its review card is a
	 * new component and was not in this list: the quote is --type-small
	 * and the attribution --type-detail, 19 and 17 at every width above
	 * 599, and the cards go TWO-UP through this whole band, so each one
	 * is between 264 and 460 wide with a review inside it. That is the
	 * exact situation the four rules above were written for. */
	.rv-card__body { font-size: 20px; }
	.rv-card__cite { font-size: 18px; }
	/* Same call for the Articles page - section 17. Its category-card
	 * description is --type-small and its article tiles carry an excerpt
	 * at --type-small over a date at --type-xs, which is 19 and 15 at
	 * every width above 599. Both card types go TWO-UP through this whole
	 * band, so each is between 264 and 460 wide with running text in it -
	 * the exact situation the rules above were written for. The date is
	 * lifted for the same reason 12.1 lifts the credentials stat label:
	 * --type-xs is a caption on a desktop and illegible on a tablet held
	 * at arm's length. */
	.ar-cat__body           { font-size: 20px; }
	.article-card__excerpt  { font-size: 20px; }
	.article-card__meta     { font-size: 17px; }
}

/* ---- 12.2 single-row header, 768-991 ---- */
@media (min-width: 768px) and (max-width: 991px) {
	/* 260 x (104/380) is 71 tall, and at the standing 20px padding the
	 * bar would have grown to 111 - taller than the DESKTOP header,
	 * which is wrong on a smaller screen. 14 brings it to 99, within a
	 * pixel of desktop's 100: the bar looks unchanged, only the mark in
	 * it grew.
	 *
	 * Set on the rule rather than by moving --header-pad-y, because the
	 * token is read by the two-row header below 768 as well, and that
	 * one Gerek has already signed off. */
	.site-header__inner { padding-block: 14px; }

	/* THE HEADLINE GETS THE ROOM THE SECOND COLUMN GAVE UP. At <=991
	 * the hero stops being two columns and the copy takes the whole
	 * panel instead of half of it, but the headline stayed at the size
	 * it was set for in a half-width column. 41 -> 50. Larger than the
	 * <=1199 step above it, and that is correct rather than an
	 * inconsistency: the column it sets in is wider here than there. */
	:root { --type-h1: 50px; --type-h1-hero: 50px; }

}

/* ---- 12.3 stacked tablet, 600-767 ----
 *
 * The hero is stacked and narrower than at 768, so it does not get the
 * full 50 - but 34 is the phone size and this is not a phone. 42/40
 * sits between the two so the 767/768 line stops being a cliff. */
@media (min-width: 600px) and (max-width: 767px) {
	:root { --type-h1: 42px; --type-h1-hero: 40px; }

}

/* ======================================================================
 * 24  VIDEO TILES AND THE LIGHTBOX
 *
 * One component, two placements: the homepage hero inset (caption
 * hidden, sized by .hp-media--hero-video) and the four tiles under the
 * second block of reviews. See inc/video.php.
 * ==================================================================== */

/* ---- 24.1  the tile ----
 *
 * A <button>, not a div with a click handler, so the focus ring, the
 * Enter/Space handling and the "button" role are the platform's and not
 * something this file has to imitate. `display: block` and a reset of
 * the UA's padding/border are the whole cost of using the right element.
 */
.itv-video {
	display: block;
	position: relative;
	width: 100%;
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	border-radius: var(--radius);
	overflow: hidden;
	box-shadow: var(--shadow-card);
	cursor: pointer;
	/* The thumbnails are 1280x674 exports of the video's own frame, so
	 * the tile holds that shape and the grid rows stay level whatever
	 * the caption does. */
	aspect-ratio: 1280 / 674;
}
.itv-video__thumb {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* The play triangle. Centred over the frame, and it grows slightly on
 * hover and focus so the tile answers the pointer without moving the
 * layout - a transform, not a size change. */
.itv-video__play {
	position: absolute;
	top: 50%;
	left: 50%;
	translate: -50% -50%;
	display: grid;
	place-items: center;
	/* !! SIZED UP FROM 64/34 ON 2026-09-03. Gerek: the disc "feels a
	 * little comical". It was two faults compounding - the disc was
	 * small for a 406px frame, and the triangle inside it was smaller
	 * still. The old path drew across 7 of the viewBox's 24 units, so a
	 * 34px icon carried a ~10px triangle in a 64px circle: mostly white
	 * space with a speck in the middle. The path now spans 11 of 24 and
	 * the disc is 84, so the triangle reads as the thing and the circle
	 * as its backing. */
	width: 84px;
	height: 84px;
	border-radius: 50%;
	background: rgba(255, 255, 255, 0.94);
	box-shadow: 0 3px 16px rgba(0, 0, 0, 0.4);
	transition: transform 150ms ease, background 150ms ease;
}
.itv-video__play > svg {
	width: 44px;
	height: 44px;
	fill: var(--navy);
	/* Optical centring: a triangle's visual mass sits left of its
	 * bounding box, so a mathematically centred one looks off to the
	 * left inside a circle. */
	margin-left: 4px;
}
.itv-video:hover .itv-video__play,
.itv-video:focus-visible .itv-video__play {
	transform: scale(1.08);
	background: #fff;
}
/* The ring goes on the tile, inset so the overflow clip cannot eat it. */
.itv-video:focus-visible {
	outline: 3px solid var(--accent);
	outline-offset: 3px;
}

/* The caption sits on the same bottom-up scrim the homepage photo tiles
 * use, for the same reason - the text has to survive whatever frame the
 * thumbnail happens to be. */
.itv-video__cap {
	position: absolute;
	inset-inline: 0;
	bottom: 0;
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--space-12);
	padding: var(--space-32) var(--space-20) var(--space-16);
	background: var(--scrim-photo);
	text-align: left;
}
.itv-video__title {
	font-family: var(--font-heading);
	font-size: var(--type-h5);
	font-weight: var(--weight-semibold);
	color: var(--on-invert);
}
.itv-video__dur {
	flex: 0 0 auto;
	font-size: var(--type-detail);
	color: var(--on-invert-muted);
	font-variant-numeric: tabular-nums;
}

/* !! THE HERO TILE RESTATES ITS OWN FRAME, AND HAS TO.
 *
 * The homepage inset's geometry belongs to 9.x's `.hp-media--hero-video`
 * - `width: min(406px, 100%)` and `aspect-ratio: 406 / 228`, the design's
 * numbers. 24.1 above sets `width: 100%` and `aspect-ratio: 1280 / 674`
 * for the review tiles, scores the same (0,1,0), and sits ~10,000 lines
 * lower, so it wins the tie on order and the hero would silently take the
 * review tiles' shape.
 *
 * The tile carries BOTH classes so the frame's own breakpoint rule still
 * applies; this only puts back the two properties 24.1 took. Change the
 * frame in 9.x and these two numbers have to move with it - which is why
 * they are written as the same expressions rather than as 406 and 1.78. */
.itv-video--hero {
	width: min(406px, 100%);
	aspect-ratio: 406 / 228;
	/* !! AND THE MARGIN, WHICH IS THE ONE THAT SHOWED. 9.x gives the
	 * frame `margin-block: 24`; 24.1's `margin: 0` scores the same and
	 * sits lower, so the tile lost it and the two CTA buttons came to
	 * rest EXACTLY on the thumbnail's bottom edge - measured gap 0.
	 * Reported as the buttons hovering over the video, which is what a
	 * zero gap looks like. */
	margin-block: var(--space-24);
}
@media (min-width: 600px) and (max-width: 991px) {
	.itv-video--hero { width: 100%; }
}

/* ---- 24.2  the four-up row under the reviews ----
 *
 * Same four columns and same gutter as .rv-cards above it, so the two
 * rows read as one block rather than as a grid and then a different
 * grid. It stacks on the same two breakpoints .rv-cards does. */
.rv-videos {
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	gap: var(--space-24);
	margin: var(--space-32) 0 0;
	padding: 0;
	list-style: none;
}
.rv-videos > li { display: block; margin: 0; }
/* Gerek 2026-09-03: these want to lift off the white a little. 24.1's
 * --shadow-card is the flattest of the three and reads as nothing on a
 * white ground; --shadow-tile is the two-layer one the homepage tiles
 * already use for exactly this. Scoped to the review row - the hero
 * tile sits on a dark photograph where no shadow is visible either way. */
.rv-videos .itv-video { box-shadow: var(--shadow-tile); }

/* ---- 24.3  the lightbox ----
 *
 * A native <dialog>. showModal() supplies the focus trap, the Escape
 * key, the inert background and the ::backdrop; everything here is
 * appearance only. See the note in main.js.
 */
.itv-lb {
	width: min(1100px, 92vw);
	max-width: none;
	max-height: 92vh;
	padding: 0;
	border: 0;
	background: none;
	overflow: visible;
	/* !! `margin: auto` IS WHAT CENTRES A MODAL <dialog>, AND THIS THEME
	 * HAD ALREADY TAKEN IT AWAY. The UA sheet centres a dialog with
	 * `inset: 0` plus `margin: auto`; 01's reset sets `margin: 0` on a
	 * broad selector that reaches <dialog> too, so the box kept the
	 * inset and lost the auto and pinned itself to the top-left corner.
	 * Measured, not guessed: computed margin came back 0px with the
	 * dialog at x0 y0. */
	margin: auto;
}
.itv-lb::backdrop {
	background: rgba(10, 26, 42, 0.82);
}
.itv-lb__panel {
	position: relative;
	background: var(--navy-deep);
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-photo);
	padding: var(--space-48) var(--space-16) var(--space-16);
}
.itv-lb__video {
	display: block;
	width: 100%;
	max-height: calc(92vh - 120px);
	border-radius: var(--radius);
	background: #000;
}
/* The title is the dialog's accessible name via aria-labelledby, so it
 * is real text and not a decorative label - it sits in the header strip
 * the top padding above makes room for. */
.itv-lb__title {
	position: absolute;
	top: var(--space-12);
	left: var(--space-20);
	right: 56px;
	margin: 0;
	font-family: var(--font-body);
	font-size: var(--type-detail);
	font-weight: var(--weight-semibold);
	line-height: 1.6;
	letter-spacing: var(--tracking-eyebrow);
	text-transform: uppercase;
	color: var(--on-invert-muted);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}
.itv-lb__close {
	position: absolute;
	top: var(--space-8);
	right: var(--space-8);
	display: grid;
	place-items: center;
	width: 40px;
	height: 40px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: rgba(255, 255, 255, 0.12);
	color: var(--on-invert);
	cursor: pointer;
	transition: background 150ms ease;
}
.itv-lb__close:hover { background: rgba(255, 255, 255, 0.24); }
.itv-lb__close:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.itv-lb__close > svg {
	width: 20px; height: 20px;
	stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round;
}

/* showModal() makes the page inert but not unscrollable - a phone will
 * still scroll the document behind the dialog under a touch drag. */
.itv-lb-open, .itv-lb-open body { overflow: hidden; }

@media (max-width: 991px) {
	.rv-videos { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 599px) {
	.rv-videos { grid-template-columns: 1fr; }
	/* Scaled with the desktop disc, keeping the same ratio. Still a
	 * comfortable target and not overbearing on a full-width tile. */
	.itv-video__play { width: 68px; height: 68px; }
	.itv-video__play > svg { width: 36px; height: 36px; margin-left: 3px; }
	.itv-lb { width: 96vw; }
	.itv-lb__panel { padding: var(--space-40) var(--space-8) var(--space-8); }
	.itv-lb__video { max-height: calc(92vh - 90px); }
}
