/*
	Theme Name:  Lucas Rey
	Theme URI:   https://lucasrey.com
	Description: The site's own theme. Generated by app/local/build_theme.py
	             from lucas-rey plus app/theme-next. Do not edit
	             anything in this directory: it is output, and the next build
	             overwrites it.
	Author:      Lucas Rey
	Version:     1.12.6
	Requires PHP: 8.1
	Text Domain: lucas-rey

	Everything below this header is the site's real stylesheet, carried over
	from lucas-rey unchanged. It has to live at the theme root:
	includes/lr-assets.php serves this file whenever assets/css/style.min.css
	is stale, so a root style.css holding only a header would leave the site
	unstyled. Do not put a comment terminator inside this header.
*/

/* Inter is loaded via wp_enqueue_style('lr-google-fonts') in
   includes/lr-assets.php, a render-blocking @import here would delay first
   paint, so it lives in the <head> as a parallel <link> instead. */


/* Property ordering convention (within each rule):
Layout/Positioning -> Box Model -> Typography -> Visuals -> Effects/Misc. */


/* This declares five layers and fixes their priority order. */
@layer reset, tokens, primitives, components, adapter;
/* ==========================================================================
   LAYER: RESET
   --------------------------------------------------------------------------
   Purpose:
   Establish a consistent baseline across all browsers by removing default
   browser styling and normalising common HTML elements.

   Contains:
   • box-sizing
   • default margins and padding
   • typography resets
   • image and media defaults
   • form element normalisation
   • accessibility defaults

   Never place:
   • colours
   • spacing scales
   • layouts
   • components
   • WordPress overrides
   ========================================================================== */
@layer reset {
	
	/* =========================================================
		1. SAFETY, GLOBAL RESETS & ACCESSIBILITY
	========================================================= */
	html {
		overflow-x: hidden;
		scroll-behavior: smooth; 
		scroll-padding-top: 120px;
		
		/* Reserve scrollbar gutter so layout does not shift horizontally
		   between short pages (no scrollbar) and long pages (scrollbar
		   visible). No-op on platforms with overlay scrollbars (macOS,
		   iOS, modern Android). */
		scrollbar-gutter: stable;
		
		/* Firefox custom scrollbar support */
		scrollbar-width: thin;
		scrollbar-color: rgba(var(--lr-color-text-muted-rgb), 0.4) var(--lr-color-bg-section); 
		
		/* Prevent iOS Safari from auto-inflating text in landscape
		   orientation, which silently breaks our typography rhythm.
		   The unprefixed property is for spec compliance, the
		   -webkit- prefix is what iOS actually reads today. */
		-webkit-text-size-adjust: 100%;
		text-size-adjust: 100%;
	}
	
	body {
		/* Cream, deliberately NOT --lr-color-bg-light: that token is the soft
		   panel tint used by math blocks, panels and table headers, which
		   still need to sit visibly *on* the page. The cream body is one half
		   of the section alternation (brief §3A, restacked onto the two-band
		   cream/blue scheme); --lr-color-band-blue is the other. Pure white
		   is no longer a ground at all, it now belongs to cards, so a white
		   card reads as raised on either band without needing a shadow. */
		background-color: var(--lr-color-band-cream);

		/* Both families are now loaded as variable fonts carrying an optical
		   size axis (see includes/lr-assets.php), so glyph contrast and
		   spacing adapt to the rendered size. This is the default, stated
		   explicitly so it is not switched off by accident. */
		font-optical-sizing: auto;

		/* Real italic and the full weight range are loaded, so the browser
		   never needs to fake either. Faux styles, a slanted upright, or an
		   emboldened outline, are the clearest giveaway of a page that has
		   not had its type set properly. Guarded for MathJax below. */
		font-synthesis: none;
		margin: 0;
		padding: 0;
		width: 100%;
		-webkit-font-smoothing: antialiased;
		-moz-osx-font-smoothing: grayscale;
		text-rendering: optimizeLegibility; 
		accent-color: var(--lr-color-accent); 
		text-wrap: pretty;
		overflow-wrap: break-word;
		hyphens: manual;
		
		/* Suppress default mobile tap highlight */
		-webkit-tap-highlight-color: transparent; 
	}
	
	body, button, input, select, textarea {
		font-family: var(--lr-font-family) !important;
	}

	/* Headings render in the serif display face. Class selectors are included
	   alongside element selectors because .lr-display/.lr-title/.lr-heading/
	   .lr-subheading are reusable primitives sometimes applied to non-heading
	   elements.

	   Deliberately NOT !important. Astra never targets headings with
	   font-family (only `body`), so headings reach here by inheritance and a
	   plain layered declaration that matches the element directly already
	   wins. Marking it important would make it unbeatable by later layers,	   cascade layers invert for important declarations, so an important
	   reset-layer rule outranks every component-layer rule, which would stop
	   .lr-eyebrow and other components opting into the monospace face. */
	h1, h2, h3, h4, h5, h6, .wp-block-heading, .entry-title, .site-title,
	.lr-display, .lr-title, .lr-heading, .lr-subheading {
		font-family: var(--lr-font-heading);
	}
	
	.site {
		overflow-x: hidden; /* Safely scopes horizontal bounds */
	}

	/* MathJax ships its own font set and leans on synthesis for some
	   bold/italic math variants. font-synthesis is inherited, so the body
	   rule above would reach into every equation on the ESAT papers and
	   could silently flatten emphasis in the maths. Restore the default
	   inside rendered math only. */
	mjx-container,
	mjx-container * {
		font-synthesis: auto;
	}
	
	*, *::before, *::after {
		box-sizing: border-box;
	}
	
	img, .wp-block-image img, figure img, .entry-content img {
		display: block;
		max-width: 100% !important;
		height: auto !important;
	}
	
	h1, h2, h3, h4, h5, h6 {
		text-wrap: balance; 
		word-break: keep-all;
		hyphens: none;
		overflow-wrap: normal; /* Keep words whole by default */
	}
	
	/* Fallback: Only allow harsh word breaks on tiny mobile screens */
	@media (max-width: 480px) {
		h1, h2, h3, h4, h5, h6 {
			overflow-wrap: anywhere;
		}
	}
	
	/* Custom Text Selection */
	::selection {
		background-color: var(--lr-color-accent);
		color: #ffffff;
	}
	
	/* Keyboard Accessibility Focus Rings

	   --lr-shadow-focus-ring is an OUTER box-shadow, which clip-path removes
	   along with everything else an element paints outside its polygon. The
	   chamfered CTAs therefore have to supply their own inset ring (see the
	   button rules in the adapter layer), and because layer precedence
	   inverts for important declarations, this reset-layer rule would
	   otherwise outrank those adapter rules and reinstate the very outer ring
	   that gets clipped, leaving the primary call to action with no visible
	   keyboard focus at all. The chamfered classes are excluded here so the
	   adapter rules can own focus for them. Anything given a clip-path in
	   future must be added to these exclusions too. */
	a:where(:not(.wp-block-button__link):not(.lr-btn-primary):not(.lr-btn-outline)):focus-visible,
	button:where(:not(.wp-block-button__link):not(.lr-btn-primary):not(.lr-btn-outline)):focus-visible,
	.wp-element-button:where(:not(.wp-block-button__link):not(.lr-btn-primary):not(.lr-btn-outline)):focus-visible,
	input:focus-visible,
	textarea:focus-visible,
	select:focus-visible,
	a.lr-tag:focus-visible,
	a.wp-block-tag:focus-visible,
	a.pill:focus-visible,
	a.lr-ms:focus-visible,
	a.lr-doi:focus-visible,
	.lr-tooltip:focus-visible {
		outline: none !important;
		box-shadow: var(--lr-shadow-focus-ring) !important;
		border-radius: var(--lr-radius-small);
		transition: box-shadow var(--lr-duration-moderate) var(--lr-easing-mechanical) !important;
	}
	
	/* Forced Colors Mode (Windows high-contrast). System swaps colours
	   wholesale and box-shadows do not render, so we re-introduce a real
	   outline for keyboard focus. CanvasText resolves to the user's
	   preferred foreground colour. */
	@media (forced-colors: active) {
		a:focus-visible,
		button:focus-visible,
		.wp-element-button:focus-visible,
		input:focus-visible,
		textarea:focus-visible,
		select:focus-visible,
		a.lr-tag:focus-visible,
		a.wp-block-tag:focus-visible,
		a.pill:focus-visible,
		a.lr-ms:focus-visible,
		a.lr-doi:focus-visible,
		.lr-tooltip:focus-visible {
			outline: 2px solid CanvasText !important;
			outline-offset: 2px;
			box-shadow: none !important;
		}
	}
	
	/* Custom Mechanical Scrollbar (Webkit)
	   UPGRADE v1.4: Enhanced scrollbar with hover expansion,	   thumb widens from 8px to 12px on hover, accent intensifies. */
	::-webkit-scrollbar {
		width: 10px;
		background-color: var(--lr-color-bg-section);
		transition: width var(--lr-duration-base) var(--lr-easing-standard);
	}
	
	::-webkit-scrollbar-thumb {
		background-color: rgba(var(--lr-color-text-muted-rgb), 0.25);
		border-radius: 10px;
		border: 2px solid var(--lr-color-bg-section);
		transition: background-color var(--lr-duration-base) var(--lr-easing-standard);
	}
	
	::-webkit-scrollbar-thumb:hover {
		background-color: var(--lr-color-accent);
		border-width: 1px;
	}
}

/* ==========================================================================
   LAYER: TOKENS
   --------------------------------------------------------------------------
   Purpose:
   Define the design system. This layer contains the project's reusable design
   decisions as CSS custom properties (design tokens).

   Contains:
   • colour palette
   • typography scale
   • spacing scale
   • border radii
   • shadows
   • z-index values
   • animation timings
   • breakpoints (if stored as variables)

   Never place:
   • component styles
   • layouts
   • selectors with visual styling
   ========================================================================== */
   
@layer tokens {
	
	/* =========================================================
		2. CSS TOKENS
	========================================================= */
	:root {
	/* TYPOGRAPHY */
	/* "Inter Fallback" / "Source Serif 4 Fallback" are local system faces
	   (Arial, Georgia) wrapped in size-adjust/ascent/descent overrides so
	   their lines measure the same as the web font; declared alongside the
	   self-hosted @font-face rules in includes/lr-fonts.php. They sit
	   immediately after the web font so the pre-swap render lands on the
	   matched face and the swap itself does not move layout. */
	--lr-font-family: 'Inter', 'Inter Fallback', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
	/* Display/heading face, a serif pairing reads editorial-academic rather
	   than generic-SaaS; body copy and UI controls stay on Inter for
	   legibility. Loaded alongside Inter in includes/lr-assets.php. */
	--lr-font-heading: 'Source Serif 4', 'Source Serif 4 Fallback', Georgia, 'Times New Roman', serif;
	/* Technical/blueprint face. Per the platform design brief
	   (reference_websites/combined_analysis_report.md §3C), structural
	   metadata, section numbers, "[01]"-style markers, category labels,	   is set in monospace to evoke engineering schematics. IBM Plex Mono
	   rather than Courier New: same blueprint read, contemporary drawing. */
	--lr-font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New', monospace;

	/* TYPE SCALE (added 1.11.2)

	   Before this, the stylesheet carried 135 font-size declarations across
	   41 distinct values: 25 fixed and 16 fluid. That is not a scale, it is
	   accumulation, and it is why sizes one step apart in the design were
	   sometimes 0.4px apart in the CSS.

	   Ten steps, in rem so the browser's own font-size setting is respected.
	   Ratio is roughly 1.2 through the reading sizes, opening to 1.25 at the
	   display end where a wider interval reads as hierarchy rather than as a
	   mistake. The four display steps are fluid: they interpolate on viewport
	   width so a heading does not have to be re-declared per breakpoint.

	   Every existing fixed value maps to a step within 2px, and all but two
	   within 1px, so adopting these is not a redesign of the page. The
	   mapping table is in docs/CSS-CASCADE-FINDINGS.md.

	   Note --lr-text-sm already had one reference, at the .lr-meta rule,
	   written as var(--lr-text-sm, 0.9rem) against a token that did not
	   exist. Defining it here retires that fallback and moves that one site
	   by -0.4px, which is the intended consolidation rather than a
	   regression.

	   ---------------------------------------------------------------
	   READ THIS BEFORE CONVERTING ANY px FONT-SIZE TO ONE OF THESE.

	   1rem is NOT 16px on this site. Astra's Customizer sets a responsive
	   root font-size and emits html{font-size:91.2%} below its mobile
	   breakpoint, so the root computes to 14.592px at a 390px viewport,
	   measured. Every rem value therefore shrinks by about 8.8 percent on
	   mobile, which is Astra's deliberate global mobile type reduction.

	   The consequence is that a px font-size in this file is not
	   automatically a mistake. Several are there precisely to OPT OUT of
	   that shrink, and converting them to rem is a regression rather than
	   an accessibility improvement:

	     - the (pointer: coarse) and (max-width: 782px) form-control rules
	       need an absolute 16px, because the iOS zoom-on-focus threshold is
	       absolute; 1rem falls under it on mobile and iOS then zooms
	     - the (max-width: 480px) .lr-btn rule sets 16px specifically to
	       hold the CTA at full size against the 91.2% root

	   That was learned the expensive way in 1.11.2: eleven px sizes were
	   converted to these tokens, and the primary CTA buttons silently went
	   from 16px to 14.592px at 390px. All eleven were reverted. These
	   tokens are therefore DECLARED but not yet adopted: adopting them is a
	   deliberate typographic decision about whether a given piece of text
	   should ride Astra's mobile shrink, and each one needs checking at a
	   real mobile viewport, not assumed from the desktop rendering. */
	--lr-text-2xs:  0.6875rem;                                        /* 11px  mono micro labels */
	--lr-text-xs:   0.75rem;                                          /* 12px  captions, metadata */
	--lr-text-sm:   0.875rem;                                         /* 14px  secondary copy, nav */
	--lr-text-base: 1rem;                                             /* 16px  body copy */
	--lr-text-md:   1.125rem;                                         /* 18px  lead paragraphs */
	--lr-text-lg:   clamp(1.25rem, 1.18rem + 0.35vw, 1.5rem);         /* 20 to 24px  h4, card titles */
	--lr-text-xl:   clamp(1.5rem, 1.35rem + 0.75vw, 1.875rem);        /* 24 to 30px  h3 */
	--lr-text-2xl:  clamp(1.875rem, 1.6rem + 1.35vw, 2.5rem);         /* 30 to 40px  h2 */
	--lr-text-3xl:  clamp(2.25rem, 1.8rem + 2.25vw, 3.25rem);         /* 36 to 52px  h1 */
	--lr-text-4xl:  clamp(2.75rem, 2rem + 3.75vw, 4.5rem);            /* 44 to 72px  hero display */

	/* LINE HEIGHT

	   Inter and Source Serif 4 do not want the same line-height at the same
	   size: the serif has the larger x-height here and carries more colour,
	   so the display steps take the tighter figure. 1.6 for body is what the
	   design brief asks for in section 4.

	   Graduated by size, which is the point. The stylesheet previously ran
	   one --lr-line-height-heading of 1.15 across h1, h2 and h3 alike, so a
	   44px h1 and a 23px h3 were set to the same ratio. A large display line
	   wants a tighter ratio because its leading is already generous in
	   absolute terms; a small heading that wraps to two lines needs a looser
	   one or the lines collide. --lr-line-height-heading is kept at 1.15 for
	   anything still referencing it. */
	--lr-leading-display: 1.1;    /* hero and h1 */
	--lr-leading-h2:      1.2;
	--lr-leading-h3:      1.3;
	--lr-leading-ui:      1.5;    /* nav, buttons, list items, captions */
	--lr-leading-body:    1.6;    /* prose, the figure the design brief asks for */

	/* TRACKING

	   A serif display face set large needs negative tracking or it reads
	   loose; the brief asks for -0.01em in section 5A and this extends that
	   across the display steps in proportion to size. Mono and all-caps
	   labels want the opposite. */
	--lr-tracking-display: -0.02em;   /* 3xl, 4xl */
	--lr-tracking-2xl:     -0.015em;
	--lr-tracking-xl:      -0.01em;
	--lr-tracking-normal:  0;
	--lr-tracking-label:   0.08em;    /* mono blueprint labels, all-caps */

	/* COLORS */
	--lr-color-text-main: #1f374e;
	/* Alias for legacy `var(--lr-color-text)` references; keep in sync with --lr-color-text-main. */
	--lr-color-text: #1f374e;
	--lr-color-text-heading: #1f374e;
	/* Re-derived for the two-band scheme. The blue band is the darker of the
	   two grounds, so it sets the floor: an earlier candidate (#5c7186) held
	   4.96:1 on cream but only 4.35:1 on blue, an AA failure for every muted
	   lead and caption sitting on a band. This value holds 5.57:1 on cream,
	   4.89:1 on the blue band and 5.67:1 on white. */
	--lr-color-text-muted: #55697d;
	--lr-color-text-subtle: #3e5871;
	--lr-color-bg-light: #eaf6fc;
	/* Hardcoded, not routed through --ast-global-color-4/-5: same reasoning
	   as --lr-color-accent below, Astra's Customizer has concrete values
	   stored for these, so the var() fallbacks were silently never applying.

	   The alternation described by brief §3A (pure-white body against one
	   soft cool grey band) has been restacked onto a warm two-band scheme:
	   the body is now cream and the band is pale blue, with cards sitting
	   pure white on top of both. The deep section padding is still what
	   makes the alternation read. */
	--lr-color-bg-card: #ffffff;
	--lr-color-bg-section: var(--lr-color-band-blue);

	/* SECTION BANDS: the two full-bleed grounds the page alternates between.
	   Cream is the body default, the band is the alternate. Cards are white
	   on both, which is what keeps a white card legible on either.

	   1.11.5: the band moved from a pale blue #d5f3ff to a warm oat #f0eee6.

	   Why. The design brief specifies a NEUTRAL for this role, "alternating
	   pure white and cool-grey" in section 3A, and the saturated blue was
	   the one element of the palette that read as generic-SaaS rather than
	   collegiate. Oat is also the ground the owner was pointing at when
	   citing claude.ai, where the warmth of the paper does most of the work
	   that people attribute to the typeface.

	   The swap spends nothing. Measured against every text colour that
	   lands on a band, old value first:

	     body    #1f374e   10.55 -> 10.54
	     muted   #55697d    4.89 ->  4.88
	     subtle  #3e5871    6.37 ->  6.37
	     link    #1a56c4    5.70 ->  5.70
	     accent  #002147    9.65 ->  9.64
	     code    #c9246d    4.57 ->  4.57

	   Worst case is 4.57 either way, so no margin is lost, and separation
	   from the cream page is 1.14 in both, so the alternation reads exactly
	   as strongly as before. Candidates were compared before choosing: the
	   brief's own #f9fafb measures 5.08 but separates at only 1.03, which is
	   too little to read as a band at all.

	   Reverting is one value. The old name is kept as an alias below because
	   a rule or a page may still reference it, and an undefined custom
	   property invalidates the whole declaration it appears in. */
	--lr-color-band-cream: #fffdf6;
	--lr-color-band: #f0eee6;
	/* Historical name, retained so nothing referencing it breaks. The blue it
	   was named for is gone; the ink frame keeps its own pale blue in
	   --lr-color-on-ink-accent, which is a different job on a dark ground. */
	--lr-color-band-blue: var(--lr-color-band);
	/* Alias for a name used by published page content (the <summary> tint on
	   38 ESAT solution pages) that was never declared here. An undefined
	   var() invalidates the whole declaration, so those summaries were
	   rendering with no tint at all. */
	--lr-color-background-subtle: var(--lr-color-bg-section);
	/* Hardcoded rather than routed through --ast-global-color-0: Astra's
	   Customizer already has a concrete value stored for that variable, so a
	   var() fallback here would never actually apply. */
	/* Deep indigo. AA on every ground it lands on: 11.00:1 on cream,
	   9.65:1 on the blue band, 11.20:1 on white, and it carries white text
	   at 11.20:1 when used as a solid button fill. */
	--lr-color-accent: #002147;
	/* Accent companions: hover state, soft wash, and an rgb triplet for rgba()/color-mix() use. */
	--lr-color-accent-hover: #123c66;
	/* Re-derived from Oxford blue in 1.12.1. This was #efedf8, mixed from the
	   old violet, and it survived the retint while feeding every accent hover
	   wash on the site, so those hovers were still tinted purple against a
	   navy palette. Measured against every text colour that lands on it,
	   violet value first: body 10.58 to 10.90, muted 4.90 to 5.05, link 5.72
	   to 5.89, accent 13.86 to 14.28, inline code 4.58 to 4.72. Worst case
	   rises from 4.58 to 4.72, so it gains a little headroom as well. */
	--lr-color-accent-wash: #eef2f8;
	--lr-color-accent-rgb: 0, 33, 71;
	--lr-color-border-subtle: #dce6ec;

	/* INK FRAME, the dark chrome that carries the Technical-HUD half of the
	   brief (§2: Brittany Chiang's dark frame, Mauricio Juba's console
	   plates). Content stays editorial-light; the ink lives in the frame
	   (header, footer) and in interior-page masthead plates.

	   1.12.0: the frame is Oxford blue, replacing the indigo #362e83 that
	   preceded it. The indigo was the site's dominant signature, carrying the
	   full-width header, every hero plate and every solid button against a
	   warm cream page, and it read as generic SaaS rather than the Collegiate
	   Editorial the brief asks for. #002147 is Oxford's own blue, on a site
	   whose eyebrow reads OXFORD RESEARCH AND COMPUTATIONAL ENGINEERING, so it
	   is the colour the page is already about.

	   It costs nothing and gains headroom. Every on-ink value re-measured,
	   indigo first: on-ink 11.00 to 15.77, on-ink-muted 6.60 to 9.46,
	   on-ink-accent 9.65 to 13.83. All AA, all better than before. */
	--lr-color-ink: #002147;
	/* Ink rgb triplet, paired with --lr-color-ink. Exists so translucent ink
	   surfaces (the frosted sticky header) can be built with
	   rgba(var(--lr-color-ink-rgb), N) instead of a hardcoded literal, the
	   frosted header shipped rgba(17,29,54,…) from the navy era and visibly
	   changed hue the moment the page scrolled. Keep in sync. */
	--lr-color-ink-rgb: 0, 33, 71;
	--lr-color-on-ink: #fffdf6;
	--lr-color-on-ink-muted: #bccadd;
	--lr-color-on-ink-accent: #d5f3ff;
	--lr-color-on-ink-accent-rgb: 213, 243, 255;
	/* Muted-grey rgb triplet, paired with --lr-color-text-muted (#55697d). Used by scrollbar
	   tints and other neutral surfaces to avoid hardcoding the colour. Keep in sync. */
	--lr-color-text-muted-rgb: 85, 105, 125;
	
	/* Fallback / compatibility tokens for ESAT pages */
	--lr-color-primary: var(--lr-color-accent);
	--lr-color-primary-subtle: rgba(var(--lr-color-accent-rgb), 0.1);
	--lr-color-text-dark: #1f374e;
	--lr-color-background-white: #ffffff;
	
	/* Semantic link aliases. Components should prefer these over --lr-color-accent
	   when the role is specifically a hyperlink colour, so future re-themes can
	   shift link tone without touching every component.

	   The hyperlink tone is no longer the accent. As accent, links rendered at
	   1.09:1 against #1f374e body copy under the old indigo, and at 1.31:1
	   under Oxford blue: still the same colour to the eye either way, with the
	   underline carrying the entire link affordance. The hyperlink
	   role now sits on a clear azure that is separated from the body navy by
	   HUE rather than by lightness: no link colour can clear both 3:1 against
	   this body text and 4.5:1 against these grounds (the §22b arithmetic), so
	   hue separation + the policy underline is the legible compromise.
	   Measured: 6.62:1 on white, 6.50:1 on cream, 5.70:1 on the band,
	   6.02:1 on bg-light, 5.72:1 on the accent wash, AA everywhere with
	   headroom. Hover darkens to 8.69:1 / 8.54:1 / 7.49:1 / 7.90:1 / 7.51:1
	   on the same five grounds. The link colour did NOT move with the palette
	   change: buttons, tags and the ink frame took Oxford blue, hyperlinks
	   keep this azure, which is what separates them by hue. */
	--lr-color-link:       #1a56c4;
	--lr-color-link-hover: #1546a0;
	
	/* Inline-code accent. Pinned here so high-contrast mode can override it
	   cleanly. Re-checked after --lr-color-bg-light was retinted: it holds
	   4.82:1 against the new chip background inline code actually renders
	   on, 5.21:1 on cream and 5.31:1 on white. */
	--lr-color-code: #c9246d;
	
	/* Centralised focus-ring colours. The composed --lr-shadow-focus-ring token
	   below references them so all focus rings stay in lock-step. */
	--lr-color-focus-ring: rgba(var(--lr-color-accent-rgb), 0.4);
	--lr-color-focus-glow: rgba(var(--lr-color-accent-rgb), 0.12);
	
	/* Prose container width for body copy on long technical pages. Informational
	   token: components opt-in by setting `max-width: var(--lr-prose-max-width)`. */
	--lr-prose-max-width: 70ch;
	--lr-content-max: 48rem;
	--lr-max-width-centered: 900px;
	--lr-color-surface-muted: #fffdf6;
	--lr-padding-inline-shell: clamp(16px, 3vw, 20px);
	/* Alias. Page content authored in the editor references
	   var(--lr-padding-inline), which has never existed, the real token is
	   --lr-padding-inline-shell above. A reference to an undefined custom
	   property is invalid at computed-value time, so the whole declaration is
	   dropped and those containers silently fall back to zero horizontal
	   padding (verified live: a homepage section computing 0px/0px where the
	   author asked for the shell gutter). Defining the alias repairs the
	   authored intent without editing page content in the database. */
	--lr-padding-inline: var(--lr-padding-inline-shell);
	
	/* --- Pedagogical Callout Tokens --- */
	--lr-callout-edge-color: var(--lr-color-accent);
	--lr-callout-bg-color: #ffffff;
	--lr-callout-shadow-idle: rgba(15, 23, 42, 0.04);
	
	/* SPACING & SIZING (8px Scale) */
	--lr-space-1: 4px;
	--lr-space-2: 8px;
	--lr-space-3: 12px;
	--lr-space-4: 16px;
	--lr-space-5: 24px;
	--lr-space-6: 32px;
	--lr-space-7: 48px;
	--lr-space-8: 64px;
	--lr-space-9: 80px;
	--lr-space-10: 96px;

	--lr-max-width-card: 1140px;
	/* Design brief §3A names spacing as "the single most significant
	   contributor" to the premium editorial feel: 80 to 120px block padding on
	   desktop, easing to ~48px on mobile. */
	--lr-padding-section: clamp(48px, 9vw, 128px);
	--lr-padding-card: clamp(20px, 2.5vw, 32px); /* closer to 24px/32px scale */
	
	/* CHAMFER (industrial corner cut)
	   Brief §3C / §5B: angled corners instead of pill radii to signal an
	   aerospace/console lineage.

	   NO LONGER APPLIED BY THIS STYLESHEET. The button rules that used it
	   have moved to --lr-radius-small to match the rounded control shape of
	   the reference design. The token is kept declared because page content
	   authored in the editor may reference it, and an undefined custom
	   property invalidates the whole declaration it appears in. If you do
	   reapply it: clip-path clips ALL of an element's painting, including
	   outer box-shadow and focus outlines, so anything chamfered must carry
	   its focus ring as an INSET shadow or it becomes a keyboard trap. */

	/* BORDER RADIUS
	   The reference palette pairs a flat, shadow-less surface with a very
	   generous corner: cards at 40px, media at 30px, controls at 13px, and
	   anything tag-shaped run all the way to a stadium. The large radius is
	   only legible because the card border is a full-strength 2px outline
	   (see --lr-border-card), soften the border and the corners just read
	   as blur. The two are a pair; change them together. */
	--lr-radius-large: 10px;
	--lr-radius-medium: 6px;
	--lr-radius-small: 4px;
	--lr-radius-pill: 999px;
	--lr-radius-tag: 999px;
	--lr-radius-image: 8px;
	
	/* ELEVATION, deliberately flat.
	   The previous scheme stacked four-to-six near-invisible micro-shadows
	   per surface (brief §3, Greenhill reference) to imply depth. The
	   reference design separates surfaces the opposite way: no shadow at
	   all, and a hard 2px outline instead. Mixing the two reads as a card
	   that is both floating and stamped, so the elevation tokens are
	   resolved to `none` rather than deleted, every `box-shadow:
	   var(--lr-shadow-*)` call site across the file stays valid and simply
	   paints nothing.

	   Two are deliberately NOT flattened: --lr-shadow-focus-ring below,
	   which is an accessibility affordance rather than decoration, and
	   --lr-shadow-editorial, which large hero photography still needs to sit
	   against the cream ground. */
	--lr-shadow-rest: none;
	--lr-shadow-active: none;
	--lr-shadow-hover-card: none;
	--lr-shadow-hover-button: none;
	--lr-shadow-hover-image: none;
	--lr-shadow-image: none;
	/* Layered depth shadow for large editorial/hero photography, a tight
	   contact shadow plus a wide soft ambient one, tinted toward the ink
	   navy rather than neutral black so it reads warmer against the site's
	   cool light backgrounds. */
	--lr-shadow-editorial: 0 2px 6px rgba(20, 33, 61, 0.06), 0 20px 40px -12px rgba(20, 33, 61, 0.22);
	/* Composed focus ring: white spacer + accent ring + soft outer glow. Used by all
	   keyboard-focus rules so the visual is exactly identical everywhere. Forced
	   Colors Mode (Windows high-contrast) overrides this in §1 with a real outline. */
	--lr-shadow-focus-ring: 0 0 0 2px #ffffff, 0 0 0 4px var(--lr-color-focus-ring), 0 0 12px var(--lr-color-focus-glow);
	
	/* CARD-SURFACE REFINEMENT
	   A slightly cooler border than the neutral grey so large surfaces
	   harmonise with the blue accent system; a shared accent hover-border
	   so every interactive surface reacts the same way; and a soft lift
	   shadow that lets non-card panels share the card family's depth. */
	--lr-color-border-card: #1f374e;
	--lr-color-border-hover: var(--lr-color-accent);
	/* Full card-outline shorthand. Card rules call `border:
	   var(--lr-border-card)` rather than `Npx solid var(--lr-color-border-card)`
	   so the WIDTH is centralised too, the outline being 2px rather than a
	   hairline is the whole reason the 40px corner reads as a shape instead
	   of a smudge, and that was previously hardcoded at eleven call sites. */
	--lr-border-card: 1px solid var(--lr-color-border-card);
	/* Secondary-button fill. A token rather than a literal because the ink
	   contexts remap --lr-color-accent to the pale on-ink blue; a hardcoded
	   white fill there put pale-blue text on white at 1.16:1, invisible.
	   The ink block remaps this to transparent so the outline button keeps
	   its dark-plate treatment. */
	--lr-color-btn-outline-bg: #ffffff;
	--lr-color-btn-outline-hover-bg: var(--lr-color-accent-wash);
	/* Ink-strength hairline for dividers and table rules, where a full 2px
	   outline would be far too loud. */
	/* Darkened from #c63838 (3.76:1 on white, an AA failure). This colour is
	   used as label text on the booking portal's destructive actions, e.g.
	   "Cancel lesson", so it has to be legible, not just indicative. Now
	   5.22:1 on white and 4.77:1 on the danger wash. Border/edge uses are
	   decorative and unaffected. */
	--lr-color-danger: #c63838;
	--lr-color-danger-wash: #fef2f2;
	--lr-shadow-hover-panel: none;
	
	/* FLUID GAPS */
	--lr-gap-section: clamp(56px, 7vw, 104px);
	--lr-gap-large: clamp(24px, 4vw, 40px);
	--lr-gap-medium: clamp(16px, 2.5vw, 24px);
	--lr-gap-small: clamp(10px, 1.5vw, 14px);
	--lr-gap-tight: 8px;
	--lr-gap-button: 12px;
	--lr-gap-card: clamp(24px, 2.5vw, 32px); /* 8px scale compatible */
	
	/* FLUID TYPOGRAPHY
	   The hero value matches the homepage hero's inline clamp exactly. That
	   inline style was, until now, the only place the intended display scale
	   actually rendered: Astra sets h1/h2/h3 sizes in unlayered CSS, which
	   outranks every layered rule here, so the rest of the site fell back to
	   Astra's 36/30/24px defaults. The heading rules in the primitives layer
	   are marked !important to reclaim the scale site-wide. */
	/* Top end brought down. The hero was 72px against TutorCruncher's 40px
	   and claude.ai's 36px, neither reference shouts, and a 72px display
	   over a 38px h2 also left a gap in the middle of the scale that
	   nothing occupied. The rendered scale is now 56/44/34/23/20/16, which
	   keeps the serif's presence without the shout and gives
	   --lr-font-size-lead a real place in the run. */
	--lr-font-size-hero: clamp(40px, 5vw, 56px);
	--lr-font-size-h1: clamp(34px, 4vw, 44px);
	--lr-font-size-h2: clamp(26px, 3vw, 34px);
	--lr-font-size-h3: clamp(20px, 2.2vw, 23px);
	/* Sub-heading step between h3 and body (used by .lr-timeline-title). Referenced
	   before it was defined, which left those titles at the inherited size. */
	--lr-font-size-h4: clamp(18px, 1.8vw, 20px);
	--lr-font-size-body: 16px;
	--lr-font-size-small: 14px;
	--lr-font-size-tag: 12px;
	--lr-font-size-caption: 12px;
	--lr-font-size-lead: clamp(18px, 2.2vw, 20px);
	--lr-font-size-figcaption: 14px;
	
	--lr-line-height-heading: 1.15;
	/* 1.6 was looser than either reference (TutorCruncher 1.40 to 1.50,
	   claude.ai 1.50) at a comparable measure. */
	--lr-line-height-body: 1.6;
	--lr-line-height-small: 1.5;
	
	--lr-font-size-label: 18px;
	/* em-based so the optical correction scales with the heading rather than
	   over-tightening small headings and under-tightening the hero (brief §4:
	   headings carry ~-0.02em). */
	--lr-letter-spacing-tight: -0.02em;
	/* Wide tracking is what makes short monospace labels read as blueprint
	   annotation rather than code (brief §3C / §5C). */
	--lr-letter-spacing-mono: 0.14em;
	
	--lr-margin-bottom-heading: 16px;
	--lr-margin-bottom-para: 16px;
	--lr-margin-bottom-tag: 24px;
	
	/* MOTION */
	/* Durations. Shortened across the board against the reference designs:
	   claude.ai's whole scale is 60/120/200/300/450ms with a 150ms default,
	   and its hover feedback sits at 120ms. The old scale topped out at
	   620ms and put card hover on 480ms, which is long enough to read as
	   lag rather than as response. */
	--lr-duration-press: 0.1s;
	--lr-duration-fast: 0.15s;
	--lr-duration-base: 0.2s;
	--lr-duration-moderate: 0.28s;
	--lr-duration-slow: 0.4s;
	--lr-duration-slower: 0.5s;
	--lr-easing-standard: cubic-bezier(0.33, 0, 0.2, 1);
	--lr-easing-smooth: cubic-bezier(0.4, 0, 0.2, 1);
	--lr-easing-mechanical: cubic-bezier(0.25, 0.46, 0.45, 0.94);
	
	/* Transition shorthand tokens. Use for new code; existing long-form
	   transition: declarations are intentionally left as-is to avoid a
	   risky global rewrite.
	     - base: short interactive feedback (hover tints, focus rings).
	     - pop:  card lift / image scale / panel pop animations. */
	--lr-transition-base: var(--lr-duration-base) var(--lr-easing-standard);
	/* Card/tag hover. Was --lr-duration-slow (480ms); a hover response
	   that slow feels like the page is catching up with the cursor. */
	--lr-transition-pop: var(--lr-duration-fast) var(--lr-easing-mechanical);
	
	/* Card lift, shared interactive motion (calmer than legacy -8px / scale 1.02). */
	--lr-lift-card-hover: -2px;
	--lr-lift-card-active: -1px;
	--lr-scale-card-active: 0.99;

	/* REMOVED IN 1.11.2: the tokens of the 1.10.00 effects layer.

	   The aurora glow, heading shimmer, scan-line, card tilt, animated
	   divider, tag shimmer and breathing CTA were removed in 1.10.00, and
	   their tokens outlived them. The note that stood here kept them on the
	   grounds that only 8 of roughly 90 templated pages had been checked,
	   and that a reference to an undefined custom property is invalid at
	   computed-value time and drops the whole declaration, which is the
	   failure that blanked the ESAT summary tints and the #tutoring-cta
	   panel. That caution was right, and the condition it set has now been
	   met, so the tokens are gone.

	   What was actually checked, on 31 August 2026:
	     - every text file under app/public, app/content, app/tools and
	       app/deploy, matched on a word boundary so --lr-space-1 does not
	       match inside --lr-space-10, which had produced false positives
	     - the RENDERED HTML of 144 live pages, crawled from the homepage
	       rather than guessed from the slug registry, because page content
	       authored in the editor lives in the database and no repository
	       scan can see it

	   That crawl was validated before being trusted: it finds
	   --lr-padding-inline, the token declared a few lines above precisely
	   because editor-authored content references it. An instrument that
	   could not find that one would not have been evidence of anything.

	   Removed: callout-border-hover, callout-shadow-hover,
	   callout-wash-color, chamfer, color-border-strong, duration-breath,
	   easing-spring, glow-accent, glow-accent-soft, glow-accent-pulse,
	   gradient-hero, gradient-heading, gradient-divider, gradient-shimmer,
	   ink-grid-line, letter-spacing-wide, padding-portfolio, padding-small,
	   perspective-card, scanline-opacity, tilt-amount.

	   Two kept that a naive scan calls unused. --lr-booking-accent and
	   --lr-booking-border are referenced by the booking plugin's own CSS:
	   the declarations here are !important overrides that re-point the
	   plugin's defaults at this palette. --lr-space-1, -7, -9 and -10 are
	   holes in the spacing scale rather than dead weight, and live markup
	   already references --lr-space-6, so the scale stays whole.

	   --lr-chamfer is the one worth knowing about: it encodes the chamfered
	   corner the design brief asks for in section 5B and which was never
	   wired to anything. It is in the git history and in the brief if that
	   idea is ever taken up. */

	/* Opt OUT of browser-level forced dark mode.

	   The palette is a single light theme: surfaces are literal #ffffff in
	   places (.lr-card-white, .lr-panel-white, the curriculum nav) while the
	   text colours come from tokens. Chrome/Edge auto-dark ("Auto Dark Mode
	   for Web Contents", and the Android default) algorithmically inverts a
	   page that declares no colour scheme, and it inverts selectively, so
	   it lightened body copy to near-white while leaving those literal white
	   cards white. Reported as unreadable text on /teaching/esat/ against a
	   navy-inverted page (2026-08-25); computed styles were correct
	   throughout, which is the signature of a compositor-level inversion
	   rather than a CSS fault.

	   `only light` is the documented opt-out: plain `light` still permits
	   forced darkening. The [data-theme="dark"] block further down is a real
	   dark palette but nothing sets that attribute yet; when a toggle ships,
	   this becomes `light dark` and the dark branch takes over properly. */
	color-scheme: only light;
}

/* UPGRADE v1.8: System-Wide Dark Mode Engine */
[data-theme="dark"] {
	--lr-color-bg-light: #0f172a;
	--lr-color-bg-section: #1e293b;
	--lr-color-bg-card: #1e293b;
	--lr-color-surface-muted: #1e293b;
	--lr-color-text-main: #f1f5f9;
	--lr-color-text: #f1f5f9;
	--lr-color-text-heading: #fffdf6;
	--lr-color-text-muted: #94a3b8;
	--lr-color-text-subtle: #cbd5e1;
	--lr-color-border-card: rgba(255, 255, 255, 0.1);
	--lr-color-border-subtle: rgba(255, 255, 255, 0.05);
	--lr-color-border-hover: rgba(var(--lr-color-accent-rgb), 0.5);
	--lr-color-ink: #020617;
	--lr-color-on-ink: #fffdf6;
	
	/* Invert Shadows for Dark Mode */
	--lr-shadow-rest: 0 4px 10px rgba(0, 0, 0, 0.5);
	--lr-shadow-active: 0 2px 4px rgba(0, 0, 0, 0.5);
	--lr-shadow-hover-card: 0 10px 30px rgba(0, 0, 0, 0.6);
	--lr-shadow-hover-button: 0 2px 12px rgba(var(--lr-color-accent-rgb), 0.4);
	--lr-shadow-editorial: 0 10px 40px rgba(0, 0, 0, 0.8);
	
	--lr-callout-bg-color: #1e293b;
	--lr-callout-shadow-idle: rgba(0, 0, 0, 0.4);
}

[data-theme="dark"] img {
	filter: brightness(0.8) contrast(1.1);
}
[data-theme="dark"] img:hover {
	filter: brightness(1) contrast(1);
}

@media (prefers-contrast: high) {
	:root {
		--lr-color-text-main: #000;
		--lr-color-text: #000;
		--lr-color-text-heading: #000;
		--lr-color-bg-card: #ffffff;
		/* Pink inline-code colour fails WCAG AA against light surfaces in
		   high-contrast environments; flatten to body text colour. */
		--lr-color-code: #000;
		/* Give large surfaces a real, visible border edge in high-contrast. */
		--lr-color-border-card: #767676;
		--lr-color-surface-muted: #ffffff;
	}
}
}

/* ==========================================================================
   LAYER: PRIMITIVES
   --------------------------------------------------------------------------
   Purpose:
   Provide reusable low-level building blocks used throughout the site.
   These should be generic, composable, and independent of any specific page.

   Contains:
   • containers
   • grids
   • flex layouts
   • stack/flow utilities
   • surface utilities
   • generic buttons
   • spacing helpers
   • reusable layout patterns

   Never place:
   • page-specific designs
   • hero sections
   • project cards
   • plugin overrides
   ========================================================================== */
@layer primitives {
	
	/* =========================================================
		3. TYPOGRAPHY PRIMITIVES
		Layout, typography, and text color. No shadows or borders.
	========================================================= */
	
	/* !important throughout this block is load-bearing, not defensive habit:
	   Astra emits `h1,.entry-content :where(h1){font-size:36px;font-weight:600;
	   line-height:1.4em}` (and h2/h3 equivalents) as UNLAYERED inline CSS.
	   Unlayered normal declarations outrank layered ones no matter how
	   specific the layered selector is, so without important these token-driven
	   sizes never applied anywhere the page markup lacked an inline style. */
	/* Heading ink. Astra sets `h1..h6{color}` unlayered from its Customizer
	   palette, which outranks any layered rule, so the heading colour token
	   never applied. Marked important here in the primitives layer (NOT in
	   reset): layer precedence inverts for important declarations, so putting
	   it in the earlier reset layer would make it unbeatable and break
	   components like .lr-eyebrow that set their own colour. Within this
	   layer, the class selectors of those components out-specify these
	   element selectors and still win. */
	h1, h2, h3, h4, h5, h6,
	.wp-block-heading:not(.lr-eyebrow),
	.entry-title {
		color: var(--lr-color-text-heading) !important;
	}

	/* :not(.lr-eyebrow) guards throughout, eyebrows are small monospace
	   labels that happen to be marked up as headings, and if content ever
	   emits one carrying wp-block-heading too, the more specific heading
	   selectors here would otherwise blow it up to display size. */
	.wp-block-heading:not(.lr-eyebrow) {
		color: var(--lr-color-text-heading);
		font-weight: 600 !important;
		line-height: var(--lr-line-height-heading) !important;
		margin-bottom: var(--lr-margin-bottom-heading);
	}

	h1.wp-block-heading:not(.lr-hero-heading):not(.lr-eyebrow) {
		font-size: var(--lr-font-size-h1) !important;
		line-height: var(--lr-leading-display) !important;
		letter-spacing: var(--lr-letter-spacing-tight);
	}

	h2.wp-block-heading:not(.lr-eyebrow) {
		font-size: var(--lr-font-size-h2) !important;
		line-height: var(--lr-leading-h2) !important;
		letter-spacing: var(--lr-tracking-2xl);
		margin-top: var(--lr-gap-section);
		margin-bottom: var(--lr-gap-medium);
	}
	h2.wp-block-heading:first-child {
		margin-top: 0;
	}
	
	/* :not(.lr-eyebrow) is here for specificity as much as for eyebrows. The
	   generic `.wp-block-heading:not(.lr-eyebrow)` rule above sets
	   line-height at (0,2,0); a bare `h3.wp-block-heading` is (0,1,1) and
	   loses to it, so the graduated leading below was silently discarded
	   until this selector matched its weight. Measured: h3 stayed at 26.45px
	   with the bare selector and takes 29.9px with this one. */
	h3.wp-block-heading:not(.lr-eyebrow) {
		font-size: var(--lr-font-size-h3) !important;
		line-height: var(--lr-leading-h3) !important;
		text-transform: none;
		letter-spacing: var(--lr-tracking-xl);
		margin-top: var(--lr-gap-large);
		margin-bottom: var(--lr-gap-small);
	}
	h3.wp-block-heading:first-child {
		margin-top: 0;
	}
	
	p {
		color: var(--lr-color-text-main);
		line-height: var(--lr-line-height-body);
		margin-bottom: var(--lr-margin-bottom-para);
		font-size: var(--lr-font-size-body);
	}
	
	/* --- Semantic Typography Primitives --- */
	/* These primitives are frequently applied to real h1/h2/h3 elements, so
	   they need the same important treatment to clear Astra's unlayered
	   heading sizes. Weights unified at 600, the display serif reads heavy
	   at 700 in the large sizes, and 600 matches the homepage hero the scale
	   is derived from. */
	.lr-display { font-size: var(--lr-font-size-hero) !important; line-height: var(--lr-leading-display) !important; font-weight: 600 !important; letter-spacing: var(--lr-letter-spacing-tight); color: var(--lr-color-text-heading); }
	.lr-title { font-size: var(--lr-font-size-h1) !important; line-height: var(--lr-leading-display) !important; font-weight: 600 !important; letter-spacing: var(--lr-letter-spacing-tight); color: var(--lr-color-text-heading); }
	.lr-heading { font-size: var(--lr-font-size-h2) !important; line-height: var(--lr-leading-h2) !important; letter-spacing: var(--lr-tracking-2xl); font-weight: 600 !important; color: var(--lr-color-text-heading); }
	.lr-subheading { font-size: var(--lr-font-size-h3) !important; line-height: var(--lr-leading-h3) !important; letter-spacing: var(--lr-tracking-xl); font-weight: 600 !important; color: var(--lr-color-text-heading); }
	/* Section eyebrows get the same blueprint-label treatment as .lr-mono-meta
	   (brief §5C): monospace, uppercase, widely tracked, accent-coloured, with
	   a short rule as a drafting tick. Eyebrows are authored per-instance in
	   page content with their own inline colour/size/spacing, so !important is
	   the only way to land one consistent treatment across every block,	   matching this stylesheet's established pattern for overriding
	   content-level inline styles (see the adapter layer). */
	.lr-eyebrow {
		font-family: var(--lr-font-mono) !important;
		font-size: 0.78rem !important;
		text-transform: uppercase;
		letter-spacing: var(--lr-letter-spacing-mono) !important;
		font-weight: 500 !important;
		color: var(--lr-color-accent) !important;
		display: flex;
		align-items: center;
		gap: 12px;
	}
	.lr-eyebrow::before {
		content: "";
		display: block;
		width: 32px;
		height: 1px;
		background-color: var(--lr-color-accent);
		flex-shrink: 0;
	}

	/* Blueprint markers ("[01]", "[02]"...) on direct-access / indexed rows.
	   The brief (§3C, Mauricio Juba reference) treats these as schematic
	   annotation: bare monospace, widely tracked, no container. They ship
	   inline-styled from content with Courier New; !important re-points them
	   at the mono token without touching content markup. */
	.lr-mono-meta {
		font-family: var(--lr-font-mono) !important;
		font-size: 0.8rem !important;
		font-weight: 500 !important;
		letter-spacing: var(--lr-letter-spacing-mono);
		color: var(--lr-color-accent) !important;
		font-variant-numeric: tabular-nums;
	}
	.lr-body { font-size: var(--lr-font-size-body); line-height: var(--lr-line-height-body); color: var(--lr-color-text-main); }
	.lr-small { font-size: var(--lr-font-size-small); line-height: var(--lr-line-height-small); color: var(--lr-color-text-main); }
	.lr-caption { font-size: var(--lr-font-size-small); color: var(--lr-color-text-muted); }

	/* --- Layout Engine Primitives --- */
	.lr-container { width: 100%; max-width: var(--lr-max-width-card); margin-inline: auto; padding-inline: var(--lr-padding-inline-shell); box-sizing: border-box; }
	.lr-prose { max-width: var(--lr-prose-max-width, 70ch); margin-inline: auto; }
	.lr-section { padding-block: var(--lr-space-8); }
	.lr-stack { display: flex; flex-direction: column; gap: var(--lr-space-4); }
	.lr-stack-sm { display: flex; flex-direction: column; gap: var(--lr-space-2); }
	.lr-stack-lg { display: flex; flex-direction: column; gap: var(--lr-space-6); }
	
	/* Lobotomized Owl for flow content (adds top margin to adjacent elements) */
	.lr-flow > * + * { margin-top: var(--lr-space-4); }
	
	.lr-cluster { display: flex; flex-wrap: wrap; gap: var(--lr-space-3); align-items: center; }
	
	.lr-grid-equal { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--lr-space-4); }
	.lr-grid-equal > * { min-width: 0; margin-top: 0 !important; margin-bottom: 0 !important; }
	.lr-grid-equal.lr-cols-4 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
	.lr-grid-equal.lr-cols-3 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
	.lr-grid-equal.lr-cols-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
	
	/* --- Surface & Component Anatomy Primitives --- */
	.lr-surface {
		background-color: var(--lr-color-bg-card, #fffdf6);
		border-radius: var(--lr-radius-medium);
		border: var(--lr-border-card);
		box-shadow: var(--lr-shadow-rest);
	}

	.wp-block-group.lr-surface-raised {
		width: 100%;
		max-width: var(--lr-max-width-card);
		margin-inline: auto;
		box-sizing: border-box;
	}
	
	.lr-card {
		display: flex;
		flex-direction: column;
		height: 100%;
		padding: var(--lr-space-5);
	}
	
	.lr-card-header {
		margin-bottom: var(--lr-space-3);
		min-height: 4rem; /* Aligns paragraph baselines by assuming 1-2 line titles */
	}
	
	.lr-card-body {
		flex-grow: 1; /* Pushes footer down */
		margin-bottom: var(--lr-space-4);
	}
	
	.lr-card-footer {
		margin-top: auto; /* Aligns to bottom */
	}
	
	.lr-label {
		text-transform: none;
		letter-spacing: normal;
		font-weight: 600;
		font-size: var(--lr-font-size-label);
		line-height: var(--lr-line-height-small);
		color: var(--lr-color-text-muted);
		margin-bottom: var(--lr-gap-medium);
	}
	
	.lr-desc { 
		color: var(--lr-color-text-muted);
		margin-bottom: var(--lr-gap-medium); 
	}
	
	/* Astra emits unlayered `a{color:var(--ast-global-color-0)}` (#046bd2) and
	   `a:hover,a:focus{color:var(--ast-global-color-1)}`. Unlayered normal
	   declarations outrank layered ones, so without important every link on
	   the site rendered in Astra's stock blue rather than the brand accent,	   the same failure mode as the heading scale and the CF7 focus ring.
	   .has-text-color stays excluded so editor-chosen link colours still win. */
	/* Underlines are owned exclusively by the link-underline policy block
	   further down this file (§22), which is unlayered and marked important:
	   persistent underlines on inline copy for WCAG, hover-only underlines on
	   structural links. This rule previously ran a SECOND, competing
	   mechanism, an animated background-gradient bar, on top of it, so
	   hovering produced two parallel lines. The gradient is gone; text-
	   decoration alone now draws every underline.

	   text-decoration here is deliberately NOT important, so the §22 policy
	   can still win: a layered important declaration would outrank the
	   unlayered policy and silently strip the accessibility underlines.

	   The exclusion list covers links that are really buttons and bring their
	   own foreground colour. .lr-portal__cta and .lr-dashboard__link are
	   styled by the booking plugin as solid accent buttons with white labels;
	   forcing the link colour on them painted brand-blue text onto a
	   brand-blue fill and made the booking page's primary CTA read as an
	   empty box. Any future button-like link needs adding here too. */
	a:not(.wp-block-button__link):not(.lr-btn):not(.lr-portal__cta):not(.lr-dashboard__link):not(.lr-card):not(.button):not(.has-text-color):not(.menu-link) {
		color: var(--lr-color-link) !important;
		text-decoration: none;
		text-underline-offset: 3px;
		/* Weight inherits from the surrounding copy. A blanket 500 used to
		   embolden every inline link, with the link hue now distinct from
		   the body navy, bolding is a second emphasis the prose does not
		   need, and reference editorial links are set at body weight. */
		transition: text-decoration-thickness var(--lr-duration-fast) var(--lr-easing-standard), color var(--lr-duration-fast) var(--lr-easing-standard);
	}
	
	a:not(.wp-block-button__link):not(.lr-btn):not(.lr-portal__cta):not(.lr-dashboard__link):not(.lr-card):not(.button):not(.has-text-color):not(.menu-link):hover,
	a:not(.wp-block-button__link):not(.lr-btn):not(.lr-portal__cta):not(.lr-dashboard__link):not(.lr-card):not(.button):not(.has-text-color):not(.menu-link):focus-visible {
		/* Hover thickens the single policy-drawn underline instead of adding
		   a second line. Beats Astra's unlayered `a:hover,a:focus{color:...}`. */
		text-decoration-thickness: 2px;
		color: var(--lr-color-link-hover) !important;
	}
	
	a:not(.wp-block-button__link):not(.lr-btn):not(.lr-portal__cta):not(.lr-dashboard__link):not(.lr-card):not(.button):not(.has-text-color):not(.menu-link):focus-visible {
		outline: 2px solid var(--lr-color-link-hover);
		outline-offset: 2px;
		border-radius: 2px;
	}
	
	@media (prefers-reduced-motion: reduce) {
		/* The forced underline that used to live here existed to give
		   reduced-motion users an affordance the animated gradient bar could
		   not provide. The gradient is gone and §22 draws a real underline in
		   every case, so only the transition needs suppressing, keeping the
		   old rule would have stacked a second line for these users. */
		a:not(.wp-block-button__link):not(.lr-btn):not(.lr-portal__cta):not(.lr-dashboard__link):not(.lr-card):not(.button):not(.has-text-color):not(.menu-link) {
			transition: none;
		}
	}
	
	@media print {
		a:not(.wp-block-button__link):not(.lr-btn):not(.lr-portal__cta):not(.lr-dashboard__link):not(.lr-card):not(.button):not(.has-text-color):not(.menu-link) {
			text-decoration: underline !important;
			color: #000 !important;
			background: none !important;
		}
	}
	
	/* =========================================================
		4. LAYOUT PRIMITIVES
	========================================================= */
	.lr-section-full {
		display: flex;
		flex-direction: column;
		gap: var(--lr-gap-section);
		/* Block padding comes from --lr-padding-section (the 80 to 120px
		   "breathing room" the brief §3A calls the biggest contributor to the
		   premium feel). This previously reused --lr-gap-section for both
		   axes, which left --lr-padding-section effectively unused and capped
		   section padding at the smaller between-children gap. */
		padding: var(--lr-padding-section) var(--lr-padding-inline-shell);
	}
	
	.lr-section-full > * {
		width: 100%;
		max-width: var(--lr-max-width-card);
		margin-inline: auto;
		margin-top: 0 !important;
		margin-bottom: 0 !important;
	}
	
	@supports (animation-timeline: view()) {
		@media (prefers-reduced-motion: no-preference) {
			.lr-section-full > * {
				animation: lr-scroll-reveal linear both;
				animation-timeline: view();
				animation-range: entry 2% cover 15%;
			}

			/* UPGRADE v1.4: Staggered cascade, each child in a section
			   enters 80ms after the previous, creating a cinematic waterfall
			   reveal instead of a uniform block slide. */
			.lr-section-full > *:nth-child(1) { animation-delay: 0ms; }
			.lr-section-full > *:nth-child(2) { animation-delay: 80ms; }
			.lr-section-full > *:nth-child(3) { animation-delay: 160ms; }
			.lr-section-full > *:nth-child(4) { animation-delay: 240ms; }
			.lr-section-full > *:nth-child(5) { animation-delay: 320ms; }
			.lr-section-full > *:nth-child(6) { animation-delay: 400ms; }
			.lr-section-full > *:nth-child(n+7) { animation-delay: 480ms; }
		}
	}
	
	/* Transform-only, deliberately. This runs on a view() timeline with
	   `both` fill, so anything before the animation range renders at the
	   from-state. With `opacity: 0` in that from-state, content is invisible
	   until the timeline resolves, and a decorative reveal should never be
	   able to hide content. Dropping the fade caps the worst case at a 15px
	   offset instead of a missing element.

	   Correction to an earlier note here: this change was originally credited
	   with fixing a faded heading on /book/. That attribution was wrong. The
	   rule sits inside `prefers-reduced-motion: no-preference`, and the
	   environment those captures came from reports `reduce`, so this
	   animation was never running when the fault was observed. The /book/
	   rendering oddity is still unexplained and may well be an artefact of
	   that capture method, it renders correctly at viewport heights where
	   the page does not scroll. Kept because the latent risk above is real
	   for users who do not request reduced motion, not because it fixed that. */
	@keyframes lr-scroll-reveal {
		from {
			transform: translateY(15px);
		}
		to {
			transform: translateY(0);
		}
	}
	
	.lr-section-centered {
		text-align: center;
		max-width: var(--lr-max-width-centered);
		margin-inline: auto;
	}
	
	/* Hub summary cards, accent edge without implying clickability. */
	.lr-at-a-glance {
		border-left: 3px solid var(--lr-color-accent);
		background-color: var(--lr-color-bg-card);
	}

	.lr-trust-strip {
		margin-top: var(--lr-gap-small);
		margin-bottom: var(--lr-gap-medium);
		opacity: 0.92;
	}

	/* Visitor intent guide, search-intent routing (lr-page-intent.json). */
	.lr-intent-guide {
		border-left: 3px solid var(--lr-color-accent-muted, var(--lr-color-accent));
		background-color: var(--lr-color-bg-section);
	}
	.lr-intent-guide__primary {
		font-weight: 500;
	}
	.lr-intent-guide__subheading {
		font-size: var(--lr-text-sm, 0.9rem);
		font-weight: 600;
		margin: 1.25rem 0 0.5rem;
		text-transform: none;
		letter-spacing: normal;
	}
	.lr-intent-guide__block:first-of-type .lr-intent-guide__subheading {
		margin-top: 0.75rem;
	}
	
	/* =========================================================
		5. GRID & COLUMN PRIMITIVES
	========================================================= */
	@media (min-width: 922px) {
		.wp-block-columns {
			display: flex;
			flex-direction: row;
			flex-wrap: nowrap;
			align-items: stretch;
			gap: var(--lr-gap-card);
			width: 100% !important;
		}
		
		.wp-block-column {
			min-width: 0;
			display: flex;
			flex-direction: column;
		}
		
		.wp-block-column > .lr-card,
		.wp-block-column > .lr-card-white,
		.wp-block-column > .lr-project-card {
			flex-grow: 1;
		}
		
		.wp-block-columns > .wp-block-column:not([class*="lr-col-"]) {
			flex: 1 1 0;
		}
		
		.lr-col-30 { flex: 0 0 30%; }
		.lr-col-40 { flex: 0 0 40%; }
		.lr-col-50 { flex: 0 0 50%; }
		.lr-explore-portfolio .wp-block-columns {
			display: flex !important;
			flex-wrap: wrap !important;
			gap: var(--lr-gap-card, 24px) !important;
			justify-content: center !important;
		}
		
		.lr-explore-portfolio .wp-block-columns > .wp-block-column {
			flex: 1 1 240px !important;
			min-width: 220px !important;
			max-width: 380px !important;
			display: flex !important;
			flex-direction: column !important;
		}
	}
	
	.wp-block-columns.lr-columns-medium {
		gap: var(--lr-gap-medium) !important;
		row-gap: var(--lr-gap-medium) !important;
		column-gap: var(--lr-gap-medium) !important;
	}
	
	/* Mobile column-stacking is handled by the `@layer adapter` §43 rule below
	   (with !important to beat Astra). The previous duplicate primitive
	   declaration has been removed. */
	
	/* =========================================================
		6. SPACERS
	========================================================= */
	.wp-block-separator.has-alpha-channel-opacity.is-style-wide {
		margin-bottom: 60px;
	}

	/* The scroll-animated gradient divider that sat here has been removed.
	   The band change is the section marker now, that is how the reference
	   design separates sections, and a fading rule drawn on top of a colour
	   change states the same boundary twice. */

}

/* ==========================================================================
   LAYER: COMPONENTS
   --------------------------------------------------------------------------
   Purpose:
   Define complete UI components that visitors interact with. Components are
   built using the primitives and design tokens defined above.

   Contains:
   • navigation
   • hero sections
   • cards
   • buttons (component variants)
   • forms
   • footers
   • project layouts
   • booking interface
   • learning hub
   • all reusable website components

   Never place:
   • browser resets
   • design tokens
   • third-party overrides
   ========================================================================== */

@layer components {
	
	/* =========================================================
		6.5. SCROLL REVEAL (MOTION ENGINE)
	========================================================= */
	.lr-reveal-pending {
		opacity: 0;
		transform: translateY(20px);
	}
	.lr-reveal-active {
		opacity: 1;
		transform: translateY(0);
		transition: opacity 0.6s var(--lr-easing-standard), transform 0.6s var(--lr-easing-standard);
		will-change: transform, opacity;
	}
	@media (prefers-reduced-motion: reduce) {
		.lr-reveal-pending,
		.lr-reveal-active {
			opacity: 1 !important;
			transform: none !important;
			transition: none !important;
		}
	}

	/* PRINT NEEDS THE IDENTICAL GUARD, and not having it lost whole sections.

	   .lr-reveal-pending sets opacity 0 and an IntersectionObserver clears it
	   as each block scrolls into view. Printing does not scroll, so anything
	   the reader had not already reached was invisible on paper while being
	   perfectly present in the DOM.

	   Measured by the cross-browser audit against real generated PDFs:
	   printing /teaching/ from the top dropped four content blocks entirely,
	   including "Part of" and "Connected work", which appear zero times in
	   the PDF text. Students and schools print worked solutions from this
	   site, so silently missing content is a serious fault rather than a
	   cosmetic one.

	   The guard already existed ten lines above for reduced motion. This is
	   the same three declarations for the same reason: the element must be
	   visible when the animation that would have revealed it cannot run. */
	@media print {
		.lr-reveal-pending,
		.lr-reveal-active {
			opacity: 1 !important;
			transform: none !important;
			transition: none !important;
		}
	}
	
	/* =========================================================
		7. GLOSSARY & DEFINITION LISTS
	========================================================= */
	.lr-glossary {
		margin: 0;
		padding: 0;
	}
	
	.lr-glossary dt {
		font-weight: 700;
		color: var(--lr-color-text-heading);
		font-size: var(--lr-font-size-body);
		margin-top: 24px;
		border-bottom: 1px solid var(--lr-color-border-subtle);
		padding-bottom: 4px;
		display: inline-block;
	}
	
	.lr-glossary dd {
		margin-left: 0;
		margin-top: 8px;
		margin-bottom: 16px;
		line-height: var(--lr-line-height-body);
		color: var(--lr-color-text-main);
		font-size: var(--lr-font-size-body);
	}
	
	/* =========================================================
		8. MATHEMATICS (MathJax Rendering Layer)
	========================================================= */
	
	.lr-math,
	.lr-math-block {
		display: block;
		width: 100%;
		min-width: 0;
		max-width: 100%;
		overflow-x: auto;
		overflow-y: hidden;
		-webkit-overflow-scrolling: touch;
		padding: clamp(16px, 4vw, 24px);
		padding-bottom: clamp(24px, 4vw, 32px);
		margin: clamp(16px, 3vw, 24px) 0;
		background-color: var(--lr-color-bg-light);
		border: var(--lr-border-card);
		border-radius: var(--lr-radius-medium, 10px);
		box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03);
		transition:
		background-color var(--lr-duration-moderate) var(--lr-easing-mechanical),
		border-color var(--lr-duration-moderate) var(--lr-easing-standard),
		box-shadow var(--lr-duration-moderate) var(--lr-easing-standard);
	}
	
	/* =========================================================
		8.5. ACCORDION COMPONENT
	========================================================= */
	.lr-accordion {
		border-bottom: 1px solid var(--lr-color-border-subtle);
		padding: var(--lr-gap-tight) 0;
	}
	.lr-accordion:first-of-type {
		border-top: 1px solid var(--lr-color-border-subtle);
	}
	
	.lr-accordion summary {
		display: flex;
		justify-content: space-between;
		align-items: center;
		cursor: pointer;
		font-weight: 600;
		color: var(--lr-color-text-heading);
		padding: var(--lr-gap-tight) 0;
		transition: color var(--lr-duration-fast);
		list-style: none;
	}
	.lr-accordion summary::-webkit-details-marker {
		display: none;
	}
	.lr-accordion summary::after {
		content: "+";
		font-size: 1.25rem;
		font-weight: 300;
		color: var(--lr-color-text-muted);
		transition: transform var(--lr-duration-moderate);
	}
	.lr-accordion[open] summary::after {
		transform: rotate(45deg);
	}
	.lr-accordion summary:hover {
		color: var(--lr-color-accent);
	}
	.lr-accordion__content {
		padding-bottom: var(--lr-gap-medium);
		color: var(--lr-color-text-main);
	}
	
	/* =========================================================
		8.6. TIMELINE COMPONENT
	========================================================= */
	.lr-timeline {
		list-style: none;
		padding: 0;
		margin: var(--lr-gap-large) 0;
		position: relative;
	}
	.lr-timeline::before {
		content: "";
		position: absolute;
		top: 0;
		bottom: 0;
		left: 14px;
		width: 2px;
		background: var(--lr-color-border-subtle);
		z-index: 1;
	}
	.lr-timeline-item {
		position: relative;
		padding-left: 48px;
		margin-bottom: var(--lr-gap-large);
	}
	.lr-timeline-item:last-child {
		margin-bottom: 0;
	}
	.lr-timeline-item::before {
		content: "";
		position: absolute;
		left: 8px;
		top: 6px;
		width: 14px;
		height: 14px;
		border-radius: 50%;
		background: var(--lr-color-bg-light);
		border: 2px solid var(--lr-color-accent);
		z-index: 2;
	}
	.lr-timeline-date {
		font-size: var(--lr-font-size-caption);
		color: var(--lr-color-text-muted);
		margin-bottom: var(--lr-gap-tight);
		font-weight: 600;
		text-transform: uppercase;
		letter-spacing: 0.05em;
	}
	.lr-timeline-title {
		font-size: var(--lr-font-size-h4);
		font-weight: 700;
		color: var(--lr-color-text-heading);
		margin-bottom: var(--lr-gap-tight);
	}
	
	/* =========================================================
		8.7. STAT BOX COMPONENT
	========================================================= */
	.lr-stat-grid {
		display: grid;
		grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
		gap: var(--lr-gap-medium);
		margin: var(--lr-gap-large) 0;
	}
	.lr-stat-box {
		padding: var(--lr-gap-medium);
		text-align: center;
		display: flex;
		flex-direction: column;
		justify-content: center;
	}
	.lr-stat-value {
		font-size: clamp(2rem, 5vw, 2.5rem);
		font-weight: 800;
		color: var(--lr-color-accent);
		line-height: 1.1;
		margin-bottom: var(--lr-gap-tight);
		letter-spacing: -0.02em;
	}
	.lr-stat-label {
		font-size: var(--lr-font-size-caption);
		color: var(--lr-color-text-heading);
		font-weight: 600;
	}

	/* Stat entrance, a quiet rise-and-fade as the box enters the viewport.
	   The previous treatment scaled from 0.8 with a blur-to-sharp 1.04
	   overshoot: two simultaneous motion cues plus a filter, which reads as
	   an effect demo rather than a reveal. Rise + fade is the vocabulary the
	   rest of the site already uses, and the numbers stay sharp throughout.
	   Same guards as before: view()-progressive only, and never for
	   reduced-motion users. */
	@supports (animation-timeline: view()) {
		@media (prefers-reduced-motion: no-preference) {
			.lr-stat-box {
				animation: lr-stat-pop linear both;
				animation-timeline: view();
				animation-range: entry 5% cover 20%;
			}
		}
	}

	@keyframes lr-stat-pop {
		from {
			opacity: 0;
			transform: translateY(12px);
		}
		to {
			opacity: 1;
			transform: translateY(0);
		}
	}
	
	/* =========================================================
		8.8. STICKY NAV & TOC
	========================================================= */
	.lr-sticky-nav {
		position: sticky;
		top: var(--lr-gap-large);
		align-self: start;
		z-index: 10;
	}
	.lr-toc-list {
		list-style: none;
		padding: 0;
		margin: 0;
		border-left: 2px solid var(--lr-color-border-subtle);
	}
	.lr-toc-item {
		margin-bottom: 8px;
	}
	.lr-toc-item a {
		color: var(--lr-color-text-muted);
		text-decoration: none;
		font-weight: 500;
		font-size: 0.95rem;
		padding-left: 14px;
		display: block;
		transition: color var(--lr-duration-fast), border-left-color var(--lr-duration-fast);
		border-left: 2px solid transparent;
		margin-left: -2px;
	}
	.lr-toc-h3 a {
		padding-left: 24px;
		font-size: var(--lr-font-size-caption);
	}
	.lr-toc-item a:hover {
		color: var(--lr-color-accent);
		border-left-color: var(--lr-color-accent);
	}
	
	.lr-math mjx-container,
	.lr-math-block mjx-container {
		margin-left: auto;
		margin-right: auto;
		font-variant-numeric: tabular-nums;
	}
	
	.lr-math mjx-math,
	.lr-math-block mjx-math {
		font-size: 1.02em;
		line-height: 1.4;
	}
	
	@media (hover: hover) and (pointer: fine) {
		.lr-math:hover,
		.lr-math-block:hover {
			background-color: var(--lr-color-accent-wash);
			border-color: rgba(var(--lr-color-accent-rgb), 0.15);
		}
	}
	
	@media (prefers-contrast: high) {
		.lr-math,
		.lr-math-block {
			background-color: #ffffff;
			border-color: #d1d5db;
		}
	}
	
	@media (prefers-reduced-motion: reduce) {
		.lr-math,
		.lr-math-block {
			transition: none;
		}
	}
	
	@media print {
		.lr-math,
		.lr-math-block {
			background: #ffffff;
			box-shadow: none;
			border: 1px solid var(--lr-color-border-subtle);
		}
	}
	
	/* =========================================================
		9. FIGURE CAPTIONS
	========================================================= */
	figure {
		max-width: 100%;
		margin: 0;
		padding: 0;
		overflow: visible;
	}
	
	figure figcaption {
		width: 100%;
		max-width: 720px;
		margin: 12px auto 0;
		padding: 8px 14px;
		writing-mode: horizontal-tb;
		/* Same editorial voice as .lr-editorial-caption, serif italic reads
		   as a considered caption rather than interface text, and now that
		   real italics are loaded it costs nothing. */
		font-family: var(--lr-font-heading);
		font-style: italic;
		font-size: var(--lr-font-size-figcaption);
		line-height: 1.45;
		color: var(--lr-color-text-muted);
		text-align: center;
		background: transparent;
		border-radius: 8px;
		word-break: break-word;
		/* No opacity on caption text. The muted token is tuned to clear
		   WCAG AA (5.1:1 on white, 4.8:1 on the section band); a 0.85
		   opacity blended it down to #788597, 3.7:1, a Lighthouse
		   colour-contrast failure on every editorial figure (2026-08-23).
		   The hover state shifts colour only. */
		transition: color var(--lr-duration-slow) var(--lr-easing-smooth);
	}
	
	@media (hover: hover) and (pointer: fine) {
		figure:hover figcaption {
			color: var(--lr-color-text-heading);
		}
	}
	
	@media (max-width: 480px) {
		figure figcaption {
			font-size: var(--lr-font-size-figcaption);
			padding: var(--lr-gap-tight) var(--lr-gap-small);
			margin-top: var(--lr-gap-tight);
		}
	}
	
	figure.caption-left figcaption { text-align: left; max-width: 640px; margin-left: 0; }
	figure.caption-right figcaption { text-align: right; max-width: 640px; margin-right: 0; }

	/* =========================================================
		9b. EDITORIAL FIGURE (hero/lead photography)

		.lr-editorial-figure ships from page content with no styling of its
		own beyond an inline margin reset, the photo and caption were
		rendering completely flat (square corners, no shadow, plain text
		caption). This is the site's single largest photo, so it carries
		real weight in the redesign.
	========================================================= */
	/* RESERVES THE SPACE BEFORE THE PHOTO LOADS. Do not remove without
	   re-measuring CLS, and update the ratio if the photo is replaced.

	   Measured on production before this rule: the homepage scored
	   CLS 0.2116, a failing Core Web Vital, against 0.0002 on the ESAT hub
	   and 0.05 on a solution page. Attributed with the layout-shift API to a
	   single event worth 0.2079 at 1267ms, in which this figure jumped from
	   102px tall to 671px as the image arrived: the caption was holding the
	   box open on its own until then, and everything below moved.

	   The cause is that the img ships from page content with no width or
	   height attributes. The theme already has a filter for exactly this,
	   lr_images_complete_img_tag in includes/lr-images.php, which resolves
	   the file and adds them. It is NOT running on this image: the served
	   tag carries neither dimensions nor the fetchpriority="high" that the
	   same filter adds unconditionally to the first eager image, so the tag
	   never reached it. That is worth chasing, but it cannot be fixed from
	   here, because the live homepage markup exists only in the database and
	   differs from content/pages/home-draft.html, which does not contain
	   this figure at all.

	   So the reservation is made in CSS instead. 664 by 889 is the image's
	   real intrinsic size, read from the rendered element. This only affects
	   the pre-load state: once the photo arrives the box is the same size it
	   always was, which is why it does not move a single pixel of the final
	   layout. */
	.lr-editorial-figure img {
		display: block;
		width: 100%;
		height: auto;
		aspect-ratio: 664 / 889;
		border-radius: var(--lr-radius-large);
		box-shadow: var(--lr-shadow-editorial);
		transition: transform var(--lr-duration-slow) var(--lr-easing-mechanical), box-shadow var(--lr-duration-slow) var(--lr-easing-standard);
	}

	@media (hover: hover) and (pointer: fine) {
		.lr-editorial-figure:hover img {
			transform: translateY(-3px);
			box-shadow: 0 4px 10px rgba(20, 33, 61, 0.08), 0 28px 48px -14px rgba(20, 33, 61, 0.28);
		}
	}

	.lr-editorial-caption {
		font-family: var(--lr-font-heading);
		font-style: italic;
		font-size: var(--lr-font-size-figcaption);
		line-height: 1.5;
		color: var(--lr-color-text-muted);
	}

	/* =========================================================
		10. CARD IMAGES
	========================================================= */
	.lr-card img,
	.lr-project-card img {
		display: block;
		width: 100%;
		height: auto;
		border-radius: var(--lr-radius-image);
		box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
		transition: transform var(--lr-duration-slow) var(--lr-easing-mechanical);
	}
	
	@media (hover: hover) and (pointer: fine) {
		.lr-project-card:hover img,
		.lr-card-white:hover img {
			transform: scale(1.01);
		}
	}
	
	/* =========================================================
		11. BUTTON VARIANTS
	========================================================= */
	.is-primary {
		background-color: var(--lr-color-accent);
		color: #ffffff;
		border: 2px solid var(--lr-color-accent);
	}
	
	.is-outline {
		background: transparent;
		color: var(--lr-color-text-heading);
		border: 2px solid var(--lr-color-border-subtle);
	}
	
	@media (hover: hover) and (pointer: fine) {
		.is-primary:hover {
			background-color: var(--lr-color-accent-hover);
			box-shadow: var(--lr-shadow-hover-button);
		}
		.is-outline:hover {
			border-color: var(--lr-color-accent);
			background-color: var(--lr-color-bg-light);
			color: var(--lr-color-accent);
		}
	}
	
	/* Utility button primitives */
	.lr-btn {
		display: inline-block;
		text-align: center;
		text-decoration: none;
		font-weight: bold;
		border-radius: var(--lr-radius-small, 6px);
		padding: 10px 24px;
		font-size: 0.9rem;
		transition: background-color var(--lr-duration-base) var(--lr-easing-standard), 
		            color var(--lr-duration-base) var(--lr-easing-standard), 
		            border-color var(--lr-duration-base) var(--lr-easing-standard),
		            transform var(--lr-duration-fast) var(--lr-easing-mechanical),
		            box-shadow var(--lr-duration-base) var(--lr-easing-standard);
		cursor: pointer;
	}
	.lr-btn:hover {
		transform: translateY(-2px);
	}
	.lr-btn:active {
		transform: translateY(0) scale(0.98);
	}
	.lr-btn-solid {
		background-color: var(--lr-color-accent);
		color: #ffffff !important;
		border: 1px solid var(--lr-color-accent);
	}
	.lr-btn-solid:hover {
		background-color: var(--lr-color-accent-hover);
		border-color: var(--lr-color-accent-hover);
		box-shadow: var(--lr-shadow-hover-button);
	}
	.lr-btn-outline {
		background-color: transparent;
		color: var(--lr-color-accent) !important;
		border: 1px solid var(--lr-color-border-subtle);
	}
	.lr-btn-outline:hover {
		border-color: var(--lr-color-accent);
		background-color: var(--lr-color-accent-wash);
	}
	
	/* Solid CTAs inside an ink context (§48).

	   The §48 token remap points --lr-color-accent at the LIGHT on-ink blue
	   (#d5f3ff). That is correct for link and label text on navy, but every
	   solid-CTA rule pairs the accent fill with a hard-coded white label
	   (.is-primary above, .lr-btn-solid above, and the .wp-block-button
	   fill/is-primary rules in the adapter layer), so on an ink plate the
	   CTA rendered white-on-#d5f3ff, 1.81:1, effectively unreadable. Seen
	   on the hero mastheads of /teaching/, /teaching/roadmap/,
	   /teaching/for-schools/ and /teaching/for-consultants/ (2026-08-25).

	   Invert the pairing instead of unpicking the remap: keep the light
	   fill and stamp the label ink navy. Measured at 13.83:1 against the pale
	   blue fill under Oxford blue, up from 9.65:1 under the indigo, and it reads as a deliberate
	   inverted button on the dark plate rather than a colour accident.
	   Hover only shifts the fill to #cbd8e8, which lifts the ratio further.

	   Layer note: this MUST live in @layer components, not adapter. The
	   declarations it corrects are themselves !important, and for important
	   declarations layer order inverts, an adapter-layer !important loses
	   to the components-layer .lr-btn-solid rule above. Same layer, later
	   source order and higher specificity is what makes it win. */
	:is(
		.site-header,
		.site-footer,
		.lr-footer-secondary-nav,
		.lr-site-footer,
		.lr-on-ink,
		.entry-content header.wp-block-group:has(.lr-hero-heading)
	) :is(
		.wp-block-button__link.is-primary,
		.wp-block-button.is-style-fill .wp-block-button__link,
		.lr-btn-primary,
		.lr-btn-solid
	) {
		background-color: var(--lr-color-on-ink-accent) !important;
		border-color: var(--lr-color-on-ink-accent) !important;
		color: var(--lr-color-ink) !important;
	}
	
	/* =========================================================
		12. UI COMPONENTS (Main Cards)
	========================================================= */
	.lr-surface-raised,
	.lr-card, 
	.lr-card-white, 
	.lr-project-card {
		position: relative;
		border-radius: var(--lr-radius-large);
		padding: var(--lr-padding-card);
		background-color: var(--lr-color-bg-card);
		box-shadow: var(--lr-shadow-rest);
		border: var(--lr-border-card);
		contain: layout;
		backface-visibility: hidden;
		transition:
		transform var(--lr-transition-pop),
		box-shadow var(--lr-transition-pop),
		/* background-color was missing, so the tier-2 hover wash (§12)
		   snapped in and out while the border it travels with faded. */
		background-color var(--lr-duration-moderate) var(--lr-easing-standard),
		border-color var(--lr-duration-moderate) var(--lr-easing-standard);
		display: flex;
		flex-direction: column;
		height: 100%;
	}
	

	.lr-surface-raised-white,
	.lr-card-white {
		background-color: #ffffff;
		padding: var(--lr-padding-card);
		display: flex;
		flex-direction: column;
		height: 100%;
		width: 100%;
		flex-grow: 1;
	}
	
	/* Prose measure is capped by the unlayered MEASURE section near the end
	   of this file, not here. A layered rule loses to the unlayered
	   exemptions that section declares, so keeping a second copy here just
	   produced a cap that silently never applied. */


	/* OPTICAL GRID ALIGNMENT
	   Pushes the final block (e.g., links or buttons) to the bottom of the card
	   so all cards in a grid row share a perfect horizontal baseline. */
	.lr-card > *:last-child,
	.lr-card-white > *:last-child,
	.lr-project-card > *:last-child {
		margin-bottom: 0;
		margin-top: auto;
	}
	
	/* Reduce visual clutter: remove shadows from nested cards */
	.lr-card .lr-card-white,
	.lr-card .lr-surface-raised-white {
		box-shadow: none;
	}

	.lr-card + .lr-card-white,
	.lr-surface-raised-white + .lr-surface-raised-white {
		margin-top: var(--lr-gap-medium);
	}

	/* ==========================================================================
	   REUSABLE CARD GRID (Subgrid Alignment)
	   ========================================================================== */
	/* UPGRADE v1.8: Native CSS Masonry & Container Queries */
	.lr-card-grid {
		display: grid;
		grid-template-columns: repeat(1, 1fr);
		gap: var(--lr-space-4);
		margin-top: var(--lr-space-5);
		grid-template-rows: masonry;
		align-tracks: start;
	}

	@container (min-width: 768px) {
		.lr-card-grid.lr-cols-2 { grid-template-columns: repeat(2, 1fr); }
		.lr-card-grid.lr-cols-3 { grid-template-columns: repeat(3, 1fr); }
		.lr-card-grid.lr-cols-4 { grid-template-columns: repeat(2, 1fr); }
	}
	@container (min-width: 1024px) {
		.lr-card-grid.lr-cols-4 { grid-template-columns: repeat(4, 1fr); }
	}

	/* Subgrid for optical alignment of internal content */
	.lr-card-grid > .lr-card-white,
	.lr-card-grid > .lr-card {
		display: grid;
		grid-template-rows: subgrid;
		grid-row: span 3;
		gap: var(--lr-space-3);
		padding: var(--lr-padding-card);
		margin: 0; /* override any conflicting WP margins */
	}

	/* Reset margin for grid children */
	.lr-card-grid > .lr-card-white > *,
	.lr-card-grid > .lr-card > * {
		margin: 0 !important;
	}

	/* Footer links auto-align to bottom */
	.lr-card-grid > .lr-card-white > *:last-child,
	.lr-card-grid > .lr-card > *:last-child {
		align-self: end;
	}
	
	.lr-card-white > div:first-child {
		margin-bottom: var(--lr-gap-medium);
	}
	
	/* Enforce vertical rhythm between STACKED cards. */
	.lr-card + .lr-card,
	.lr-card-white + .lr-card-white,
	.lr-card + .lr-card-white,
	.lr-card-white + .lr-card,
	.lr-project-card + .lr-project-card,
	.lr-surface-raised + .lr-surface-raised {
		margin-top: 32px; /* Reduced from var(--lr-gap-card) */
	}

	/* … but cancel it for cards laid out SIDE BY SIDE.
	   On /projects/ the card class is applied directly to the WordPress
	   column element (`.wp-block-column.lr-project-card`), so two cards in
	   one row are adjacent siblings and the rule above pushed the right-hand
	   card down by 32px. Every two-card row on that page was rendering
	   misaligned: measured left card at y=5993, right card at y=6025.

	   A card that is a flex or grid item takes its spacing from the
	   container's `gap` in both axes, so the adjacency margin is not just
	   wrong here, it is redundant. Higher specificity than the rule above,
	   and later in the same layer. */
	.wp-block-columns > .lr-project-card + .lr-project-card,
	.wp-block-columns > .lr-card + .lr-card,
	.wp-block-columns > .lr-card-white + .lr-card-white,
	.wp-block-columns > .lr-surface-raised + .lr-surface-raised,
	.lr-card-grid > * + * {
		margin-top: 0;
	}

	/* Vertical rhythm BETWEEN rows of cards.
	   A run of .wp-block-columns siblings sits inside a plain
	   .wp-block-group (display: block), so neither the .lr-section-full flex
	   gap nor the row's own column-gap reaches the space between one row and
	   the next, consecutive rows were touching exactly (measured gap 0 on
	   every adjacent pair on /projects/).

	   The space is placed on the CARDS rather than on the row container on
	   purpose. A margin on .wp-block-columns does not take effect on these
	   pages: in an isolated headless render with every stylesheet readable,
	   an inline `margin-top: 99px` reads back from the style attribute but
	   computes to 0px, and a scan of every rule in every sheet that the row
	   matches returns no margin declaration at all. That cause is still
	   unidentified. Margins on the card itself demonstrably DO apply, the
	   32px adjacency margin cancelled above is the proof, so the rhythm is
	   hung on the thing that is known to respond.

	   :not(:last-child) keeps it from adding a trailing gap under the final
	   row. On mobile, where Astra stacks the columns, this margin sits on
	   top of the container's row-gap; that is a slightly larger stacked gap,
	   not a broken one. */
	.wp-block-columns:not(:last-child) > .lr-project-card,
	.wp-block-columns:not(:last-child) > .lr-card,
	.wp-block-columns:not(:last-child) > .lr-card-white {
		margin-bottom: var(--lr-gap-card);
	}
	
	/* CARD HOVER, IN THREE TIERS
	   ------------------------------------------------------------------
	   The card classes are worn by two unrelated kinds of thing: small
	   tiles whose only purpose is to send you somewhere, and full-width
	   prose sections that merely want the card's surface. Counted across
	   content/pages: 373 tiles against 2,041 prose sections. They must not
	   answer a hover the same way, and until now they very nearly did,
	   which is what made the tiles feel dead.

	   The dead rule was the one below, which every plain .lr-card matched:
	   `box-shadow: var(--lr-shadow-hover-panel)` resolves to `none` (all
	   decorative shadows on this site were deliberately flattened), and
	   --lr-color-border-hover is #002147 against a resting border of
	   #1f374e. Two dark navies one 1px line wide. Measured in a harness
	   over this stylesheet: the ONLY declaration that survived was a
	   colour change no one can see. The card promised a response and paid
	   out nothing.

	   So: tier 1 for whole-card destinations, tier 2 for cards that hold
	   several links and are therefore NOT themselves click targets, and
	   nothing at all for prose. Tier 2 is deliberately quieter than tier
	   1: displacement reads as "click me", and on a card carrying three
	   different destinations that would be a lie. It borrows the wash and
	   the accent edge from .lr-list-item:hover instead, which is already
	   this site's idiom for "interactive, but the target is inside me".
	   ------------------------------------------------------------------ */
	@media (hover: hover) and (pointer: fine) {
		/* Card hover: the card shifts up-left and a solid accent slab appears
		   behind it. This is the flat-design counterpart to a drop shadow,		   it reads as displacement rather than depth, which is the only
		   hover idiom consistent with a hard 2px outline and no elevation.
		   It replaces a 3D perspective tilt, and unlike the conic glow it
		   replaces, nothing is painted over the card's own text. */
		.lr-surface-raised-white:hover,
		.lr-card-white:hover,
		.lr-project-card:hover {
			transform: translate(-4px, -4px);
			box-shadow: 6px 6px 0 0 var(--lr-color-accent);
			border-color: var(--lr-color-accent);
		}

		/* TIER 2: cards that CONTAIN links rather than being one.
		   The six "Who are you?" tiles on /contact/, the four on the
		   homepage, and the seven enquiry panels: every one of them holds
		   two to four separate destinations, so there is no single URL a
		   whole-card click could go to. They get a visible state with no
		   displacement, matching .lr-list-item:hover, and the inset edge
		   keeps it inside the flat idiom rather than reintroducing a
		   shadow. Grid membership is the discriminator because it is what
		   actually separates the two populations in the markup, and it
		   needs no edit to 2,000 pages.

		   The wash is mixed against the card's own background rather than
		   taken from --lr-color-accent-wash directly, so the tint follows
		   the surface it sits on: the enquiry panels have a transparent
		   background over the cream page, the tiles have a white one, and
		   a fixed #eef2f8 would be a hair wrong on one of them and wrong
		   outright on any future dark or on-ink context. */
		.lr-grid-equal > .lr-card:not(.lr-card-white):not(.lr-project-card):hover,
		.lr-card-grid > .lr-card:not(.lr-card-white):not(.lr-project-card):hover,
		.lr-enquiry-panel:hover {
			background-color: color-mix(in srgb, var(--lr-color-accent) 7%, var(--lr-color-bg-card));
			border-color: var(--lr-color-accent);
			box-shadow: inset 3px 0 0 0 var(--lr-color-accent);
		}
	}

	/* Keyboard parity for tier 2. The generic .lr-card:focus-within rule
	   below lifts the card 2px and changes nothing else visible, which is
	   the same empty promise the old hover rule made; these three get the
	   real state instead. Not inside the (hover: hover) query on purpose,
	   a touch user tabbing through with a keyboard still needs it. */
	.lr-grid-equal > .lr-card:not(.lr-card-white):not(.lr-project-card):focus-within,
	.lr-card-grid > .lr-card:not(.lr-card-white):not(.lr-project-card):focus-within,
	.lr-enquiry-panel:focus-within {
		/* Cancels the 2px lift the generic .lr-card:focus-within rule below
		   would otherwise add, so focus and hover show the same state and
		   tier 2 stays free of displacement in both. */
		transform: none;
		background-color: color-mix(in srgb, var(--lr-color-accent) 7%, var(--lr-color-bg-card));
		border-color: var(--lr-color-accent);
		box-shadow: inset 3px 0 0 0 var(--lr-color-accent);
	}

	.lr-card-white[data-active="true"],
	.lr-project-card[data-active="true"] {
		transform: translateY(var(--lr-lift-card-active)) scale(var(--lr-scale-card-active));
		box-shadow: var(--lr-shadow-active);
		z-index: 10;
	}

	/* A "magnetic directional hover" lived here: an invisible ::after
	   covering the right half of every project card at z-index 20, ABOVE
	   the card's own content, with pointer-events:auto, so that a
	   :has(::after:hover) selector could tilt the card toward the cursor.

	   That overlay sat on top of any link or button in the right half of
	   the card and swallowed the pointer, so it was a click-target bug as
	   well as decoration. Removed outright. */

	/* A rotating conic-gradient "glowing border" lived here. It worked by
	   giving the card `border: 1px solid transparent` plus
	   `background-clip: padding-box` and spinning a gradient behind it, so
	   the gradient showed through the transparent border ring.

	   Removed for two reasons. It was tuned for a 14px radius and a light
	   blue accent; at 40px with the indigo accent the wedge crossed the
	   card face and made the text hard to read. More importantly the
	   scaffolding was overriding the real card border,	   `border: 1px solid transparent` here beat `border: var(--lr-border-card)`
	   declared forty lines above, so every .lr-card-white on the site was
	   rendering with no visible outline at all. Deleting this restores the
	   outline the card was always supposed to have. */

	/* UPGRADE v1.4: Focus-Within Card Elevation
	   When a keyboard user focuses any interactive element inside a card,
	   the entire card lifts, giving keyboard navigators the same premium
	   feedback as mouse users. */
	@media (prefers-reduced-motion: no-preference) {
		.lr-card-white:focus-within,
		.lr-project-card:focus-within,
		.lr-card:focus-within {
			transform: translateY(var(--lr-lift-card-hover));
			box-shadow: var(--lr-shadow-hover-card);
			border-color: var(--lr-color-border-hover);
		}
	}
	
	/* =========================================================
		13. PORTFOLIO & PROJECTS
	========================================================= */
	.lr-project-card h3 {
		font-size: var(--lr-font-size-h3);
		margin-bottom: var(--lr-gap-tight);
	}
	
	.lr-project-meta {
		text-transform: none;
		letter-spacing: normal;
		font-size: var(--lr-font-size-caption);
		line-height: 1.5;
		margin-top: auto;
		color: var(--lr-color-text-muted);
		margin-bottom: var(--lr-gap-medium);
	}
	
	.lr-surface-raised > p,
	.lr-surface-sunken > p,
	.lr-project-card p.lr-desc {
		flex: 1 1 auto;
	}
	
	/* =========================================================
		14. IMAGES
	========================================================= */
	.lr-img-rounded {
		width: 100%;
		height: auto;
		object-fit: cover;
		border-radius: var(--lr-radius-medium);
		box-shadow: var(--lr-shadow-image);
		transition: 
		transform var(--lr-duration-slow) var(--lr-easing-mechanical),
		box-shadow var(--lr-duration-slow) var(--lr-easing-standard);
	}
	
	.lr-img-rounded,
	.lr-logo-item img,
	.lr-edu-logo img {
		user-select: none;
		-webkit-user-select: none;
		-webkit-user-drag: none;
	}
	
	@media (hover: hover) and (pointer: fine) {
		.lr-img-rounded:hover {
			transform: translateY(-4px);
			box-shadow: var(--lr-shadow-hover-image);
		}
	}
	
	.lr-caption {
		font-size: var(--lr-font-size-caption);
		line-height: 1.5;
		text-align: center;
		margin-top: 15px;
		color: var(--lr-color-text-muted);
	}
	
	/* =========================================================
		15. TAGS & PILLS
	========================================================= */
	.lr-tag,
	.lr-tag-group span,
	.pill,
	.tag-pill,
	.lr-pill-small {
		display: inline-block;
		background-color: var(--lr-color-bg-card);
		padding: 8px 16px;
		border-radius: var(--lr-radius-pill);
		font-size: var(--lr-font-size-tag);
		color: var(--lr-color-text-subtle);
		border: 1px solid var(--lr-color-border-subtle);
		text-decoration: none;
		font-weight: 600;
		letter-spacing: 0.01em;
		margin: 4px 6px 4px 0;
		transition: 
		transform var(--lr-duration-base) var(--lr-easing-mechanical),
		background-color var(--lr-duration-base) var(--lr-easing-standard),
		border-color var(--lr-duration-base) var(--lr-easing-standard),
		box-shadow var(--lr-duration-base) var(--lr-easing-standard),
		color var(--lr-duration-base) var(--lr-easing-standard);
	}
	
	.lr-tag-group {
		display: flex;
		flex-wrap: wrap;
		gap: var(--lr-gap-tight);
		margin-bottom: var(--lr-margin-bottom-tag);
	}
	
	@media (hover: hover) and (pointer: fine) {
		a.lr-tag:hover, 
		a.pill:hover,
		a.tag-pill:hover {
			background-color: var(--lr-color-accent-wash);
			border-color: var(--lr-color-accent);
			transform: translateY(-2px);
			color: var(--lr-color-accent);
			box-shadow: 0 4px 10px rgba(var(--lr-color-accent-rgb), 0.12);
		}

	}

	/* A light-reflection "shimmer" swept across each tag on hover. Removed:
	   the tag already answers a hover with a fill change, a border change
	   and a lift, and a sweep on top of that states the same thing a
	   fourth time. */

	a.lr-tag:active, 
	a.pill:active,
	a.tag-pill:active {
		transform: translateY(0) scale(0.96);
	}

	/* A "blob morph" ran the tag's border-radius through four asymmetric
	   corner sets on a 2s infinite alternate loop while hovered. With
	   --lr-radius-tag now a stadium, that animated a pill into a wobbling
	   blob. Removed. */
	
	/* =========================================================
		16. LISTS & ACADEMIC TYPOGRAPHY
	========================================================= */
	.lr-list-group {
		display: flex;
		flex-direction: column;
		gap: var(--lr-gap-small);
	}
	
	ul.lr-list-group,
	ol.lr-list-group {
		list-style: none;
		padding-left: 0;
		margin: 0;
	}
	
	.lr-list-item {
		position: relative !important;
		min-width: 0;
		max-width: 100%;
		overflow-x: auto !important;
		overflow-y: hidden !important;
		-webkit-overflow-scrolling: touch;
		overflow-wrap: break-word;
		word-wrap: break-word;
		list-style-type: none;
		background-color: transparent;
		padding: 8px 16px 8px 36px !important;
		font-size: var(--lr-font-size-small);
		color: var(--lr-color-text-main);
		border-left: 2px solid var(--lr-color-border-subtle);
		margin: 0;
		transition:
		border-color var(--lr-duration-base) var(--lr-easing-standard),
		background-color var(--lr-duration-base) var(--lr-easing-standard);
	}
	
	.lr-list-item a[aria-current="true"] {
		font-weight: 600;
		color: var(--lr-color-accent);
		text-decoration: none;
	}

	li.lr-list-item:has(a[aria-current="true"]) {
		border-left-color: var(--lr-color-accent);
		background-color: var(--lr-color-accent-wash, rgba(var(--lr-color-accent-rgb), 0.06));
	}

	li.lr-list-item {
		list-style: none;
	}
	li.lr-list-item::marker { content: ""; }
	
	.lr-list-item::before {
		content: "";
		position: absolute;
		left: 14px;
		top: calc(8px + 0.8em);
		transform: translateY(-50%);
		width: 6px;
		height: 6px;
		border-radius: 50%;
		background-color: var(--lr-color-text-muted);
		opacity: 0.7;
		transition:
		background-color var(--lr-duration-base) var(--lr-easing-smooth),
		opacity var(--lr-duration-base) var(--lr-easing-smooth),
		transform var(--lr-duration-base) var(--lr-easing-mechanical);
	}
	
	@media (hover: hover) and (pointer: fine) {
		.lr-list-item:hover {
			border-left-color: var(--lr-color-accent);
			background-color: var(--lr-color-accent-wash);
		}
		.lr-list-item:hover::before {
			background-color: var(--lr-color-accent);
			opacity: 1;
			transform: translateY(-50%) scale(1.4);
		}
	}
	
	.lr-index-list {
		margin: 0;
		padding-left: 0;
		list-style: none;
		line-height: 2;
		font-size: var(--lr-font-size-body);
	}
	
	.lr-index-list li {
		margin-bottom: 8px;
		list-style: none;
	}
	.lr-index-list li::marker { content: ""; }
	
	.lr-index-list a {
		display: inline-block;
		text-decoration: none;
		color: var(--lr-color-text-heading);
		border-bottom: 1px solid rgba(0, 0, 0, 0.08);
		transition:
		transform var(--lr-duration-base) var(--lr-easing-mechanical),
		color var(--lr-duration-base) var(--lr-easing-standard),
		border-color var(--lr-duration-base) var(--lr-easing-standard);
	}
	
	@media (hover: hover) and (pointer: fine) {
		.lr-index-list a:hover {
			color: var(--lr-color-accent);
			border-color: var(--lr-color-accent);
			transform: translateY(-1px);
		}
	}
	
	.lr-index-list a:active {
		transform: scale(0.98);
	}
	
	.lr-analytic {
		max-width: 820px;
		margin: 0 0 32px 0;
		font-size: 16px;
		line-height: 1.8;
		opacity: 0.9;
		color: var(--lr-color-text-main);
	}
	
	details.lr-archive {
		background: var(--lr-color-bg-light);
		border: 1px solid var(--lr-color-border-subtle);
		border-radius: var(--lr-radius-medium);
		padding: 24px;
		transition: background-color var(--lr-duration-base) var(--lr-easing-standard);
	}
	
	details.lr-archive summary {
		cursor: pointer;
		font-weight: 700;
		font-size: var(--lr-font-size-h3);
		color: var(--lr-color-text-heading);
		outline: none;
		user-select: none;
	}
	
	details.lr-archive summary:focus-visible {
		box-shadow: var(--lr-shadow-focus-ring);
		border-radius: 4px;
	}
	
	.lr-glossary dt,
	h2[id], 
	h3[id] {
		scroll-margin-top: 140px;
	}
	
	/* =========================================================
		17. BUTTON LAYOUT UTILITIES
	========================================================= */
	.lr-button-group-center { justify-content: center; }
	/* =========================================================
		18. PEDAGOGICAL CALLOUTS & INTERACTION LOGIC
	========================================================= */
	
	/* --- A. The Base Panel Primitive --- */
	.lr-surface-sunken,
	.lr-panel-white,
	.lr-panel-light {
		position: relative;
		padding: clamp(20px, 4vw, 26px) clamp(20px, 5vw, 28px);
		background-color: var(--lr-color-bg-light);
		border: var(--lr-border-card);
		border-radius: var(--lr-radius-medium, 10px);
		margin: clamp(20px, 4vw, 32px) 0;
		box-shadow: none;
		transition:
			transform var(--lr-transition-pop),
			border-color var(--lr-duration-moderate) var(--lr-easing-standard);
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
	}
	
	.lr-panel-white > *:last-child {
		margin-bottom: 0 !important;
	}
	
	/* Plain panels are informational (non-interactive) asides, so they get a
	   quiet non-motion hover response, a slightly deeper shadow and a faint
	   accent border, rather than a button-like lift that would falsely imply
	   clickability. The accent-edge callout keeps its own bespoke hover
	   (defined unlayered at the foot of the file) and is excluded here. */
	@media (hover: hover) and (pointer: fine) {
		.lr-surface-sunken:not(.lr-panel-accent-edge):hover,
		.lr-panel-white:not(.lr-panel-accent-edge):hover,
		.lr-panel-light:not(.lr-panel-accent-edge):hover {
			border-color: var(--lr-color-border-hover);
		}
	}
	
	/* Pedagogical callouts (.lr-panel-accent-edge): unlayered overrides at file bottom, clean slate borders / hover / shadow */
	
	/* =========================================================
		19. RESEARCH AFFILIATIONS
	========================================================= */
	.lr-affiliations-card,
	.lr-affiliations-wrapper {
		padding: clamp(20px, 4vw, 40px) 30px;
		background: var(--lr-callout-bg-color);
		border-radius: var(--lr-radius-large);
		box-shadow: var(--lr-shadow-rest);
		text-align: center;
		margin: 30px auto 0;
		border: var(--lr-border-card);
	}
	
	.lr-affiliations-wrapper > p:first-child {
		font-size: var(--lr-font-size-caption);
		text-transform: none;
		letter-spacing: normal;
		font-weight: 600;
		color: var(--lr-color-text-muted);
		opacity: 0.7;
		margin-bottom: 30px;
	}
	
	.lr-logo-grid {
		display: flex;
		justify-content: center;
		align-items: center;
		gap: 60px;
		flex-wrap: wrap;
		padding: 10px 0;
	}
	
	.lr-affiliations-wrapper .lr-logo-grid {
		flex-direction: row;
		flex-wrap: nowrap;
		gap: 30px;
	}
	
	.lr-logo-item { line-height: 0; }
	
	.lr-logo-item img {
		max-height: 80px;
		height: auto;
		width: auto;
		display: block;
		object-fit: contain;
		filter: grayscale(100%) opacity(0.5);
		border-radius: 0;
		box-shadow: none;
		transition: 
		filter var(--lr-duration-slower) var(--lr-easing-mechanical), 
		opacity var(--lr-duration-slower) var(--lr-easing-mechanical), 
		transform var(--lr-duration-slower) var(--lr-easing-mechanical);
	}
	
	.lr-affiliations-wrapper .lr-logo-item img { max-height: 50px; }
	
	@media (hover: hover) and (pointer: fine) {
		.lr-logo-item:hover img {
			filter: grayscale(0%) opacity(1);
			transform: scale(1.08);
		}
	}
	
	@media (max-width: 921px) {
		.lr-logo-grid { gap: 50px; }
		.lr-logo-item img { max-height: 70px; }
		.lr-affiliations-wrapper .lr-logo-item img { max-height: 45px; }
	}
	
	@media (max-width: 540px) {
		.lr-logo-grid:not(.lr-affiliations-wrapper .lr-logo-grid) { 
			flex-direction: column; 
			gap: 40px; 
		}
		.lr-logo-item img { max-height: 65px; }
	}
/* =========================================================
		20. TECHNICAL TOOLKIT
	========================================================= */
	.lr-tools-grid {
		display: flex;
		flex-wrap: wrap;
		justify-content: center;
		align-items: center;
		gap: 30px;
		padding: 10px 0;
	}
	
	/* Added .lr-tool-icon and svg to catch shortcode outputs */
	.lr-tools-grid .lr-logo-item img,
	.lr-tools-grid .lr-logo-item svg,
	.lr-tools-grid .lr-logo-item .lr-tool-icon {
		max-height: 36px;
		height: 36px;
		width: auto;
		min-width: 36px; /* Prevents SVGs from collapsing to 0 width */
		object-fit: contain; /* Prevents stretching if min-width triggers */
		display: block;
		border-radius: 0;
		box-shadow: none;
		filter: grayscale(100%) opacity(0.5);
		transition: 
			filter var(--lr-duration-slow) var(--lr-easing-mechanical), 
			opacity var(--lr-duration-slow) var(--lr-easing-mechanical), 
			transform var(--lr-duration-slow) var(--lr-easing-mechanical);
	}
	
	@media (max-width: 600px) {
		.lr-tools-grid { gap: 20px; }
		.lr-tools-grid .lr-logo-item img,
		.lr-tools-grid .lr-logo-item svg,
		.lr-tools-grid .lr-logo-item .lr-tool-icon { 
			max-height: 28px; 
			height: 28px; 
			min-width: 28px; 
		}
	}
	
	@media (hover: hover) and (pointer: fine) {
		.lr-tools-grid .lr-logo-item:hover img,
		.lr-tools-grid .lr-logo-item:hover svg,
		.lr-tools-grid .lr-logo-item:hover .lr-tool-icon {
			filter: grayscale(0%) opacity(1);
			transform: scale(1.12);
		}
	}
	
	/* =========================================================
		21. PROJECT ARCHITECTURE
	========================================================= */
	.lr-section-heading {
		margin-top: clamp(60px, 8vw, 100px);
		margin-bottom: clamp(30px, 4vw, 50px);
		text-align: center;
	}
	
	/* Section headings inside cards inherit card rhythm, not page-level splits. */
	.lr-card .lr-section-heading,
	.lr-card-white .lr-section-heading,
	.lr-project-card .lr-section-heading,
	.lr-at-a-glance .lr-section-heading {
		margin-top: 0;
		margin-bottom: var(--lr-gap-medium);
		text-align: inherit;
	}
	
	.lr-explore-portfolio .wp-block-column > .lr-project-card {
		flex: 1;
	}
	
	.lr-project-card .lr-tag-group { margin-bottom: var(--lr-gap-medium); }
	.lr-project-card .lr-list-group { margin-top: var(--lr-gap-medium); }
	
	/* =========================================================
		21b. HOVER-ARROW CALL TO ACTION

		.lr-hover-arrow ships in page content ("View all publications", and
		similar forward links) but had no CSS at all, so it rendered as a
		plain link, the third content hook found unstyled, after
		.lr-editorial-figure and .lr-mono-meta.

		The brief (Greenhill analysis §D) specifies this interaction: a
		trailing arrow that slides on hover. The arrow is drawn in the
		pseudo-element rather than written into the content so it cannot be
		selected, copied, or read out as part of the link text.
	========================================================= */
	.lr-hover-arrow {
		display: inline-flex;
		align-items: baseline;
		gap: 0.4em;
	}

	.lr-hover-arrow::after {
		content: "\2192"; /* → */
		display: inline-block;
		transition: transform var(--lr-duration-base) var(--lr-easing-standard);
	}

	@media (hover: hover) and (pointer: fine) {
		.lr-hover-arrow:hover::after,
		.lr-hover-arrow:focus-visible::after {
			transform: translateX(4px);
		}
	}

	@media (prefers-reduced-motion: reduce) {
		.lr-hover-arrow::after {
			transition: none;
		}
		/* Movement is the whole affordance here, so with motion suppressed the
		   arrow simply stays put rather than jumping to the offset position. */
		.lr-hover-arrow:hover::after,
		.lr-hover-arrow:focus-visible::after {
			transform: none;
		}
	}

	.lr-back-to-top {
		font-size: var(--lr-font-size-small);
		margin-top: 24px;
		display: inline-block;
		color: var(--lr-color-text-muted);
		text-decoration: none;
		transition:
		color var(--lr-duration-moderate) var(--lr-easing-standard),
		transform var(--lr-duration-base) var(--lr-easing-mechanical);
	}
	
	@media (hover: hover) and (pointer: fine) {
		.lr-back-to-top:hover {
			color: var(--lr-color-accent);
			transform: translateY(-1px);
		}
	}
	
	.lr-mb-40 { margin-bottom: var(--lr-gap-large); }
	.lr-mt-40 { margin-top: var(--lr-gap-large); }
	.lr-mb-24 { margin-bottom: var(--lr-gap-medium); }

	/* Standalone spacer blocks between cards (lr-content.php), not for use on .lr-card */
	.lr-spacer-md:not(.lr-card) {
		display: block;
		height: 0;
		margin: 0;
		padding: 0;
		border: 0;
	}
	.lr-spacer-md:not(.lr-card)::before {
		content: "";
		display: block;
		height: var(--lr-gap-medium);
	}
	.lr-opacity-muted {
		/* No opacity, despite the class name. Same fault as the figcaption
		   rules in §9/§34: the muted token is tuned to clear WCAG AA on its
		   own (5.1:1 on white), and a 0.85 layer blends it down to #788597,		   3.76:1, a contrast failure on /engineering/ and the five
		   fluid-mechanics pages that use it (2026-08-25). The class earns its
		   name from the muted colour and the smaller size, which is what it
		   was always doing visually. */
		color: var(--lr-color-text-muted);
		font-size: var(--lr-font-size-small);
		line-height: var(--lr-line-height-small);
		margin-top: var(--lr-gap-small);
		margin-bottom: 0;
	}
	
	.lr-tag-container-wide {
		max-width: 1100px;
		margin-left: auto;
		margin-right: auto;
	}
	
	/* =========================================================
		22. HERO SECTION
	========================================================= */
	/* Same Astra-unlayered-beats-layered reason as the primitives heading
	   block: page hero titles need important to reach the display scale. */
	.lr-hero-heading {
		font-size: var(--lr-font-size-hero) !important;
		font-weight: 600 !important;
		letter-spacing: var(--lr-letter-spacing-tight);
		line-height: 1.1 !important;
		margin-bottom: var(--lr-gap-medium);
	}
	.lr-hero-subheading {
		font-size: var(--lr-font-size-lead);
		line-height: var(--lr-line-height-body);
		color: var(--lr-color-text-main);
		opacity: 0.9;
		margin-bottom: var(--lr-gap-medium);
	}
	
	/* =========================================================
		23. EDUCATION & CV LISTS
	========================================================= */
	.lr-cv-row {
		border-bottom: 1px solid rgba(0, 0, 0, 0.08);
		padding-bottom: var(--lr-gap-large);
		margin-bottom: var(--lr-gap-large);
	}
	
	.lr-cv-row:last-child {
		border-bottom: none;
		padding-bottom: 0;
		margin-bottom: 0;
	}
	
	.lr-edu-logo img {
		max-height: 35px;
		width: auto;
		display: block;
		margin: 12px 0 0 0;
		border-radius: 0;
		box-shadow: none;
		border: none;
		filter: grayscale(100%) opacity(0.6);
		transition: 
		filter var(--lr-duration-slow) var(--lr-easing-mechanical),
		opacity var(--lr-duration-slow) var(--lr-easing-mechanical),
		transform var(--lr-duration-slow) var(--lr-easing-mechanical);
	}
	
	@media (hover: hover) and (pointer: fine) {
		.lr-edu-logo:hover img { 
			filter: grayscale(0%) opacity(1);
			transform: scale(1.08);
		}
	}
	
	.lr-cv-list { margin: 15px 0 15px 20px; padding: 0; }
	.lr-cv-list li {
		font-size: 14px;
		line-height: 1.6;
		color: var(--lr-color-text-main);
		margin-bottom: 8px;
	}
	
	@media (max-width: 768px) {
		.lr-edu-logo img { margin: 15px auto 20px auto; }
		.lr-cv-list { text-align: left; display: inline-block; }
	}
	
	/* =========================================================
		24. PUBLICATIONS & CROSS-REFERENCES
	========================================================= */
	.lr-ms {
		display: inline-block;
		background-color: var(--lr-color-bg-light);
		padding: 8px 14px;
		border-radius: var(--lr-radius-tag);
		font-size: var(--lr-font-size-tag);
		color: var(--lr-color-text-subtle);
		font-weight: 600;
		border: 1px solid var(--lr-color-border-subtle);
		transition: 
		transform var(--lr-duration-base) var(--lr-easing-mechanical), 
		background-color var(--lr-duration-base) var(--lr-easing-standard), 
		border-color var(--lr-duration-base) var(--lr-easing-standard), 
		box-shadow var(--lr-duration-base) var(--lr-easing-standard),
		color var(--lr-duration-base) var(--lr-easing-standard);
	}
	
	.lr-doi {
		display: inline-flex;
		align-items: center;
		font-size: 13px;
		background: var(--lr-color-bg-card);
		padding: 4px 10px;
		border-radius: var(--lr-radius-small);
		color: var(--lr-color-link);
		border: var(--lr-border-card);
		word-break: break-all;
		text-decoration: none;
		font-weight: 500;
		transition: 
		transform var(--lr-duration-base) var(--lr-easing-mechanical),
		background-color var(--lr-duration-base) var(--lr-easing-standard),
		color var(--lr-duration-base) var(--lr-easing-standard),
		border-color var(--lr-duration-base) var(--lr-easing-standard),
		box-shadow var(--lr-duration-base) var(--lr-easing-standard);
	}
	
	.lr-doi:active { transform: scale(0.96); }
	
	a.lr-xref { 
		border-bottom: 1px dashed var(--lr-color-accent); 
		text-decoration: none;
		transition: 
		background-color var(--lr-duration-base) var(--lr-easing-mechanical),
		border-color var(--lr-duration-base) var(--lr-easing-mechanical);
	}
	
	@media (hover: hover) and (pointer: fine) {
		.lr-ms:hover {
			background-color: var(--lr-color-accent-wash);
			border-color: var(--lr-color-accent);
			color: var(--lr-color-accent);
			transform: translateY(-1px);
			box-shadow: 0 4px 10px rgba(var(--lr-color-accent-rgb), 0.12);
		}
		.lr-doi:hover {
			background-color: var(--lr-color-accent);
			color: #ffffff;
			border-color: var(--lr-color-accent);
			transform: translateY(-1px);
			box-shadow: 0 4px 8px rgba(var(--lr-color-accent-rgb), 0.12);
		}
		a.lr-xref:hover {
			background-color: rgba(var(--lr-color-accent-rgb), 0.06);
			border-bottom: 1px solid var(--lr-color-accent);
		}
	}
	
	/* =========================================================
		25. FORM CONTROLS
	========================================================= */
	input[type="text"],
	input[type="email"],
	input[type="url"],
	input[type="password"],
	input[type="search"],
	input[type="number"],
	input[type="tel"],
	textarea,
	select {
		background-color: var(--lr-color-bg-light);
		border: 1px solid var(--lr-color-border-subtle);
		border-radius: var(--lr-radius-medium);
		padding: 14px 18px;
		color: var(--lr-color-text-main);
		font-family: inherit;
		font-size: var(--lr-font-size-body);
		width: 100%;
		max-width: 100%;
		box-sizing: border-box;
		transition: 
		border-color var(--lr-duration-moderate) var(--lr-easing-mechanical),
		box-shadow var(--lr-duration-moderate) var(--lr-easing-mechanical),
		background-color var(--lr-duration-moderate) var(--lr-easing-standard);
	}
	
	@media (hover: hover) and (pointer: fine) {
		input[type="text"]:hover,
		input[type="email"]:hover,
		textarea:hover,
		select:hover {
			border-color: rgba(var(--lr-color-accent-rgb), 0.3);
		}
	}
	
	/* Contact Form 7 and Astra both ship their own unlayered, non-important
	   `select:focus, textarea:focus, .wpcf7-form-control:focus` rule with a
	   hardcoded #046BD2 border. Per the cascade layers spec, ANY unlayered
	   normal-priority declaration beats ALL layered normal-priority
	   declarations regardless of source order or specificity, so without
	   !important here, every form field site-wide (including the contact
	   form) shows that off-brand blue on focus instead of the accent token.
	   :focus is included alongside :focus-visible for the same reason:
	   CF7/Astra's own rule matches plain :focus. */
	input[type="text"]:focus-visible,
	input[type="text"]:focus,
	input[type="email"]:focus-visible,
	input[type="email"]:focus,
	textarea:focus-visible,
	textarea:focus,
	select:focus-visible,
	select:focus,
	.wpcf7-form-control:focus {
		outline: none !important;
		background-color: #ffffff;
		border-color: var(--lr-color-accent) !important;
		/* CF7/Astra's own rule also sets box-shadow: none, which would
		   otherwise erase this focus glow the same way it erases the
		   border color. */
		box-shadow: 0 8px 24px rgba(var(--lr-color-accent-rgb), 0.08), 0 2px 6px rgba(0, 0, 0, 0.02) !important;
		transform: translateY(-1px);
	}

	textarea {
		resize: vertical;
		min-height: 120px;
		line-height: 1.6;
	}
	
	input, textarea {
		caret-color: var(--lr-color-accent);
	}
	
	::placeholder {
		color: var(--lr-color-text-muted);
		opacity: 0.6;
		font-weight: 400;
	}
	
	/* ============================================================
		25b. MOBILE-INPUT REFINEMENTS, coarse-pointer only
		
		1. font-size: 16px on form controls
		   iOS Safari auto-zooms any focused form control whose
		   font-size is below 16px, then snaps back on blur. Forcing
		   16px on touch devices silences this jarring zoom-in /
		   zoom-out cycle. Desktop UI is unaffected.
		
		2. touch-action: manipulation on tap-targets
		   Opts the element out of the legacy double-tap-to-zoom
		   gesture so taps register instantly on iOS Safari. The
		   page itself remains pinch-zoomable. Applied only to
		   demonstrably interactive controls (buttons, pills,
		   utility buttons, summary toggles); deliberately NOT
		   applied to general anchors in body copy where users may
		   still expect double-tap-to-zoom around prose.
	============================================================ */
	@media (pointer: coarse) {
		input[type="text"],
		input[type="email"],
		input[type="url"],
		input[type="password"],
		input[type="search"],
		input[type="number"],
		input[type="tel"],
		textarea,
		select {
			font-size: 16px;
		}
		
		.wp-element-button,
		.wp-block-button__link,
		.lr-tag,
		.lr-tag-group span,
		.pill,
		.tag-pill,
		.lr-pill-small,
		.lr-ms,
		.lr-doi,
		.lr-back-to-top,
		#ast-scroll-top,
		details summary {
			touch-action: manipulation;
		}
	}
	
	/* =========================================================
		26. CODE & TECHNICAL SNIPPETS
	========================================================= */
	code, kbd, samp, pre {
		font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
		font-size: 0.9em;
	}
	
	code {
		background-color: var(--lr-color-bg-light);
		color: var(--lr-color-code);
		padding: 2px 6px;
		border-radius: 4px;
		border: 1px solid var(--lr-color-border-subtle);
		word-break: break-word;
	}
	
	pre {
		background-color: #1f374e;
		color: #fffdf6;
		padding: 24px;
		border-radius: var(--lr-radius-medium);
		overflow-x: auto;
		box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.2);
		border: 1px solid rgba(255, 255, 255, 0.1);
	}
	
	pre code {
		background-color: transparent;
		color: inherit;
		padding: 0;
		border: none;
	}
	
	/* =========================================================
		27. TABLES
	========================================================= */
	table {
		width: 100%;
		border-collapse: collapse;
		table-layout: auto;
		min-width: 480px;
	}
	
	/* Table headers speak the same blueprint language as the [01] markers and
	   section eyebrows (brief §3C): monospace, uppercase, widely tracked. On
	   an engineering site the data tables are content, not furniture, this
	   ties them into the technical-HUD register. Astra's unlayered th rules
	   set only padding/border, so these typography declarations win from the
	   components layer without needing !important. */
	th {
		background-color: var(--lr-color-bg-light);
		color: var(--lr-color-text-heading);
		font-family: var(--lr-font-mono);
		font-weight: 500;
		text-align: left;
		padding: 16px 20px;
		text-transform: uppercase;
		letter-spacing: var(--lr-letter-spacing-mono);
		font-size: 11px;
		border-bottom: 2px solid var(--lr-color-accent);
		font-variant-numeric: tabular-nums;
	}
	
	td {
		padding: 14px 20px;
		border-bottom: 1px solid var(--lr-color-border-subtle);
		color: var(--lr-color-text-main);
		font-size: 14px;
		word-break: normal;
		overflow-wrap: break-word;
		font-variant-numeric: tabular-nums;
		transition: background-color var(--lr-duration-base) var(--lr-easing-standard);
	}
	
	/* Quiet zebra striping aids row-tracking across wide technical tables.
	   Kept lighter than the row-hover tint so hover stays distinguishable. */
	tbody tr:nth-child(even) td {
		background-color: rgba(var(--lr-color-accent-rgb), 0.022);
	}
	
	@media (hover: hover) and (pointer: fine) {
		tr:hover td {
			background-color: rgba(var(--lr-color-accent-rgb), 0.06);
			color: var(--lr-color-text-heading);
		}
	}
	
	/* =========================================================
		27b. EMPTY & LOADING STATES
	========================================================= */
	.lr-empty-state {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		gap: var(--lr-gap-small);
		padding: var(--lr-padding-card);
		text-align: center;
		color: var(--lr-color-text-muted);
		font-size: var(--lr-font-size-small);
		line-height: var(--lr-line-height-small);
		border: 2px dashed var(--lr-color-border-card);
		border-radius: var(--lr-radius-medium);
		background: var(--lr-color-surface-muted);
	}
	
	.lr-loading {
		position: relative;
		pointer-events: none;
		color: transparent;
		min-height: 1.25em;
		border-radius: var(--lr-radius-small);
		background: linear-gradient(
			90deg,
			var(--lr-color-surface-muted) 0%,
			var(--lr-color-bg-light) 50%,
			var(--lr-color-surface-muted) 100%
		);
		background-size: 200% 100%;
		animation: lr-loading-shimmer 1.2s var(--lr-easing-smooth) infinite;
	}
	
	@keyframes lr-loading-shimmer {
		0% { background-position: 100% 0; }
		100% { background-position: -100% 0; }
	}
	
	@media (prefers-reduced-motion: reduce) {
		.lr-loading {
			animation: none;
			background: var(--lr-color-surface-muted);
			color: var(--lr-color-text-muted);
		}
	}
	
	/* Booking page, align shell padding with the rest of the platform. */
	body.lr-page-booking .lr-section-full {
		padding-bottom: var(--lr-padding-section);
	}
	
	/* =========================================================
		28. SECTION VISUAL PROPERTIES
	========================================================= */
	/* Bands alternate cream / blue down the page.
	
	   This is written as a general-sibling chain rather than
	   :nth-of-type(even) on purpose. The published pages interleave
	   .lr-section-full with plain wrapper DIVs inside .entry-content, on
	   /teaching/ the run of children is NAV, SECTION, DIV*, DIV, DIV…, so
	   :nth-of-type counts the wrappers too and the alternation lands on the
	   wrong elements. Each additional `~ .lr-section-full` matches "at least
	   N of my kind precede me", and because a later rule of equal
	   specificity wins, the last matching link decides the colour. Chained
	   to eight, which is more bands than any current page carries; a ninth
	   simply repeats the eighth's colour rather than breaking. */
	.lr-section-full { background-color: var(--lr-color-band-cream); }
	.lr-section-full ~ .lr-section-full { background-color: var(--lr-color-band-blue); }
	.lr-section-full ~ .lr-section-full ~ .lr-section-full { background-color: var(--lr-color-band-cream); }
	.lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full { background-color: var(--lr-color-band-blue); }
	.lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full { background-color: var(--lr-color-band-cream); }
	.lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full { background-color: var(--lr-color-band-blue); }
	.lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full { background-color: var(--lr-color-band-cream); }
	.lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full ~ .lr-section-full { background-color: var(--lr-color-band-blue); }

	/* A rule here once painted `.lr-section-full:has(.lr-hero-heading)` in
	   the blue band, on the reasoning that every interior page carries
	   exactly one .lr-section-full and that it wraps the masthead, so the
	   sibling chain above, which only starts alternating at the SECOND
	   band, never fires and the blue ground was going unused.

	   The premise was wrong and the rule is removed. The DOM was checked on
	   one page, where .lr-section-full sits among eleven siblings inside
	   .entry-content and does look like a masthead wrapper. What was never
	   measured is how TALL it is. On most templates it wraps nearly the
	   whole article: measured band height as a share of page height was 69%
	   on /teaching/, 76% on /about/, 80% on /teaching/esat/, 85% on
	   /projects/, 89% on /viscous-flow-fluid-mechanics/ and 97% on
	   /teaching/esat/paper-0001/maths/. That does not read as a band against
	   a cream page; it makes blue the page ground and cream a thin margin,
	   which inverts the intended relationship.

	   Real alternation needs a per-section hook in the page markup, because
	   the templates do not give CSS a section boundary to attach to. The
	   .lr-band-cream / .lr-band-blue classes below exist for exactly that
	   and can be added to individual sections in WordPress. Until then the
	   page grounds are cream body + white cards, which is a two-tone page
	   already, just not a three-tone one. */

	/* Explicit opt-outs, for pages that need to pin a band regardless of
	   where it falls in the run. These come last so they beat every link in
	   the chain above. */
	.lr-section-full.lr-band-cream { background-color: var(--lr-color-band-cream); }
	.lr-section-full.lr-band-blue { background-color: var(--lr-color-band-blue); }

	/* A faint 32px drafting grid was painted over every band (brief §3C,
	   blueprint aesthetic). Removed: the band now carries a real colour
	   change, and a texture on top of that is a second statement of the
	   same boundary. Neither reference design uses surface texture. */
	
	/* =========================================================
		29. WORKED SOLUTIONS DETAILS AND SUMMARY
	========================================================= */
	details.lr-details-solution {
		margin-top: 16px;
		border: 1px solid var(--lr-color-border-subtle);
		border-radius: var(--lr-radius-medium);
		background-color: #ffffff;
		overflow: hidden;
		transition: box-shadow var(--lr-transition-base);
	}
	details.lr-details-solution:hover {
		box-shadow: var(--lr-shadow-rest);
	}
	details.lr-details-solution summary {
		display: flex;
		justify-content: space-between;
		align-items: center;
		padding: 16px 20px;
		font-weight: 700;
		font-size: 1.05rem;
		color: var(--lr-color-accent);
		background-color: var(--lr-color-bg-light);
		cursor: pointer;
		outline: none;
		list-style: none;
		border: 1px solid transparent;
		transition: background-color var(--lr-transition-base), color var(--lr-transition-base), border-color var(--lr-transition-base);
	}
	details.lr-details-solution summary::-webkit-details-marker {
		display: none;
	}
	details.lr-details-solution summary::after {
		content: "↓";
		font-weight: bold;
		font-size: 1.1rem;
		color: var(--lr-color-accent);
		transition: transform var(--lr-transition-base);
	}
	details.lr-details-solution[open] summary::after {
		transform: rotate(180deg);
	}
	details.lr-details-solution[open] summary {
		background-color: var(--lr-color-bg-light);
		color: var(--lr-color-accent);
	}
	/* The white label here is only legible because the accent fill lands
	   with it, and that fill was losing the cascade. Every published
	   <summary> carries an inline `background: var(--lr-color-background-subtle)`,
	   and that token was never declared, an undefined var() invalidates the
	   whole declaration, so the shorthand computed to `transparent` and, as
	   an inline style, still outranked this rule's background-color. Result:
	   white text on the white <details> panel the moment the pointer touched
	   a solution toggle. The token is now declared in §tokens, but that only
	   fixes the resting state; !important is what stops any inline
	   background on the summary from stripping the fill out from under the
	   white label again.

	   :focus-visible rather than :focus, too. A summary keeps focus after a
	   mouse click, so with :focus every solution the reader opened stayed
	   inverted while they scrolled the page, the reported symptom. Keyboard
	   focus still gets the fill, and the ring rule below still applies. */
	details.lr-details-solution summary:hover,
	details.lr-details-solution summary:focus-visible {
		background-color: var(--lr-color-accent) !important;
		color: #ffffff;
	}
	details.lr-details-solution summary:hover::after,
	details.lr-details-solution summary:focus-visible::after {
		color: #ffffff;
	}
	details.lr-details-solution summary:focus-visible {
		border-color: var(--lr-color-accent-hover);
		box-shadow: var(--lr-shadow-focus-ring) !important;
	}
	details.lr-details-solution .lr-details-content {
		padding: 20px;
		border-top: 1px solid var(--lr-color-border-subtle);
		background-color: var(--lr-color-bg-card);
	}
   
   
/* LUCAS REY CURRICULUM STYLES */

/* Tooltips, interactive labs, curriculum print (migrated from Titan inline blocks) */
.lr-tooltip {
	border-bottom: 1px dotted var(--lr-color-accent, #002147);
	cursor: help;
	text-decoration: none;
}

#lr-titan-tooltip-host {
	position: fixed;
	z-index: 100000;
	max-width: min(420px, 92vw);
	padding: 10px 14px;
	/* Ink system, not a standalone navy: the host previously sat on #1a1f2e,
	   a literal from before the ink frame was unified, the
	   same drift the frosted header carried. On-ink 15.77:1. */
	background: var(--lr-color-ink, #002147);
	color: var(--lr-color-on-ink, #fffdf6);
	font-size: 0.82rem;
	line-height: 1.45;
	border: 1px solid rgba(255, 255, 255, 0.14);
	border-radius: var(--lr-radius-small, 13px);
	box-shadow: 0 2px 6px rgba(20, 33, 61, 0.18), 0 12px 32px -8px rgba(20, 33, 61, 0.35);
	pointer-events: none;
	display: none;
}

#lr-titan-tooltip-host.lr-titan-tip-visible {
	display: block;
}

.lr-titan-lab {
	margin: 20px 0;
	padding: 16px 18px;
	border: 1px solid var(--lr-color-border-subtle, #dce6ec);
	border-radius: 10px;
	background: var(--lr-color-bg-light, #eaf6fc);
}

.lr-titan-lab h4 {
	margin: 0 0 12px;
	font-size: 1rem;
	color: var(--lr-color-accent, #002147);
}

.lr-titan-lab label {
	display: block;
	font-size: 0.82rem;
	margin: 8px 0 4px;
}

.lr-titan-lab input {
	width: 100%;
	max-width: 280px;
	padding: 8px 10px;
	border: 1px solid var(--lr-color-border-subtle, #dce6ec);
	border-radius: 6px;
	font-size: 0.95rem;
}

.lr-titan-lab-out {
	margin-top: 14px;
	padding: 14px 16px;
	border-radius: 8px;
	background: #ffffff;
	border: 2px solid var(--lr-color-accent, #002147);
	font-size: 1.35rem;
	font-weight: 700;
	text-align: center;
	font-variant-numeric: tabular-nums;
}

.lr-titan-lab-note {
	font-size: 0.78rem;
	color: #5a6270;
	margin-top: 10px;
}

/* Fluid mechanics curriculum dashboard (index hub) - Publications-style lr-explore-portfolio + lr-card-white */

.lr-curriculum-hub * {
	box-sizing: border-box;
}

body.lr-curriculum-hub {
	margin: 0;
	font-family: inherit;
	color: var(--lr-color-text-main);
	background: var(--lr-color-bg-section);
	line-height: var(--lr-line-height-body);
}

.lr-curriculum-hub .lr-section-full {
	padding-top: clamp(2rem, 5vw, 3rem);
	padding-bottom: clamp(3rem, 8vw, 4.5rem);
}

.lr-curriculum-hub .lr-curriculum-search-section {
	width: 100%;
	max-width: var(--lr-max-width-card);
	margin-inline: auto;
}

.lr-curriculum-hub .lr-curriculum-search-section .lr-global-search-outer {
	max-width: 36rem;
	margin-inline: auto;
	margin-bottom: 0;
}

.lr-curriculum-hub .lr-curriculum-explore-heading {
	margin-bottom: var(--lr-padding-card);
	font-size: clamp(1.25rem, 3vw, 2rem);
	line-height: var(--lr-line-height-heading);
}

.lr-curriculum-hub .lr-curriculum-roadmap-desc {
	margin: 0 auto var(--lr-gap-card);
	max-width: 42rem;
}

/* Full-card links: same white card shell as Publications explore grid; no faux link underlines */
.lr-curriculum-hub a.lr-curriculum-dashboard-card,
.lr-curriculum-hub a.lr-curriculum-dashboard-card:hover,
.lr-curriculum-hub a.lr-curriculum-dashboard-card:visited {
	text-decoration: none !important;
	border-bottom: none !important;
	box-shadow: none !important;
	color: inherit !important;
	background-color: #ffffff !important;
}

.lr-curriculum-hub a.lr-curriculum-dashboard-card:focus-visible {
	outline: 2px solid var(--lr-color-accent);
	outline-offset: 3px;
}

.lr-curriculum-hub .lr-curriculum-module-label {
	display: block;
	font-size: 11px;
	color: var(--lr-color-accent);
	text-transform: none;
	letter-spacing: normal;
	font-weight: 600;
	margin-bottom: 10px;
}

.lr-curriculum-hub .lr-curriculum-dashboard-card h3.wp-block-heading {
	margin-bottom: 10px;
	text-transform: none !important;
}

.lr-curriculum-hub .lr-curriculum-dashboard-card .has-small-font-size {
	line-height: 1.7;
	margin-bottom: 0;
}

.lr-curriculum-hub .lr-curriculum-dashboard-cta {
	margin-top: auto;
	padding-top: var(--lr-gap-card);
	font-size: var(--lr-font-size-caption);
	font-weight: 600;
	color: var(--lr-color-accent);
}

/* Global curriculum search (dashboard hub) */
.lr-visually-hidden,
.lr-sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

.lr-global-search-outer {
	max-width: 1100px;
	margin: 0 auto 1.5rem;
	padding: 0 var(--lr-padding-inline-shell);
	position: relative;
	z-index: 20;
}

.lr-global-search {
	position: relative;
	max-width: 520px;
	margin: 0 auto;
	text-align: left;
}

.lr-global-search-input {
	width: 100%;
	padding: 12px 16px;
	border-radius: var(--lr-radius-medium);
	border: 1px solid var(--lr-color-border-subtle);
	font-size: 1rem;
	background: var(--lr-callout-bg-color);
	box-shadow: 0 4px 18px rgba(13, 17, 23, 0.06);
	transition:
		border-color var(--lr-transition-base),
		box-shadow var(--lr-transition-base);
}

.lr-global-search-input:focus-visible {
	outline: none;
	border-color: var(--lr-color-accent, #002147);
	box-shadow: var(--lr-shadow-focus-ring);
}

.lr-global-search-dropdown {
	position: absolute;
	left: 0;
	right: 0;
	top: calc(100% + var(--lr-gap-tight));
	background: var(--lr-callout-bg-color);
	border: var(--lr-border-card);
	border-radius: var(--lr-radius-medium);
	box-shadow: 0 16px 40px rgba(13, 17, 23, 0.16);
	padding: 6px;
	max-height: 320px;
	overflow-y: auto;
	/* Prevent the page from starting to scroll when the user reaches
	   the bottom of the dropdown's own scroll area (touch devices).
	   Pure UX: no visual change, no impact on mouse-wheel users. */
	overscroll-behavior: contain;
	z-index: 50;
}

.lr-global-search-dropdown[hidden] {
	display: none !important;
}

.lr-global-search-dropdown a {
	display: block;
	padding: 10px 14px;
	font-size: 0.9rem;
	color: var(--lr-color-text, #1f374e);
	text-decoration: none;
	border-radius: var(--lr-radius-small);
	border-left: 3px solid transparent;
	transition:
		background-color var(--lr-duration-base) var(--lr-easing-standard),
		color var(--lr-duration-base) var(--lr-easing-standard),
		border-color var(--lr-duration-base) var(--lr-easing-standard);
}

/* Keyboard-driven highlight (.is-highlighted) is rendered identically
   to mouse hover and :focus, so users moving through results with
   ArrowDown/ArrowUp see the same affordance as mouse users. The class
   is toggled by the curriculum global-search keydown handler in
   functions.php §25-#4. */
.lr-global-search-dropdown a:hover,
.lr-global-search-dropdown a:focus-visible,
.lr-global-search-dropdown a.is-highlighted {
	background: var(--lr-color-accent-wash, #eef2f8);
	color: var(--lr-color-accent, #002147);
	border-left-color: var(--lr-color-accent, #002147);
	outline: none;
	box-shadow: inset 0 0 0 2px rgba(var(--lr-color-accent-rgb, 0, 33, 71), 0.25);
}

.lr-global-search-dropdown .lr-global-search-empty {
	padding: 12px 14px;
	font-size: 0.85rem;
	color: var(--lr-color-text-subtle, #3e5871);
}

/* Touch-input tap-target sizing.
   The existing max-width:480px rule below already covers small phones,
   but tablets (>480px wide, coarse pointer) fall through that net.
   Targeting `pointer: coarse` covers iPad portrait/landscape and any
   touch device regardless of viewport width, raising each result to
   the WCAG 2.5.5 minimum 44px hit area without affecting mouse users. */

/* Wide math blocks: horizontal scroll with subtle scrollbar.
   The scrollbar tints below previously used the legacy accent (#4a7ac7)
   verbatim; switched to the canonical accent rgb token so the scrollbar
   stays in lock-step with the rest of the palette. */
.lr-math-block {
	overflow-x: auto;
	overflow-y: hidden;
	max-width: 100%;
	scrollbar-width: thin;
	scrollbar-color: rgba(var(--lr-color-accent-rgb), 0.4) rgba(240, 243, 248, 0.8);
}

.lr-math-block::-webkit-scrollbar {
	height: 6px;
}

.lr-math-block::-webkit-scrollbar-track {
	background: rgba(240, 243, 248, 0.9);
	border-radius: 3px;
}

.lr-math-block::-webkit-scrollbar-thumb {
	background: rgba(var(--lr-color-accent-rgb), 0.35);
	border-radius: 3px;
}

.lr-math-block::-webkit-scrollbar-thumb:hover {
	background: rgba(var(--lr-color-accent-rgb), 0.55);
}

/* Grand Synthesis: curriculum HTML utilities (replaces former inline style attributes) */
nav.lr-related-modules {
	max-width: 900px;
	margin-left: auto;
	margin-right: auto;
	padding: 16px 20px;
	border-radius: var(--lr-radius-medium);
	border: 1px solid var(--lr-color-border-subtle);
}

body.lr-titan-doc .lr-list-relaxed,
.lr-titan-doc .lr-list-relaxed {
	line-height: 1.65;
}

body.lr-titan-doc .lr-mb-0 {
	margin-bottom: 0 !important;
}

body.lr-titan-doc .lr-mb-8 {
	margin-bottom: 8px;
}

body.lr-titan-doc .lr-cross-link,
.lr-titan-doc .lr-cross-link {
	color: inherit;
}

body.lr-titan-doc .lr-text-link-underline,
.lr-titan-doc .lr-text-link-underline {
}

body.lr-titan-doc .lr-text-link-muted,
.lr-titan-doc .lr-text-link-muted {
	color: var(--lr-color-text-subtle);
}

body.lr-titan-doc .wp-block-columns.lr-figure-placeholder,
.lr-titan-doc .wp-block-columns.lr-figure-placeholder {
	background-color: var(--lr-color-bg-light);
	border: 2px dashed var(--lr-color-border-subtle);
	border-radius: var(--lr-radius-medium);
	height: 200px;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	margin: 16px 0;
}

body.lr-titan-doc .lr-figure-placeholder-icon,
.lr-titan-doc .lr-figure-placeholder-icon {
	font-size: 24px;
	color: var(--lr-color-text-subtle);
	margin-bottom: 8px;
}

body.lr-titan-doc .lr-figure-placeholder-label,
.lr-titan-doc .lr-figure-placeholder-label {
	color: var(--lr-color-text-subtle);
	text-align: center;
	font-family: monospace;
	letter-spacing: 0.5px;
}

body.lr-titan-doc .lr-figure-placeholder-desc,
.lr-titan-doc .lr-figure-placeholder-desc {
	color: var(--lr-color-text-subtle);
	text-align: center;
	margin-top: 4px;
	padding: 0 16px;
}

/* `.lr-sr-only` is now handled globally next to `.lr-visually-hidden` above. */

body.lr-titan-doc .lr-titan-iter-details-box,
.lr-titan-doc .lr-titan-iter-details-box {
	margin: 16px 0;
	padding: 12px;
	border: 1px solid var(--lr-color-border-subtle);
	border-radius: 8px;
}

body.lr-titan-doc .lr-titan-iter-summary,
.lr-titan-doc .lr-titan-iter-summary {
	cursor: pointer;
	font-weight: bold;
	color: var(--lr-color-accent);
}

body.lr-titan-doc .lr-derivation-content.lr-titan-iter-content,
.lr-titan-doc .lr-derivation-content.lr-titan-iter-content {
	margin-top: 12px;
}

/* LUCAS REY GRAND AUDIT: tables, callouts, mobile */

/* Narrow viewports (~375px): avoid media / code / flex children blowing layout */
body.lr-titan-doc img,
body.lr-titan-doc video,
body.lr-titan-doc svg {
	max-width: 100%;
	height: auto;
}

body.lr-titan-doc pre {
	overflow-x: auto;
	max-width: 100%;
	box-sizing: border-box;
}

body.lr-titan-doc .wp-block-table.lr-table-wrap,
.lr-titan-doc .wp-block-table.lr-table-wrap {
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
	max-width: 100%;
}

table.lr-table-clean {
	width: 100%;
	border-collapse: collapse;
	font-size: 0.9rem;
	line-height: 1.45;
	text-align: left;
}

table.lr-table-clean th,
table.lr-table-clean td {
	padding: 0.5rem 0.65rem;
	border-bottom: 1px solid var(--lr-color-border-subtle, #dce6ec);
	vertical-align: top;
}

table.lr-table-clean thead th {
	font-weight: 600;
	background: rgba(240, 243, 248, 0.65);
}

table.lr-table-clean.lr-table-center th,
table.lr-table-clean.lr-table-center td {
	text-align: center;
}

table.lr-table-clean.lr-table-compact {
	font-size: 0.85em;
}

/* Structural integrity for the newly semantic <aside> tags, excluding premium callouts */
body.lr-titan-doc aside.lr-panel-white:not(.lr-panel-accent-edge),
.lr-titan-doc aside.lr-panel-white:not(.lr-panel-accent-edge) {
    box-sizing: border-box !important;
    border: 1px solid var(--lr-color-border-subtle) !important;
    background-color: #ffffff !important;
    padding: clamp(16px, 3vw, 24px) clamp(20px, 4vw, 28px) !important;
    box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04) !important;
    border-radius: 10px !important;
    font-size: 0.875rem !important;
    line-height: 1.5 !important;
}

/* =========================================================
   PEDAGOGICAL CALLOUTS, UNLAYERED (ASTRA / MINIFIER SAFE)
========================================================= */

/* --- Pedagogical Callouts Clean Slate --- */
.lr-panel-white.lr-panel-accent-edge {
	/* A faint accent wash (not flat white) distinguishes callout panels from
	   plain content cards, which share almost identical border/shadow
	   treatment otherwise. */
	background-color: var(--lr-color-accent-wash);

	/* Decoupled borders to prevent minifier squashing */
	border-top: 1px solid var(--lr-color-border-subtle) !important;
	border-right: 1px solid var(--lr-color-border-subtle) !important;
	border-bottom: 1px solid var(--lr-color-border-subtle) !important;
	border-left: 4px solid var(--lr-callout-edge-color) !important;

	/* Setting up the transition for smooth lifting */
	transition:
		transform var(--lr-transition-pop),
		box-shadow var(--lr-transition-pop);
	/* The one piece worth keeping from the "frosted glass" pass: a 1px
	   light-catching edge along the top of the panel. The backdrop-filter
	   that shipped with it is gone, these panels sit on flat solid bands,
	   so blur(12px)+saturate(140%) composited exactly the band colour back
	   over itself while costing a layer of GPU per panel, and the
	   overflow:hidden it needed clipped focus rings and MathJax descenders
	   at the panel edge. Solid wash + light edge gives the same finish at
	   zero paint cost. */
	box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.9), 0 2px 8px var(--lr-callout-shadow-idle);
}

/* Hover and Mobile Pop States */
/* Only altering transform, to preserve the 5px left edge AND the resting
   box-shadow. The rule used to also set `box-shadow:
   var(--lr-shadow-hover-panel)`, which is the same no-op token the dead card
   hover used: it resolves to `none`, so it did not add a hover shadow, it
   DELETED the resting one. Hovering a callout removed the inset light edge
   the comment above calls the one piece worth keeping, plus the soft
   contact shadow, and lifted the panel 2px into a flatter state than it
   started in. Dropping the declaration is the whole fix, the transform was
   always the intended change. */
.lr-panel-white.lr-panel-accent-edge:hover,
.lr-panel-white.lr-panel-accent-edge[data-active="true"] {
	transform: translateY(var(--lr-lift-card-hover));
}

.lr-panel-white.lr-panel-accent-edge > .lr-panel-scroll-x {
	min-width: 0 !important;
	max-width: 100% !important;
	overflow-x: auto !important;
	overflow-y: visible !important;
	-webkit-overflow-scrolling: touch !important;
	box-sizing: border-box !important;
}

.lr-panel-white.lr-panel-accent-edge strong {
	color: var(--lr-callout-edge-color) !important;
	font-weight: 700 !important;
}

/* MathJax display equations inside premium accent notes: not inside .lr-math-block, so give the mjx-container its own horizontal scroll (curriculum / lr-titan-doc only) */
body.lr-titan-doc aside.lr-panel-white.lr-panel-accent-edge mjx-container[display="true"],
.lr-titan-doc aside.lr-panel-white.lr-panel-accent-edge mjx-container[display="true"] {
	display: block !important;
	max-width: 100% !important;
	overflow-x: auto !important;
	overflow-y: hidden !important;
	-webkit-overflow-scrolling: touch !important;
	padding-bottom: 6px !important;
	box-sizing: border-box !important;
}

body.lr-titan-doc aside.lr-panel-white.lr-panel-accent-edge mjx-container[display="true"] > mjx-math,
.lr-titan-doc aside.lr-panel-white.lr-panel-accent-edge mjx-container[display="true"] > mjx-math {
	min-width: max-content !important;
}

/* --- Section Navigation Row Clean Slate --- */
.lr-nav-row {
	display: flex;
	justify-content: space-between;
	align-items: center;
	width: 100%;
	border-top: 1px solid var(--lr-color-border-subtle);
	padding-top: var(--lr-gap-small);
	margin-top: var(--lr-gap-medium);
	font-size: var(--lr-font-size-small);
}

/* Force perfect symmetry: 3 equal-width columns */
.lr-nav-row > :first-child {
	flex: 1;
	text-align: left;
}
.lr-nav-row > :nth-child(2) {
	flex: 1;
	text-align: center;
}
.lr-nav-row > :last-child {
	flex: 1;
	text-align: right;
}

/* Mobile responsiveness: stack vertically on small screens */
/* =========================================================
   FIX: EMAILS AND MULTILINE BUTTONS (Contact Panel)
========================================================= */

/* 1. Prevent emails from breaking mid-word */
a.lr-email-link,
.lr-panel-light a[href*="mailto:"] {
    white-space: nowrap !important;
    word-break: normal !important;
    overflow-wrap: normal !important;
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: clamp(13px, 1.5vw, 16px) !important;
}

/* 2. Allow buttons in the contact panel to wrap whole words */
.lr-panel-light .wp-block-button__link,
.lr-panel-light .wp-element-button {
    white-space: normal !important;
    word-break: normal !important;
    overflow-wrap: normal !important;
    height: auto !important;
    min-height: 48px !important;
    line-height: 1.3 !important;
    padding-top: 12px !important;
    padding-bottom: 12px !important;
    text-align: center !important;
}

.lr-contact-intent-field {
    margin-bottom: 1.25rem;
}

.lr-contact-intent-field label {
    display: block;
    font-weight: 600;
    margin-bottom: 0.35rem;
}

.lr-contact-intent-field select {
    width: 100%;
    max-width: 28rem;
    min-height: 44px;
}

/* Contact, teaching enquiry form inside lr-card */
.lr-contact-enquiry .lr-contact-form-wrap {
    margin-top: 1.5rem;
    padding-top: 0.25rem;
}

.lr-contact-form-wrap .wpcf7 {
    margin: 0;
}

/* Ensure labels have consistent rhythm and weight */
.lr-contact-form-wrap .wpcf7 form label {
    display: block;
    font-weight: 600;
    margin-bottom: var(--lr-space-2);
}

.lr-contact-form-wrap .wpcf7 form > p {
    margin-bottom: var(--lr-space-4);
}

/* Premium input styling */
.lr-contact-form-wrap .wpcf7-form-control:not([type="submit"]):not([type="checkbox"]):not([type="radio"]) {
    width: 100%;
    max-width: 100%;
    padding: var(--lr-space-3) var(--lr-space-4);
    border-radius: var(--lr-radius-small);
    border: var(--lr-border-card);
    background-color: var(--lr-color-surface-muted);
    font-family: inherit;
    font-size: 1rem;
    transition: 
        border-color var(--lr-duration-fast) var(--lr-easing-standard),
        box-shadow var(--lr-duration-fast) var(--lr-easing-standard);
}

.lr-contact-form-wrap .wpcf7-form-control:not([type="submit"]):not([type="checkbox"]):not([type="radio"]):focus {
    outline: none;
    border-color: var(--lr-color-accent);
    box-shadow: var(--lr-shadow-focus-ring);
    background-color: #ffffff;
}

/* Checkbox optical alignment */
.lr-contact-form-wrap .wpcf7-list-item {
    display: flex;
    align-items: flex-start;
    margin-left: 0;
}

.lr-contact-form-wrap .wpcf7-list-item input[type="checkbox"] {
    margin-top: 4px; /* Optical alignment with first line of label */
    margin-right: var(--lr-space-2);
}

.lr-contact-form-wrap .wpcf7-list-item-label {
    font-weight: 400; /* Distinct from main field labels */
}

/* Submit button */
.lr-contact-form-wrap .wpcf7-submit {
    margin-top: var(--lr-space-3);
    padding: var(--lr-space-3) var(--lr-space-5);
    background-color: var(--lr-color-accent);
    color: #ffffff;
    border: none;
    border-radius: var(--lr-radius-tag);
    font-weight: 600;
    cursor: pointer;
    transition: transform var(--lr-transition-pop), box-shadow var(--lr-transition-pop);
}

.lr-contact-form-wrap .wpcf7-submit:hover {
    transform: translateY(-1px);
    box-shadow: var(--lr-shadow-hover-button);
}

/* Contact, professional enquiry panels (email paths; teaching uses form below) */
.lr-enquiry-paths {
    margin-top: 1.5rem;
}

.lr-enquiry-jump {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 0.75rem;
    margin: 1rem 0 1.5rem;
    padding: 0;
    list-style: none;
}

.lr-enquiry-jump a {
    display: inline-block;
    min-height: 44px;
    line-height: 44px;
    padding: 0 0.85rem;
    border: 1px solid var(--lr-color-border-subtle);
    border-radius: 6px;
    font-size: 0.9rem;
    text-decoration: none;
}

.lr-enquiry-panel {
    margin-bottom: 1.25rem;
    padding: 1.25rem 1.35rem;
    border: 1px solid var(--lr-color-border-subtle);
    border-radius: 8px;
    scroll-margin-top: 120px;
    /* The panel carried no transition at all, so its :target state snapped
       on. Now that it also answers hover and focus (tier 2, §12) the three
       states need to cross-fade rather than jump. Colour only, so there is
       nothing here for prefers-reduced-motion to suppress. */
    transition:
        background-color var(--lr-duration-base) var(--lr-easing-smooth),
        border-color var(--lr-duration-base) var(--lr-easing-smooth),
        box-shadow var(--lr-duration-base) var(--lr-easing-smooth);
}

.lr-enquiry-panel.is-active,
.lr-enquiry-panel:target {
    border-color: var(--lr-color-accent, #002147);
    box-shadow: 0 0 0 1px var(--lr-color-accent, #002147);
}

.lr-enquiry-panel h3 {
    margin-top: 0;
}

.lr-contact-intent-live {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Visible breadcrumb trail (mirrors JSON-LD hierarchy) */
.lr-breadcrumb {
    margin: 0 0 1.25rem;
    font-size: 0.875rem;
    line-height: 1.5;
}



.lr-breadcrumb-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

.lr-breadcrumb-item {
    display: inline-flex;
    align-items: center;
    color: var(--lr-color-text-muted);
}

.lr-breadcrumb-item:not(:last-child)::after {
    content: "/";
    margin: 0 0.45rem;
    color: var(--lr-color-border-subtle);
    pointer-events: none;
}

.lr-breadcrumb-item a {
    text-decoration: none;
}

.lr-breadcrumb-item a:hover,
.lr-breadcrumb-item a:focus-visible {
    text-decoration: underline;
}

.lr-breadcrumb-item[aria-current="page"] {
    color: inherit;
    font-weight: 600;
}

.lr-author-provenance {
	margin-top: 1.5rem;
	border-top: 1px solid var(--lr-color-border-subtle, #dce6ec);
}

.lr-author-provenance .lr-section-heading {
	margin-bottom: 0.75rem;
}

/* Knowledge graph, nested pillar membership (parent_pillar hierarchy) */
.lr-list-group .lr-list-item.lr-list-item-nested {
	margin-left: 1.25em;
	font-size: 0.95em;
}

/* Runtime curriculum path (lr-curriculum.php) */
.lr-curriculum-path .lr-list-item strong {
	font-weight: 600;
}

/* =====================================================================
   Cookie consent & legal footer (lr-compliance.php)
===================================================================== */
.lr-cookie-banner {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 100001;
	padding: 14px max(14px, env(safe-area-inset-left, 0px)) calc(14px + env(safe-area-inset-bottom, 0px)) max(14px, env(safe-area-inset-right, 0px));
	background: rgba(255, 255, 255, 0.98);
	border-top: 1px solid var(--lr-color-border-subtle, #dce6ec);
	box-shadow: 0 -8px 28px rgba(15, 23, 42, 0.1);
	-webkit-backdrop-filter: blur(10px);
	backdrop-filter: blur(10px);
}

.lr-cookie-banner[hidden] {
	display: none !important;
}

.lr-cookie-banner__inner {
	max-width: var(--lr-content-max);
	margin: 0 auto;
	display: grid;
	gap: var(--lr-gap-small);
}

.lr-cookie-banner__title {
	margin: 0;
	font-size: 1rem;
	font-weight: 700;
	color: var(--lr-color-text-heading, #1f374e);
}

.lr-cookie-banner__text {
	margin: 0;
	font-size: 0.9rem;
	line-height: 1.55;
	color: var(--lr-color-text, #1f374e);
}

.lr-cookie-banner__link {
	color: var(--lr-color-accent, #002147);
	font-weight: 600;
	text-decoration: underline;
	text-underline-offset: 2px;
}

.lr-cookie-banner__actions {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	justify-content: flex-end;
}

/* These are bare <button> elements, and Astra styles `button` (background,
   border, radius, padding, font) in unlayered CSS. This block sits inside
   @layer components, and unlayered normal declarations outrank layered ones
   at any specificity, so without important every cookie button rendered in
   Astra's stock blue at 4px radius, including the reject/customise variants
   that are supposed to be quiet outline buttons. */
.lr-cookie-banner__btn {
	appearance: none;
	border-radius: 999px !important;
	padding: 0.5rem 1rem !important;
	min-height: 44px;
	font: inherit;
	font-weight: 600 !important;
	cursor: pointer;
	border: 2px solid transparent !important;
	box-shadow: none !important;
}

.lr-cookie-banner__btn[data-lr-cookie-accept] {
	background: var(--lr-color-accent, #002147) !important;
	border-color: var(--lr-color-accent, #002147) !important;
	color: #ffffff !important;
}

.lr-cookie-banner__btn[data-lr-cookie-reject],
.lr-cookie-banner__btn[data-lr-cookie-save] {
	background: #ffffff !important;
	border-color: var(--lr-color-border-subtle, #dce6ec) !important;
	color: var(--lr-color-text-heading, #1f374e) !important;
}

.lr-cookie-banner__btn[data-lr-cookie-customize] {
	background: transparent !important;
	border-color: var(--lr-color-border-subtle, #dce6ec) !important;
	color: var(--lr-color-text-heading, #1f374e) !important;
}

.lr-cookie-banner__prefs {
	display: grid;
	gap: 10px;
	grid-column: 1 / -1;
	padding: 12px;
	border: 1px solid var(--lr-color-border-subtle, #dce6ec);
	border-radius: 8px;
	background: var(--lr-color-surface-muted, #fffdf6);
}

.lr-cookie-banner__prefs[hidden] {
	display: none !important;
}

.lr-cookie-pref label {
	display: flex;
	align-items: flex-start;
	gap: 8px;
	font-size: 0.9rem;
	font-weight: 600;
	color: var(--lr-color-text-heading, #1f374e);
	cursor: pointer;
}

.lr-cookie-pref p {
	margin: 0 0 0 1.6rem;
	font-size: 0.85rem;
	line-height: 1.45;
	color: var(--lr-color-text, #1f374e);
}

.lr-cookie-pref--locked label {
	cursor: default;
	opacity: 0.92;
}

html.lr-cookie-banner-open #ast-scroll-top {
	bottom: calc(var(--lr-cookie-banner-offset, 7.5rem) + 20px) !important;
}

.lr-footer-secondary-nav {
	box-sizing: border-box;
	width: 100%;
	margin: 1.25rem 0 0;
	padding: 0 var(--lr-padding-inline-shell);
	text-align: center;
}

.lr-footer-secondary-nav__list {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 0.35rem 1rem;
	margin: 0;
	padding: 0;
	list-style: none;
	font-size: 0.875rem;
	line-height: 1.5;
}

.lr-footer-secondary-nav__list a {
	color: var(--lr-color-text-muted, #55697d);
	text-decoration: none;
}

.lr-footer-secondary-nav__list a:hover,
.lr-footer-secondary-nav__list a:focus-visible {
	/* Token, not Astra's literal: this block predates the §48 ink remap,
	   which overrides the same states with #cbd8e8 !important, but any
	   context that escapes that override would have flashed Astra's stock
	   #0274be here. */
	color: var(--lr-color-link-hover, #1546a0);
	text-decoration: underline;
}

.lr-site-footer {
	box-sizing: border-box;
	width: 100%;
	margin: 1.5rem 0 0;
	padding: 1.25rem var(--lr-padding-inline-shell) 2rem;
	border-top: 1px solid var(--lr-color-border-subtle, #dce6ec);
	text-align: center;
	font-size: 0.875rem;
	line-height: 1.5;
	color: var(--lr-color-text, #1f374e);
}

.lr-site-footer__copyright {
	margin: 0 0 0.35rem;
}

.lr-site-footer__contact {
	margin: 0 0 0.75rem;
}

.lr-site-footer__links {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem 1rem;
	justify-content: center;
	margin: 0;
	padding: 0;
	list-style: none;
}

.lr-footer-secondary-nav__list li,
.lr-site-footer__links li,
.lr-footer-copyright li,
.site-footer li,
footer li {
	list-style: none !important;
	list-style-type: none !important;
	margin: 0;
}

.lr-footer-secondary-nav__list li::before,
.lr-site-footer__links li::before,
.lr-footer-copyright li::before,
.site-footer li::before,
footer li::before,
.lr-footer-secondary-nav__list li::after,
.lr-site-footer__links li::after,
.lr-footer-copyright li::after,
.site-footer li::after,
footer li::after,
.lr-footer-secondary-nav__list li::marker,
.lr-site-footer__links li::marker,
.lr-footer-copyright li::marker,
.site-footer li::marker,
footer li::marker {
	display: none !important;
	content: "" !important;
}

.lr-site-footer__links a,
/* Also a bare <button>: same unlayered-Astra problem as the cookie buttons.
   Without important this rendered as a solid blue Astra button rather than
   the inline text link it is meant to be, sitting among the footer links. */
.lr-site-footer__manage {
	color: var(--lr-color-accent, #002147) !important;
	text-decoration: none;
	background: none !important;
	border: none !important;
	padding: 0 !important;
	font: inherit;
	font-weight: 600 !important;
	box-shadow: none !important;
	cursor: pointer;
}

.lr-site-footer__links a:hover,
.lr-site-footer__manage:hover {
	text-decoration: underline;
}

.lr-legal-page {
	max-width: var(--lr-content-max);
	margin: 0 auto;
}

.lr-legal-nav__list {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem 1.25rem;
	margin: 0 0 1.5rem;
	padding: 0;
	list-style: none;
}

.lr-legal-page__body h2 {
	margin-top: 1.75rem;
	font-size: 1.15rem;
}

.lr-legal-page__body h3 {
	margin-top: 1.25rem;
	font-size: 1rem;
}

.lr-cf7-privacy-notice,
.lr-cf7-privacy-accept {
	font-size: 0.9rem;
	line-height: 1.5;
}

html[data-lr-consent-functional="denied"] .grecaptcha-badge {
	visibility: hidden !important;
}

/* HTML site map (v1.2.73) */
.lr-html-sitemap__main {
	max-width: 48rem;
	margin: 0 auto;
	padding: 2rem 1rem 3rem;
}

.lr-html-sitemap__header {
	margin-block-end: 2rem;
}

.lr-html-sitemap__section {
	margin-block-end: 1.5rem;
	padding-block-end: 1rem;
	border-block-end: 1px solid rgba(15, 23, 42, 0.08);
}

.lr-html-sitemap__section ul {
	margin: 0.5rem 0 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
}

.lr-html-sitemap__section a {
	color: var(--lr-color-accent, #002147);
	text-decoration: none;
}

.lr-html-sitemap__section a:hover {
	text-decoration: underline;
}

/* =========================================================
	ESAT MODULAR COMPONENTS
========================================================= */
.lr-panel-white {
	background-color: #ffffff;
	border: 1px solid var(--lr-color-border-subtle);
	border-radius: var(--lr-radius-medium);
}
.lr-panel-accent-edge {
	border-left: 4px solid var(--lr-color-primary);
}

.lr-esat-warning {
	border-left: 4px solid #f59e0b;
	padding: 16px;
	border-radius: 8px;
	background-color: #ffffff;
}
.lr-esat-options {
	padding: 16px;
	border-radius: 8px;
	font-size: 1.1rem;
	background-color: #ffffff;
	border: 1px solid var(--lr-color-border-subtle);
}
.lr-esat-metadata {
	display: flex;
	flex-wrap: wrap;
	gap: 16px;
	padding: 12px 16px;
	border-radius: 6px;
	background-color: #ffffff;
	border: 1px solid var(--lr-color-border-subtle);
}
.lr-esat-key-idea {
	padding: 16px;
	background-color: #ffffff;
	border-radius: 6px;
	border: 1px solid var(--lr-color-border-subtle);
	/* Accent stripe must come AFTER the `border` shorthand: the shorthand
	   resets all four edges, so declaring border-left first left the stripe
	   at border-subtle grey and only its width restored. */
	border-left: 4px solid var(--lr-color-primary);
}
.lr-esat-fastest-approach {
	border-left: 4px solid #10b981;
	padding: 12px 16px;
	margin-bottom: 16px;
	border-radius: 4px;
	background-color: rgba(16, 185, 129, 0.05);
}
.lr-esat-full-derivation {
	padding: 16px 20px;
	margin-bottom: 16px;
	border-radius: 8px;
	border: 1px solid var(--lr-color-border-subtle);
	background-color: #ffffff;
}
.lr-esat-common-mistake {
	border-left: 4px solid #c63838;
	padding: 12px 16px;
	margin-bottom: 16px;
	border-radius: 4px;
	background-color: rgba(239, 68, 68, 0.05);
}
.lr-esat-takeaway {
	border-left: 4px solid #3b82f6;
	padding: 12px 16px;
	border-radius: 4px;
	background-color: rgba(59, 130, 246, 0.05);
}
.lr-esat-question-text {
	padding: 20px;
	border-radius: 8px;
	font-size: 1.1rem;
	line-height: 1.6;
	background-color: #ffffff;
	border: 1px solid var(--lr-color-border-subtle);
}
.lr-esat-curriculum-nav {
	max-width: 900px;
	margin-left: auto;
	margin-right: auto;
	padding: 16px 20px;
	border-radius: var(--lr-radius-medium);
	border: 1px solid var(--lr-color-border-subtle);
	background-color: #ffffff;
}.lr-esat-grid-card {
	border-radius: 8px;
	padding: 20px;
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	border: 1px solid var(--lr-color-border-subtle);
	background-color: #ffffff;
}

/* --- ESAT mock-module / paper link cards ---------------------------------
   These cards ARE links. The previous inline-styled version inherited body
   colour and removed the underline, so the whole grid read as inert boxes and
   visitors could not tell the practice sets were clickable. Title carries the
   link colour, an explicit call to action carries the arrow, and hover/focus
   move the card so the affordance survives at a glance. Unlayered on purpose:
   it must outrank Astra's own unlayered anchor colour rules. */
.lr-module-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
	gap: 12px;
	padding-bottom: 8px;
}
.lr-module-grid--papers {
	padding-bottom: 20px;
}
a.lr-module-card {
	display: block;
	padding: 14px 16px;
	border: 1px solid var(--lr-color-border-subtle);
	border-left: 3px solid var(--lr-color-primary);
	border-radius: 6px;
	background-color: var(--lr-color-background-white);
	text-decoration: none;
	transition:
		border-color var(--lr-duration-moderate) var(--lr-easing-mechanical),
		box-shadow var(--lr-duration-moderate) var(--lr-easing-mechanical),
		transform var(--lr-duration-moderate) var(--lr-easing-mechanical);
}
.lr-module-card__title {
	display: block;
	margin-bottom: 2px;
	font-size: 0.95rem;
	font-weight: 700;
	color: var(--lr-color-primary);
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 2px;
}
.lr-module-card__meta {
	display: block;
	font-size: 0.8rem;
	line-height: 1.5;
	color: var(--lr-color-text-muted);
}
.lr-module-card__cta {
	display: block;
	margin-top: 8px;
	font-size: 0.78rem;
	font-weight: 600;
	color: var(--lr-color-primary);
}
a.lr-module-card:hover,
a.lr-module-card:focus-visible {
	border-color: var(--lr-color-primary);
	box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
	transform: translateY(-2px);
}
a.lr-module-card:focus-visible {
	outline: 2px solid var(--lr-color-primary);
	outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
	a.lr-module-card {
		transition: none;
	}
	a.lr-module-card:hover,
	a.lr-module-card:focus-visible {
		transform: none;
	}
}

/* --- ESAT Pedagogy Cards (Advanced UI) --- */
.esat-card {
	padding: 18px 24px;
	border-radius: var(--lr-radius-medium);
	margin-bottom: 20px;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
	border: 1px solid var(--lr-color-border-subtle);
	background-color: var(--lr-color-background-white);
	transition: box-shadow var(--lr-duration-moderate) var(--lr-easing-mechanical);
}
.esat-card:hover {
	box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.esat-card h4 {
	font-size: 1.05rem;
	font-weight: 700;
	margin-top: 0;
	margin-bottom: 12px;
	display: flex;
	align-items: center;
	gap: 8px;
}
.esat-card p {
	margin-bottom: 0;
	line-height: 1.6;
}
.idea-card {
	border-left: 5px solid #eab308;
	background-color: #fefce8;
}
.idea-card h4 { color: #854d0e; }

.fast-card {
	border-left: 5px solid #10b981;
	background-color: #f0fdf4;
}
.fast-card h4 { color: #065f46; }

.full-card {
	border-left: 5px solid var(--lr-color-primary);
	background-color: var(--lr-color-primary-subtle);
}
.full-card h4 { color: var(--lr-color-primary); }

.mistake-card {
	border-left: 5px solid #c63838;
	background-color: #fef2f2;
}
.mistake-card h4 { color: #991b1b; }

.takeaway-card {
	border-left: 5px solid #8b5cf6;
	background-color: #f5f3ff;
}
.takeaway-card h4 { color: #5b21b6; }

/* --- ESAT Pattern Details Tab --- */
.esat-tab {
	border: 1px solid var(--lr-color-border-subtle);
	border-radius: var(--lr-radius-medium);
	background-color: #ffffff;
	margin-bottom: 20px;
	overflow: hidden;
}
.esat-tab summary {
	padding: 16px 20px;
	font-weight: 700;
	font-size: 1.05rem;
	cursor: pointer;
	background-color: var(--lr-color-bg-section);
	transition: background-color 0.2s ease;
}
.esat-tab summary:hover {
	background-color: var(--lr-color-border-subtle);
}
.esat-tab p {
	padding: 16px 20px;
	margin: 0;
	line-height: 1.6;
	background-color: #ffffff;
	border-top: 1px solid var(--lr-color-border-subtle);
}
.pattern-tab {
	border-left: 5px solid #f97316;
}
.pattern-tab summary {
	color: #c2410c;
}

/* =================================================================
   22. SPECIFICITY OVERRIDES - LINK UNDERLINES POLICY
   ================================================================= */

/* 1. Suppress permanent underlines on structural, layout, navigation, sitemaps, tags, and card links */
.lr-list-group a,
.lr-list-item a,
.lr-breadcrumb-item a,
.lr-footer-secondary-nav__list a,
.lr-site-footer__links a,
.lr-site-footer a,
.lr-html-sitemap__section a,
.lr-footer-overlay a,
.main-navigation a,
.lr-header-sections-nav a,
.widget a,
.widget-title a,
.lr-enquiry-jump a,
.lr-project-card a,
.lr-btn a,
.lr-tag,
.wp-block-tag,
.pill,
.lr-ms,
.lr-doi,
.lr-card a,
.menu-item a,
.sub-menu a,
.site-title a,
.entry-title a,
.wp-block-post-title a,
.wp-block-button a,
.wp-block-file a.wp-block-file__button,
.lr-menu-toggle {
	text-decoration: none !important;
}

/* 2. Enable underlines exclusively on hover and focus-visible states for structural links */
.lr-list-group a:hover,
.lr-list-group a:focus-visible,
.lr-list-item a:hover,
.lr-list-item a:focus-visible,
.lr-breadcrumb-item a:hover,
.lr-breadcrumb-item a:focus-visible,
.lr-footer-secondary-nav__list a:hover,
.lr-footer-secondary-nav__list a:focus-visible,
.lr-site-footer__links a:hover,
.lr-site-footer__links a:focus-visible,
.lr-site-footer a:hover,
.lr-site-footer a:focus-visible,
.lr-html-sitemap__section a:hover,
.lr-html-sitemap__section a:focus-visible,
.lr-footer-overlay a:hover,
.lr-footer-overlay a:focus-visible,
.main-navigation a:not(.menu-link):hover,
.main-navigation a:not(.menu-link):focus-visible,
.lr-header-sections-nav a:not(.menu-link):hover,
.lr-header-sections-nav a:not(.menu-link):focus-visible,
.widget a:hover,
.widget a:focus-visible,
.widget-title a:hover,
.widget-title a:focus-visible,
.lr-enquiry-jump a:hover,
.lr-enquiry-jump a:focus-visible,
.lr-project-card a:hover,
.lr-project-card a:focus-visible,
.lr-btn a:hover,
.lr-btn a:focus-visible,
.lr-tag:hover,
.lr-tag:focus-visible,
.wp-block-tag:hover,
.wp-block-tag:focus-visible,
.pill:hover,
.pill:focus-visible,
.lr-ms:hover,
.lr-ms:focus-visible,
.lr-doi:hover,
.lr-doi:focus-visible,
.lr-card a:hover,
.lr-card a:focus-visible,
.menu-item a:not(.menu-link):hover,
.menu-item a:not(.menu-link):focus-visible,
.sub-menu a:hover,
.sub-menu a:focus-visible,
.site-title a:hover,
.site-title a:focus-visible,
.entry-title a:hover,
.entry-title a:focus-visible,
.wp-block-post-title a:hover,
.wp-block-post-title a:focus-visible,
.wp-block-button a:hover,
.wp-block-button a:focus-visible,
.wp-block-file a.wp-block-file__button:hover,
.wp-block-file a.wp-block-file__button:focus-visible {
	text-decoration: underline !important;
}

/* 3. Protect and enforce underlines on inline copy links for readability and WCAG accessibility

   `li a` is scoped away from navigation, and this is the fix for a fault that
   survived two previous attempts because both aimed at the wrong element.

   Astra marks up the primary nav as <li class="menu-item"><a class="menu-link">,
   so a bare `li a` matches every desktop menu link. This rule then forced an
   underline on them, and on hover the nav showed TWO lines: this underline,
   drawn at the browser default `text-decoration-thickness: auto` which is a
   heavy 2 to 3px slab, plus the sliding 2px accent bar the nav draws on
   .menu-link::after at bottom -6px.

   Both earlier fixes wrote `text-decoration: none !important` onto
   `.main-header-menu .menu-link` in the ADAPTER layer, and neither could ever
   work. For important declarations the cascade INVERTS layer order, so an
   important in `components` beats an important in the later `adapter`
   outright, and specificity is never consulted. Chrome's own matched-styles,
   read over the DevTools protocol with hover forced, shows every rule on the
   anchor declaring `none` while the computed value is `underline`, which is
   what sent the earlier attempts looking at the anchor instead of at the rule
   that actually wins.

   Excluding .menu-link here fixes it at the source rather than fighting it
   downstream. Prose links are untouched: a menu-link never appears in running
   copy. */
p a,
li a:not(.menu-link),
.entry-content p a,
.entry-content li a:not(.menu-link),
.lr-prose a,
.prose a,
/* Contact Form 7 renders its acceptance/checkbox labels as <span>s, not
   <p>, so the privacy-policy link inside the consent checkbox escaped the
   prose rule and was distinguishable by colour alone (Lighthouse
   link-in-text-block, 2026-08-23). */
.wpcf7-list-item-label a,
.wpcf7-acceptance a {
	text-decoration: underline !important;
	text-underline-offset: 3px !important;
}

/* 3b. Re-assert §1 for structural links inside .entry-content.
   §1's selectors (e.g. `.lr-breadcrumb-item a`, 0-1-1) are out-specified by
   §3's `.entry-content li a` (0-1-2), so breadcrumbs, navigational lists,
   card links and pills picked up permanent prose underlines and read as
   cluttered. These selectors restore the intended split, prose copy is
   underlined, structural navigation is not, and are only needed because
   both blocks are unlayered and important, leaving specificity to decide. */
/* .lr-list-group / .lr-list-item are deliberately NOT in this no-underline
   list any more. They were, on the reading that they are structural
   navigation, but every one of them is written as "<a>link</a>, trailing
   description", so the anchor sits inside a block of non-link text and
   WCAG 1.4.1 applies. With the underline stripped, colour was the only cue,
   and as an accent-coloured link it measures 1.31:1 against the surrounding
   copy (#1f374e) under Oxford blue, 1.09:1 under the indigo before it: the
   two are indistinguishable either way.

   Recolouring cannot fix this. To clear 3:1 against that body text a link
   would need a relative luminance of at least 0.211, but to hold 4.5:1
   against the page background it must stay at or below 0.178. No colour
   satisfies both, which is precisely why the guideline asks for a non-colour
   cue. So the underline comes back for these two, and only these two.
   Breadcrumbs, index lists, cards and pills are genuinely bare links with no
   surrounding prose and stay clean. */
.entry-content .lr-breadcrumb-item a,
.entry-content .lr-index-list a,
.entry-content .lr-card a,
.entry-content .lr-project-card a,
.entry-content .lr-enquiry-jump a,
.entry-content .lr-tag,
.entry-content .wp-block-tag,
.entry-content .pill,
.entry-content .lr-ms,
.entry-content .lr-doi {
	text-decoration: none !important;
}

/* 3c. .lr-card is a GROUND, not a link, so its blanket exemption above was
   too wide.

   The block above reasons that "cards and pills are genuinely bare links
   with no surrounding prose". That is true of a pill, a tag, a breadcrumb
   and a project card. It is not true of .lr-card, which on this theme wraps
   whole sections: the class is the section wrapper, so every paragraph
   inside a section is inside a card, and `.entry-content .lr-card a` at
   (0,2,1) therefore strips the underline from ordinary prose links too.

   Measured on production before this rule existed: /teaching/esat/start-here/
   carried 38 links inside cards and none of them was underlined.

   That is the same WCAG 1.4.1 failure the block above fixed for
   .lr-list-group and .lr-list-item, and the same arithmetic applies. It was
   re-derived rather than taken on trust: brute-forcing the whole sRGB cube
   finds ZERO colours that clear 3:1 against the body navy #1f374e while
   also holding 4.5:1 against all five grounds this text sits on. Colour
   alone cannot carry the affordance here, so the underline has to.

   What this restores is the split the block above intended: prose copy is
   underlined, structural navigation is not. A link inside a <p>, or inside
   one of the list components already ruled to be prose, is prose wherever it
   happens to sit. A card title, a call to action, a button or a pill is not,
   and none of those live in a <p>, so none of them match here.

   Specificity, since both blocks are unlayered and important and the winner
   is therefore decided on specificity alone: these run (0,6,2) and (0,3,2)
   against the (0,2,1) above, so they win. The :not() list mirrors the §22
   exclusions so a button-like link inside a paragraph is still left alone. */
.entry-content .lr-card p a:not(.lr-btn):not(.wp-block-button__link):not(.lr-tag):not(.pill),
.entry-content .lr-card .lr-list-item a,
.entry-content .lr-card .lr-list-group a {
	text-decoration: underline !important;
	text-underline-offset: 3px !important;
}

/* Structural lists nested inside a card stay bare. Higher specificity than
   the rule immediately above so a breadcrumb or an index list inside a card
   is not dragged back into the prose treatment. */
.entry-content .lr-card .lr-breadcrumb-item a,
.entry-content .lr-card .lr-index-list a,
.entry-content .lr-card p a.lr-tag,
.entry-content .lr-card p a.pill {
	text-decoration: none !important;
}

.entry-content .lr-breadcrumb-item a:hover,
.entry-content .lr-breadcrumb-item a:focus-visible,
.entry-content .lr-index-list a:hover,
.entry-content .lr-index-list a:focus-visible,
.entry-content .lr-card a:hover,
.entry-content .lr-card a:focus-visible,
.entry-content .lr-project-card a:hover,
.entry-content .lr-project-card a:focus-visible,
.entry-content .lr-enquiry-jump a:hover,
.entry-content .lr-enquiry-jump a:focus-visible {
	text-decoration: underline !important;
}

/* Cache bust size bump: 1.3.15 */
/* === LATEST USER REQUESTS === */
.lr-card-footer .lr-small {
    display: flex;
    flex-wrap: wrap;
    column-gap: 8px;
    row-gap: 4px;
    align-items: center;
}

.lr-accordion summary:focus-visible {
    outline: none;
    border-radius: var(--lr-radius-small);
    box-shadow: var(--lr-shadow-focus-ring);
}

#home-hero .lr-grid-equal {
    gap: clamp(32px, 6vw, 80px);
    align-items: center;
}

section[aria-label="Quick Summary"] {
    max-width: var(--lr-prose-max-width);
    margin-inline: auto;
}


/* === CONTACT FORM 7 AND UI TWEAKS === */
/* Modernize Contact Form 7 layout and inputs */
.lr-contact-form-wrap .wpcf7 form p {
    margin-bottom: 24px !important;
}

.lr-contact-form-wrap .wpcf7 form label {
    font-size: 0.95rem !important;
    color: var(--lr-color-text-heading) !important;
    margin-bottom: 8px !important;
}

/* Chunky, modern input fields with subtle depth */
.lr-contact-form-wrap .wpcf7-form-control:not([type="submit"]):not([type="checkbox"]):not([type="radio"]) {
    padding: 16px 20px !important;
    border-radius: var(--lr-radius-medium) !important;
    /* --lr-color-border-subtle is tuned to sit quietly against white; on the
       tinted field fill (--lr-color-bg-light) it measured 1.15:1, so the
       boundary of every input on the enquiry form was effectively invisible.
       WCAG 1.4.11 asks 3:1 for the visual boundary of a control. The muted
       TEXT token is used instead because it is held to 4.5:1 by the type
       rules, which means it can never drift below the 3:1 a border needs,       it currently measures 5.16:1 here. Deliberately a token rather than a
       literal, so it survives the next palette revision. */
    border: 1px solid var(--lr-color-text-muted) !important;
    background-color: var(--lr-color-bg-light) !important;
    box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.02) !important;
    font-size: 1rem !important;
}

/* Enhanced focus state for accessibility and polish */
.lr-contact-form-wrap .wpcf7-form-control:not([type="submit"]):not([type="checkbox"]):not([type="radio"]):focus {
    background-color: #ffffff !important;
    border-color: var(--lr-color-accent) !important;
    box-shadow: 0 0 0 4px rgba(var(--lr-color-accent-rgb), 0.12) !important;
    outline: none !important;
}

/* Make the checkbox area look intentional and aligned */
.lr-contact-form-wrap .wpcf7-acceptance {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-top: 10px;
}

.lr-contact-form-wrap .wpcf7-acceptance input[type="checkbox"] {
    width: 20px !important;
    height: 20px !important;
    margin: 2px 0 0 0 !important;
    accent-color: var(--lr-color-accent);
    cursor: pointer;
}

.lr-contact-form-wrap .wpcf7-list-item-label {
    font-size: 0.9rem !important;
    line-height: 1.5 !important;
    color: var(--lr-color-text-main) !important;
}

/* Bold, full-width submit button */
.lr-contact-form-wrap .wpcf7-submit {
    width: 100% !important;
    padding: 16px 24px !important;
    font-size: 1.1rem !important;
    border-radius: var(--lr-radius-medium) !important;
    box-shadow: 0 4px 12px rgba(var(--lr-color-accent-rgb), 0.15) !important;
    cursor: pointer;
}

.lr-contact-form-wrap .wpcf7-submit:hover {
    transform: translateY(-2px) !important;
    box-shadow: 0 6px 16px rgba(var(--lr-color-accent-rgb), 0.25) !important;
}

/* Unified Intent Guide Component */
.lr-intent-guide {
    width: calc(100% - (var(--lr-padding-inline-shell) * 2)) !important; /* Recreates the side bumpers */
    max-width: var(--lr-max-width-card) !important;
    margin-inline: auto !important;
    background-color: #ffffff !important;
    padding: var(--lr-padding-card) !important;
    border-radius: var(--lr-radius-large) !important;
    border: var(--lr-border-card) !important;
    border-left: 4px solid var(--lr-color-accent) !important;
    box-shadow: var(--lr-shadow-rest) !important;
}

.lr-intent-guide p {
    /* `max-width: none !important` used to sit here. It was defensive,       written when nothing capped paragraph measure, but it is exactly
       what stopped the MEASURE section at the end of this file from
       reaching the intent guide, whose lead paragraph was measured at 134
       characters on 17 pages. The cap is applied there now; this rule is
       left in place, empty of the override, so the selector's other job
       (documenting that this component was deliberately considered) is not
       lost from the file. */
}

/* 3. Strip the horizontal scrolling behavior from list items */
.lr-list-group .lr-list-item {
    overflow-x: visible !important;
    overflow-y: visible !important;
}

/* ...except in ESAT solutions, where list items carry MathJax that is far
   wider than the column on a phone. Measured on /teaching/esat/paper-0001/
   physics/ at a 390px viewport: a 497px equation inside a 215px list item,
   clipped with no way to scroll to the rest of it, the working is simply
   unreadable on mobile, on the site's core teaching content.

   The rule above is in this same layer and marked important, and the ESAT
   scroll fix at the foot of this file is unlayered and not important, so it
   loses the cascade. Winning it back needs a more specific important
   selector in this layer rather than another unlayered rule. Scoped to ESAT
   sections so ordinary bullet lists keep their scrollbar-free treatment. */
.lr-esat-section .lr-list-group .lr-list-item,
.lr-esat-question-text .lr-list-group .lr-list-item {
    overflow-x: auto !important;
    overflow-y: hidden !important;
}

/* Reclaim horizontal space on phones. ESAT solutions nest an article card
   inside a details panel inside a section, and each level contributes
   padding; measured at a 390px viewport that left a 215px text column, so
   roughly 45% of the screen was gutter. The bullet indent alone was 36px.
   Trimming the indent and the innermost card padding gives the working
   noticeably more room without touching the desktop layout. */
@media (max-width: 600px) {
    .lr-esat-section .lr-list-item,
    .lr-esat-question-text .lr-list-item {
        padding-left: 22px !important;
        padding-right: 8px !important;
    }

    /* Matches the base rule's `details.lr-details-solution .lr-details-content`
       specificity (0-2-1); a plain two-class selector loses to it. */
    details.lr-details-solution .lr-details-content {
        padding-left: 12px;
        padding-right: 12px;
    }

    #problem-container > article.lr-card {
        padding-left: var(--lr-space-4);
        padding-right: var(--lr-space-4);
    }
}


/* Booking Page: Widget Centering and Padding Fix */
body.lr-page-booking .lr-section-full {
    padding-bottom: clamp(64px, 8vw, 96px) !important; /* Forces the missing bottom space */
}

/* Target the booking widget wrapper to pull it into the center */
body.lr-page-booking .lr-section-full > :not(header):not(nav),
body.lr-page-booking .wp-block-html {
    margin-left: auto !important;
    margin-right: auto !important;
    max-width: 860px !important; /* Constrains it to a clean, readable dashboard width */
    width: 100% !important;
    display: block !important;
}

/* Booking Page: Widget Centering and Padding Fix */
body.lr-page-booking .lr-section-full {
    padding-bottom: clamp(64px, 8vw, 96px) !important;
}

body.lr-page-booking .lr-section-full > :not(header):not(nav),
body.lr-page-booking .wp-block-html {
    margin-left: auto !important;
    margin-right: auto !important;
    max-width: 860px !important;
    width: 100% !important;
    display: block !important;
}

/* Force the gray background to span full width while centering the portal */
body.lr-page-booking .lr-section-full {
    max-width: 100% !important;
    width: 100% !important;
}
body.lr-page-booking .wp-block-html {
    max-width: 860px !important;
    margin: 0 auto !important;
}

/* =========================================================
   BOOKING PORTAL POLISH 
========================================================= */

/* 1. Demote 'Cancel lesson' to a destructive outline button */
body.lr-page-booking .lr-booking__session-actions button:last-child {
    background: transparent !important;
    color: var(--lr-color-danger, #c63838) !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    margin-left: 8px; /* Adds space from the Reschedule button */
}

body.lr-page-booking .lr-booking__session-actions button:last-child:hover {
    background-color: var(--lr-color-danger-wash, #fef2f2) !important;
    border-color: var(--lr-color-danger, #c63838) !important;
}

/* 2. Add a divider between Add to Calendar links */
body.lr-page-booking .lr-booking__calendar-links {
    display: flex;
    gap: 12px;
    align-items: center;
    margin-bottom: 16px;
}

body.lr-page-booking .lr-booking__calendar-links a:first-child::after {
    content: "|";
    color: var(--lr-color-border-subtle);
    margin-left: 12px;
    pointer-events: none;
}

/* 3. Style the Calendar Navigation Buttons */
body.lr-page-booking .lr-booking__nav {
    background: transparent;
    color: var(--lr-color-text-heading);
    border: 1px solid var(--lr-color-border-subtle);
    border-radius: var(--lr-radius-small);
    padding: 8px 16px;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard);
}

body.lr-page-booking .lr-booking__nav:hover {
    border-color: var(--lr-color-accent);
    color: var(--lr-color-accent);
    background-color: var(--lr-color-accent-wash);
}

/* =========================================================
   BOOKING PORTAL: INDESTRUCTIBLE OVERRIDES 
========================================================= */

/* Destructive Outline Button */
.lr-btn-cancel-override {
    background: transparent !important;
    color: var(--lr-color-danger, #c63838) !important;
    border: 1px solid var(--lr-color-danger, #c63838) !important;
    margin-left: 8px !important;
    box-shadow: none !important;
}
.lr-btn-cancel-override:hover {
    background-color: var(--lr-color-danger-wash, #fef2f2) !important;
}

/* Link Divider */
.lr-link-divider-override::after {
    content: "|" !important;
    color: var(--lr-color-border-subtle, #dce6ec) !important;
    margin-left: 12px !important;
    margin-right: 12px !important;
    pointer-events: none !important;
    display: inline-block !important;
}

/* Calendar Controls */
.lr-btn-cal-nav-override {
    background: transparent !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    border-radius: var(--lr-radius-small, 6px) !important;
    padding: 6px 14px !important;
    font-weight: 600 !important;
    box-shadow: none !important;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard),
                transform var(--lr-duration-fast) var(--lr-easing-standard) !important;
}
.lr-btn-cal-nav-override:hover {
    border-color: var(--lr-color-accent, #002147) !important;
    color: var(--lr-color-accent, #002147) !important;
    background-color: var(--lr-color-bg-light, #eaf6fc) !important;
}

/* Fix short-page gray background gap */
body.lr-page-booking .lr-section-full {
    min-height: 85vh !important; /* Forces background down to footer */
}

/* =========================================================
   BOOKING PORTAL: PILL LINKS & LAYOUT FIX
========================================================= */

/* 1. Remove the old text divider */
body.lr-page-booking .lr-link-divider-override::after {
    display: none !important;
}

/* 2. Style calendar links as modern 'Pill' tertiary buttons */
body.lr-page-booking .lr-booking__calendar-links {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 10px !important;
    margin-bottom: 0 !important; /* Let the parent container handle spacing */
}

body.lr-page-booking .lr-booking__calendar-links a {
    display: inline-flex !important;
    align-items: center !important;
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    padding: 6px 16px !important;
    border-radius: 99px !important; /* Creates the pill shape */
    font-size: 0.85rem !important;
    font-weight: 500 !important;
    text-decoration: none !important;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard),
                transform var(--lr-duration-fast) var(--lr-easing-standard) !important;
}

body.lr-page-booking .lr-booking__calendar-links a:hover {
    background-color: var(--lr-color-border-subtle, #dce6ec) !important;
    color: var(--lr-color-accent, #002147) !important;
    border-color: var(--lr-color-border-subtle, #dce6ec) !important;
}

/* 3. Fix the wrapping issue on Reschedule/Cancel buttons */
body.lr-page-booking .lr-booking__session-actions {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 12px !important;
    align-items: center !important;
    margin-top: 16px !important;
}

body.lr-page-booking .lr-btn-cancel-override {
    margin-left: 0 !important; /* Reset the old hack so flex gap handles spacing cleanly */
}

/* =========================================================
   BOOKING PORTAL: PILL LINKS & LAYOUT FIX
========================================================= */

/* 1. Remove the old text divider */
body.lr-page-booking .lr-link-divider-override::after {
    display: none !important;
}

/* 2. Style calendar links as modern 'Pill' tertiary buttons */
body.lr-page-booking .lr-booking__calendar-links {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 10px !important;
    margin-bottom: 0 !important; /* Let the parent container handle spacing */
}

body.lr-page-booking .lr-booking__calendar-links a {
    display: inline-flex !important;
    align-items: center !important;
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    padding: 6px 16px !important;
    border-radius: 99px !important; /* Creates the pill shape */
    font-size: 0.85rem !important;
    font-weight: 500 !important;
    text-decoration: none !important;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard),
                transform var(--lr-duration-fast) var(--lr-easing-standard) !important;
}

body.lr-page-booking .lr-booking__calendar-links a:hover {
    background-color: var(--lr-color-border-subtle, #dce6ec) !important;
    color: var(--lr-color-accent, #002147) !important;
    border-color: var(--lr-color-border-subtle, #dce6ec) !important;
}

/* 3. Fix the wrapping issue on Reschedule/Cancel buttons */
body.lr-page-booking .lr-booking__session-actions {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 12px !important;
    align-items: center !important;
    margin-top: 16px !important;
}

body.lr-page-booking .lr-btn-cancel-override {
    margin-left: 0 !important; /* Reset the old hack so flex gap handles spacing cleanly */
}

/* =========================================================
   BOOKING PORTAL: CARDS, EMPTY STATES & TAB CONTRAST
========================================================= */

/* 1. Active Tab Contrast Fix (White text on Blue background) */
body.lr-page-booking [aria-selected="true"],
body.lr-page-booking .active,
body.lr-page-booking [class*="--active"],
body.lr-page-booking [class*="-active"] {
    color: #ffffff !important;
}

/* 2. Dashboard Cards Premium Hover */
body.lr-page-booking .lr-dashboard__card {
    transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    background: #ffffff !important;
}
body.lr-page-booking .lr-dashboard__card:hover {
    transform: translateY(-2px) !important;
    box-shadow: 0 10px 15px -3px rgba(0,0,0,0.05), 0 4px 6px -2px rgba(0,0,0,0.02) !important;
    border-color: var(--lr-color-border-hover, #cbd5e1) !important;
}

/* 3. Empty States (Muted & Italic) */
body.lr-page-booking .lr-dashboard__empty {
    color: var(--lr-color-text-muted, #55697d) !important;
    font-style: italic !important;
    font-size: 0.9em !important;
}

/* 4. Modern Progress Bar */
body.lr-page-booking progress {
    height: 8px !important;
    border-radius: 99px !important;
    overflow: hidden !important;
    -webkit-appearance: none;
    appearance: none;
    border: none !important;
    background-color: var(--lr-color-border-subtle, #dce6ec) !important;
}
body.lr-page-booking progress::-webkit-progress-bar {
    background-color: var(--lr-color-border-subtle, #dce6ec) !important;
    border-radius: 99px !important;
}
body.lr-page-booking progress::-webkit-progress-value {
    background-color: var(--lr-color-accent, #002147) !important;
    border-radius: 99px !important;
}
body.lr-page-booking progress::-moz-progress-bar {
    background-color: var(--lr-color-accent, #002147) !important;
    border-radius: 99px !important;
}

/* =========================================================
   BOOKING PORTAL: CALENDAR HOVER & CONTRAST OVERRIDES
========================================================= */

/* Force high-contrast text and premium background on calendar hover */
body.lr-page-booking table td:not(:empty):hover,
body.lr-page-booking table td:not(:empty):hover *,
body.lr-page-booking table td button:hover,
body.lr-page-booking table td button:hover * {
    background-color: var(--lr-color-accent-wash, #eef2f8) !important;
    color: var(--lr-color-accent, #002147) !important;
    cursor: pointer !important;
}

/* Ensure the explicitly selected/clicked date stays highly visible (Blue background, White text) */
body.lr-page-booking table td[class*="selected"],
body.lr-page-booking table td[class*="selected"] *,
body.lr-page-booking table td[class*="active"]:not(:hover),
body.lr-page-booking table td[class*="active"]:not(:hover) * {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
}

/* =========================================================
   BOOKING PORTAL: TAB HOVER FIX
========================================================= */

/* Ensure text turns white when hovering over tabs to match the blue background */
body.lr-page-booking [role="tab"]:hover {
    color: #ffffff !important;
}

/* =========================================================
   BOOKING PORTAL: BANNERS & DISABLED STATES
========================================================= */

/* Reschedule Banner Styling */
body.lr-page-booking .lr-booking__reschedule-banner {
    background-color: var(--lr-color-accent-wash, #eef2f8) !important;
    border-left: 4px solid var(--lr-color-accent, #002147) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    padding: 12px 16px !important;
    border-radius: 0 4px 4px 0 !important;
    font-size: 0.95rem !important;
    margin: 16px 0 !important;
}

/* Generic Disabled Button States */
body.lr-page-booking button:disabled,
body.lr-page-booking .lr-booking__nav:disabled,
body.lr-page-booking .lr-portal__cta:disabled {
    opacity: 0.5 !important;
    cursor: not-allowed !important;
    pointer-events: none !important;
    box-shadow: none !important;
}

/* =========================================================
   BOOKING PORTAL: SELECTED CALENDAR DAY FIX
========================================================= */

/* Ensure selected calendar days have a solid background to support the white text */
body.lr-page-booking table [aria-selected="true"],
body.lr-page-booking table [aria-selected="true"] *,
body.lr-page-booking table [class*="selected"],
body.lr-page-booking table [class*="selected"] * {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
    border-color: var(--lr-color-accent, #002147) !important;
}

/* =========================================================
   BOOKING PORTAL: BLUR AND INNER ELEMENT FIXES
========================================================= */

/* 1. Force solid background on active tabs even when out of focus */
body.lr-page-booking [role="tab"][aria-selected="true"] {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
    border-color: var(--lr-color-accent, #002147) !important;
}

/* 2. Override inner button styles for selected calendar days */
body.lr-page-booking table td[aria-selected="true"],
body.lr-page-booking table td[aria-selected="true"] button,
body.lr-page-booking table td[aria-selected="true"] span {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
    border-color: var(--lr-color-accent, #002147) !important;
}

/* =========================================================
   BOOKING PORTAL: THE MASTER UI MODERNIZATION BUNDLE
========================================================= */

/* Universal Card Elevation and Modern Radiuses */
body.lr-page-booking .lr-booking__cal-wrap,
body.lr-page-booking .lr-booking__day-panel,
body.lr-page-booking .lr-booking__confirm-summary,
body.lr-page-booking .lr-booking__bookings-list {
    background-color: #ffffff !important;
    border-radius: var(--lr-radius-large, 14px) !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    box-shadow: 0 4px 15px rgba(0,0,0,0.03), 0 1px 3px rgba(0,0,0,0.02) !important;
    padding: 24px !important;
    margin-bottom: 24px !important;
    transition: box-shadow 0.3s ease !important;
}

/* Calendar Grid Spacing and Containment */
body.lr-page-booking .lr-booking__cal-grid {
    gap: 8px !important;
    padding: 16px !important;
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    border-radius: var(--lr-radius-large, 14px) !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
}

/* Soft Hover States for Individual Lesson Rows */
body.lr-page-booking .lr-booking__booking-item {
    border-bottom: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    padding: 16px 0 !important;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard),
                transform var(--lr-duration-fast) var(--lr-easing-standard) !important;
    border-radius: var(--lr-radius-small, 6px) !important;
}
body.lr-page-booking .lr-booking__booking-item:last-child {
    border-bottom: none !important;
}
body.lr-page-booking .lr-booking__booking-item:hover {
    background-color: var(--lr-color-bg-light, #eaf6fc) !important;
    padding-left: 12px !important;
    padding-right: 12px !important;
    border-bottom-color: transparent !important;
}

/* Destructive Actions and Warning Polish */
body.lr-page-booking .lr-booking__cancel-wrap {
    background-color: var(--lr-color-danger-wash, #fef2f2) !important;
    border-left: 4px solid var(--lr-color-danger, #c63838) !important;
    border-radius: 0 var(--lr-radius-medium, 10px) var(--lr-radius-medium, 10px) 0 !important;
    padding: 16px 20px !important;
    margin-top: 16px !important;
}
body.lr-page-booking .lr-booking__cancel-prompt {
    color: var(--lr-color-text-heading, #1f374e) !important;
    font-weight: 600 !important;
    display: block !important;
    margin-bottom: 12px !important;
}

/* Typography Polish for Headings and Summaries */
body.lr-page-booking .lr-booking__confirm-lead,
body.lr-page-booking .lr-booking__day-heading {
    font-size: 1.25rem !important;
    font-weight: 700 !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    letter-spacing: -0.02em !important;
    margin-bottom: 20px !important;
}

/* =========================================================
   BOOKING PORTAL: BLUR AND INACTIVE WINDOW FIX
========================================================= */

/* Lock the blue background and white text permanently on selected items, even when the window loses focus */
body.lr-page-booking [role="tab"][aria-selected="true"],
body.lr-page-booking [role="tab"][aria-selected="true"] *,
body.lr-page-booking table td[aria-selected="true"],
body.lr-page-booking table td[aria-selected="true"] button,
body.lr-page-booking table td[aria-selected="true"] span,
body.lr-page-booking table td[class*="selected"],
body.lr-page-booking table td[class*="selected"] button,
body.lr-page-booking table td[class*="selected"] span {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
    border-color: var(--lr-color-accent, #002147) !important;
    opacity: 1 !important;
    outline: none !important;
}

/* =========================================================
   BOOKING PORTAL: BLUR AND INACTIVE WINDOW FIX
========================================================= */

/* Lock the blue background and white text permanently on selected items, even when the window loses focus */
body.lr-page-booking [role="tab"][aria-selected="true"],
body.lr-page-booking [role="tab"][aria-selected="true"] *,
body.lr-page-booking table td[aria-selected="true"],
body.lr-page-booking table td[aria-selected="true"] button,
body.lr-page-booking table td[aria-selected="true"] span,
body.lr-page-booking table td[class*="selected"],
body.lr-page-booking table td[class*="selected"] button,
body.lr-page-booking table td[class*="selected"] span {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
    border-color: var(--lr-color-accent, #002147) !important;
    opacity: 1 !important;
    outline: none !important;
}

/* =========================================================
   BOOKING PORTAL: FINAL CALENDAR CONTRAST FIXES
========================================================= */

/* 1. Fix the bottom number (available slots) blue on blue issue */
body.lr-page-booking table td[aria-selected="true"] .lr-booking__cal-day-count,
body.lr-page-booking table td[class*="selected"] .lr-booking__cal-day-count {
    color: rgba(255, 255, 255, 0.85) !important;
}

/* 2. Fix the blurred window white on light grey issue */
/* Force the inner button of a selected day to be solid blue, ignoring focus/blur */
body.lr-page-booking table td[aria-selected="true"] .lr-booking__cal-day,
body.lr-page-booking table td[class*="selected"] .lr-booking__cal-day {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
}

/* Ensure the day number stays pure white in all states if selected */
body.lr-page-booking table td[aria-selected="true"] .lr-booking__cal-day-num,
body.lr-page-booking table td[class*="selected"] .lr-booking__cal-day-num {
    color: #ffffff !important;
}

/* =========================================================
   BOOKING PORTAL: UNIVERSAL BUTTONS & CALENDAR LINKS
========================================================= */

/* 1. Force a universal modern border radius on ALL buttons */
body.lr-page-booking button,
body.lr-page-booking .button,
body.lr-page-booking [class*="btn"],
body.lr-page-booking .lr-portal__cta,
body.lr-page-booking .lr-booking__nav {
    border-radius: 8px !important;
}

/* 2. Target calendar links strictly by their URL to bypass React overrides */
body.lr-page-booking a[href*="calendar"],
body.lr-page-booking a[href*="google.com/calendar"],
body.lr-page-booking a[href*=".ics"] {
    display: inline-flex !important;
    align-items: center !important;
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    padding: 8px 16px !important;
    border-radius: 8px !important; /* Matches the universal button radius */
    font-size: 0.85rem !important;
    font-weight: 600 !important;
    text-decoration: none !important;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard),
                transform var(--lr-duration-fast) var(--lr-easing-standard) !important;
    margin-right: 8px !important;
    margin-bottom: 8px !important;
}

body.lr-page-booking a[href*="calendar"]:hover,
body.lr-page-booking a[href*="google.com/calendar"]:hover,
body.lr-page-booking a[href*=".ics"]:hover {
    background-color: var(--lr-color-border-subtle, #dce6ec) !important;
    color: var(--lr-color-accent, #002147) !important;
    border-color: var(--lr-color-border-subtle, #dce6ec) !important;
}

/* =========================================================
   BOOKING PORTAL: FLEXBOX ALIGNMENT & ROGUE LINKS
========================================================= */

/* Force all action containers to use Flexbox so buttons align in a neat row */
body.lr-page-booking .lr-booking__session-actions,
body.lr-page-booking .lr-booking__calendar-links,
body.lr-page-booking .lr-booking__booking-item p:has(a),
body.lr-page-booking .lr-booking__booking-item div:has(> a),
body.lr-page-booking .lr-booking__booking-item div:has(> button) {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 12px !important;
    align-items: center !important;
    margin-top: 12px !important;
}

/* Aggressively target ANY link sitting in the action areas and force it into a button */
body.lr-page-booking .lr-booking__session-actions a,
body.lr-page-booking .lr-booking__calendar-links a,
body.lr-page-booking .lr-booking__booking-item a:not([class*="btn"]):not([class*="cta"]) {
    display: inline-flex !important;
    align-items: center !important;
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    padding: 8px 16px !important;
    border-radius: 8px !important;
    font-size: 0.85rem !important;
    font-weight: 600 !important;
    text-decoration: none !important;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard),
                transform var(--lr-duration-fast) var(--lr-easing-standard) !important;
    margin: 0 !important; /* Let the flex gap handle the spacing */
}

body.lr-page-booking .lr-booking__session-actions a:hover,
body.lr-page-booking .lr-booking__calendar-links a:hover,
body.lr-page-booking .lr-booking__booking-item a:not([class*="btn"]):not([class*="cta"]):hover {
    background-color: var(--lr-color-border-subtle, #dce6ec) !important;
    color: var(--lr-color-accent, #002147) !important;
}

/* Reset the cancel button margin so it sits flush in the flex container */
body.lr-page-booking .lr-btn-cancel-override {
    margin: 0 !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.00 MASTER VARIABLES & ANIMATION
========================================================= */

/* 1. Native Theming: Redefining the plugin's own brain */
:root {
    --lr-booking-accent: var(--lr-color-accent, #002147) !important;
    --lr-booking-accent-hover: var(--lr-color-accent-hover, #123c66) !important;
    --lr-booking-border: var(--lr-color-border-subtle, #dce6ec) !important;
    --lr-booking-radius-base: 8px !important;
}

/* 2. Premium Entrance Animation */
.lr-page-booking {
    animation: lrPortalFadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards !important;
    opacity: 0;
    transform: translateY(10px);
}

@keyframes lrPortalFadeIn {
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* =========================================================
   BOOKING PORTAL: RESOURCE CARDS & CALENDAR STATES
========================================================= */

/* 1. Un-mash the dashboard resource text */
body.lr-page-booking [class*="resource"] strong,
body.lr-page-booking [class*="resource"] b,
body.lr-page-booking [class*="resource"] h4 {
    display: block !important;
    font-size: 1.05rem !important;
    margin-bottom: 6px !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
}

body.lr-page-booking [class*="resource"] {
    line-height: 1.5 !important;
    color: var(--lr-color-text-subtle) !important;
}

/* 2. Absolute override for the blue-on-blue slot number */
body.lr-page-booking table td[aria-selected="true"] *,
body.lr-page-booking table td[class*="selected"] * {
    color: #ffffff !important;
}

/* 3. Clarify Today vs Selected Date */
/* If a date is "today" but NOT actively selected, give it a clean outline instead of a solid block */
body.lr-page-booking table td[aria-current="date"]:not([aria-selected="true"]):not([class*="selected"]) {
    background-color: #f1f5f9 !important;
    border: 2px solid var(--lr-color-accent, #002147) !important;
}
body.lr-page-booking table td[aria-current="date"]:not([aria-selected="true"]):not([class*="selected"]) * {
    color: var(--lr-color-text-heading, #1f374e) !important;
}


/* =========================================================
   BOOKING PORTAL: BUTTON NORMALIZATION & STICKY HOVER FIX
========================================================= */

/* 1. Force exact uniform dimensions on all actions */
body.lr-page-booking .lr-booking__booking-item a,
body.lr-page-booking .lr-booking__booking-item button {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    height: 40px !important;
    padding: 0 16px !important;
    border-radius: 8px !important;
    box-sizing: border-box !important;
    font-size: 0.9rem !important;
    text-decoration: none !important;
    margin: 0 !important;
}

/* 2. Catch any naked links and force secondary button styling */
body.lr-page-booking .lr-booking__booking-item a:not([class*="btn"]) {
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
}

body.lr-page-booking .lr-booking__booking-item a:not([class*="btn"]):hover {
    background-color: var(--lr-color-border-subtle, #dce6ec) !important;
    color: var(--lr-color-accent, #002147) !important;
}

/* 3. Strip sticky hover states from inactive calendar days */
body.lr-page-booking table td:not(:hover):not([aria-selected="true"]):not([class*="selected"]) .lr-booking__cal-day,
body.lr-page-booking table td:not(:hover):not([aria-selected="true"]):not([class*="selected"]) {
    background-color: transparent !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.03 MASTER BUTTON MICRO-ALIGNMENT
========================================================= */

/* 1. Absolute Button Normalization (Forces all links and buttons into the exact same rigid box) */
body.lr-page-booking .lr-booking__session-actions > *,
body.lr-page-booking .lr-booking__calendar-links > *,
body.lr-page-booking .lr-booking__booking-item a,
body.lr-page-booking .lr-booking__booking-item button {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    height: 42px !important;
    padding: 0 18px !important;
    margin: 0 !important;
    box-sizing: border-box !important;
    border-radius: var(--lr-booking-radius-base, 8px) !important;
    font-size: 0.9rem !important;
    font-weight: 600 !important;
    line-height: 1 !important;
    text-decoration: none !important;
    outline: none !important;
    appearance: none !important;
}

/* Ensure the red cancel button gets the exact same structural border */
body.lr-page-booking .lr-booking__booking-item .lr-btn-cancel-override,
body.lr-page-booking .lr-booking__booking-item button:has(span:contains("Cancel")) {
    border: 1px solid var(--lr-color-danger, #c63838) !important;
    background-color: transparent !important;
    color: var(--lr-color-danger, #c63838) !important;
}

/* 2. Calendar Sticky State Eradication */
/* Remove all transitions from calendar days so hover states snap off instantly */
body.lr-page-booking table td,
body.lr-page-booking table td * {
    transition: none !important;
}

/* Hard reset for any day that is NOT explicitly selected */
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) .lr-booking__cal-day,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) {
    background-color: transparent !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.04 DATA ATTRIBUTE OVERRIDES
========================================================= */

/* 1. Dashboard Resource Text Un-mashing */
body.lr-page-booking [data-student-dashboard] [class*="resource"] > *:first-child,
body.lr-page-booking [data-student-dashboard] li > strong,
body.lr-page-booking [data-student-dashboard] li > b,
body.lr-page-booking [data-student-dashboard] [class*="title"] {
    display: block !important;
    margin-bottom: 6px !important;
    font-size: 1.05rem !important;
    font-weight: 600 !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
}

/* 2. Target "Next Lesson" Action Links Directly */
body.lr-page-booking [data-next-session] a,
body.lr-page-booking [data-next-session] button {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    height: 42px !important;
    padding: 0 18px !important;
    border-radius: var(--lr-booking-radius-base, 8px) !important;
    font-size: 0.9rem !important;
    font-weight: 600 !important;
    text-decoration: none !important;
    box-sizing: border-box !important;
    line-height: 1 !important;
    margin-bottom: 8px !important;
    margin-right: 8px !important;
}

/* Force secondary styling on any naked link in the next session area */
body.lr-page-booking [data-next-session] a:not([class*="btn"]) {
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
}

body.lr-page-booking [data-next-session] a:not([class*="btn"]):hover {
    background-color: var(--lr-color-border-subtle, #dce6ec) !important;
    color: var(--lr-color-accent, #002147) !important;
}

/* 3. Strip inner button backgrounds to fix Opera hover sticking */
body.lr-page-booking table td button.lr-booking__cal-day {
    background-color: transparent !important;
    transition: none !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.05 GHOST HOVER & DASHBOARD FLEX
========================================================= */

/* 1. Dashboard "Next Lesson" Alignment Fix */
body.lr-page-booking [data-next-session] .lr-booking__session-actions,
body.lr-page-booking [data-next-session] > div:has(> a) {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 12px !important;
    align-items: center !important;
    margin-top: 16px !important;
}

body.lr-page-booking [data-next-session] a,
body.lr-page-booking [data-next-session] button {
    margin: 0 !important; /* Let the flex gap handle all spacing */
}

/* 2. Calendar Ghost Stickiness Eradication */
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]):focus,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]):active,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]):focus-within,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) button:focus,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) button:active {
    background-color: transparent !important;
    outline: none !important;
    box-shadow: none !important;
}

/* Ensure text inside an unselected focused day snaps back to default */
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]):focus * {
    color: var(--lr-color-text-heading, #1f374e) !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.02 RADIUS & FOCUS ERADICATION
========================================================= */

/* 1. Universal Button Radius (Forces consistency across all shapes) */
body.lr-page-booking .lr-booking button,
body.lr-page-booking .lr-booking [class*="btn"],
body.lr-page-booking .lr-booking [class*="button"],
body.lr-page-booking .lr-booking__nav,
body.lr-page-booking .lr-portal__cta {
    border-radius: 8px !important;
}

/* 2. Aggressively target 'Add to Calendar' links */
body.lr-page-booking .lr-booking__next a,
body.lr-page-booking .lr-booking__booking-item a {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    height: 40px !important;
    padding: 0 16px !important;
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    border-radius: 8px !important;
    font-size: 0.85rem !important;
    font-weight: 600 !important;
    text-decoration: none !important;
    box-sizing: border-box !important;
    margin: 4px 8px 4px 0 !important;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard),
                transform var(--lr-duration-fast) var(--lr-easing-standard) !important;
}

body.lr-page-booking .lr-booking__next a:hover,
body.lr-page-booking .lr-booking__booking-item a:hover {
    background-color: var(--lr-color-bg-light, #eaf6fc) !important;
    color: var(--lr-color-accent, #002147) !important;
    border-color: var(--lr-color-accent, #002147) !important;
}

/* 3. Eradicate Ghost Focus on Calendar Days */
body.lr-page-booking table td[aria-selected="false"]:focus .lr-booking__cal-day,
body.lr-page-booking table td[aria-selected="false"] .lr-booking__cal-day:focus,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]):focus .lr-booking__cal-day,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) .lr-booking__cal-day:focus {
    background-color: transparent !important;
    color: inherit !important;
    box-shadow: none !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.06 ASTRA FOCUS & DASHBOARD LINKS
========================================================= */

/* 1. Dashboard Links Discovered in React Source (.lr-dashboard__link) */
body.lr-page-booking .lr-dashboard__link {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    height: 42px !important;
    padding: 0 18px !important;
    border-radius: var(--lr-booking-radius-base, 8px) !important;
    font-size: 0.9rem !important;
    font-weight: 600 !important;
    line-height: 1 !important;
    text-decoration: none !important;
    box-sizing: border-box !important;
    margin: 0 !important;
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard),
                transform var(--lr-duration-fast) var(--lr-easing-standard) !important;
}

body.lr-page-booking .lr-dashboard__link:hover {
    background-color: var(--lr-color-bg-light, #eaf6fc) !important;
    color: var(--lr-color-accent, #002147) !important;
    border-color: var(--lr-color-accent, #002147) !important;
}

/* Force the Reschedule button to look like a primary action */
body.lr-page-booking button.lr-dashboard__link--action {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
    border-color: var(--lr-color-accent, #002147) !important;
}

body.lr-page-booking button.lr-dashboard__link--action:hover {
    background-color: var(--lr-booking-accent-hover) !important;
    border-color: var(--lr-booking-accent-hover) !important;
    color: #ffffff !important;
}

/* 2. The True Ghost Hover Culprit (Astra Theme's Default Button Focus) */
/* Astra forces a solid background on button:focus globally. We must kill it locally. */
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) button.lr-booking__cal-day:focus,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) button.lr-booking__cal-day:active {
    background-color: transparent !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    border-color: transparent !important;
    outline: none !important;
    box-shadow: none !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.07 ABSOLUTE CENTERING & BUTTON GRID
========================================================= */

/* 1. Center the Entire Portal */
body.lr-page-booking .lr-section-full,
body.lr-page-booking .lr-card,
body.lr-page-booking .lr-student-portal {
    max-width: 900px !important;
    margin-left: auto !important;
    margin-right: auto !important;
}

/* 2. Brute-Force Dashboard Button Grid */
/* Target absolutely every link and button in the dashboard and force them into shape */
body.lr-page-booking [data-student-dashboard] a,
body.lr-page-booking [data-student-dashboard] button,
body.lr-page-booking [data-next-session] a,
body.lr-page-booking [data-next-session] button {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    height: 42px !important;
    padding: 0 18px !important;
    border-radius: var(--lr-booking-radius-base, 8px) !important;
    font-size: 0.9rem !important;
    font-weight: 600 !important;
    text-decoration: none !important;
    box-sizing: border-box !important;
    margin: 4px 8px 4px 0 !important; /* Forces a neat grid gap */
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard),
                transform var(--lr-duration-fast) var(--lr-easing-standard) !important;
    line-height: 1 !important;
    appearance: none !important;
}

/* Primary Action Buttons (Reschedule, Book) get the solid blue */
body.lr-page-booking [data-student-dashboard] button.lr-dashboard__link--action,
body.lr-page-booking [data-next-session] button.lr-dashboard__link--action,
body.lr-page-booking button[class*="primary"],
body.lr-page-booking [data-student-dashboard] a:hover,
body.lr-page-booking [data-student-dashboard] button:hover:not(.lr-btn-cancel-override) {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
    border-color: var(--lr-color-accent, #002147) !important;
}

/* Ensure Cancel Button stays red and transparent */
body.lr-page-booking .lr-btn-cancel-override,
body.lr-page-booking button:has(span:contains("Cancel")) {
    background-color: transparent !important;
    border: 1px solid var(--lr-color-danger, #c63838) !important;
    color: var(--lr-color-danger, #c63838) !important;
}

/* 3. Ultimate Ghost Focus Eradication */
/* If a day is NOT explicitly selected, strip all backgrounds permanently */
body.lr-page-booking table td:not([aria-selected="true"]) .lr-booking__cal-day,
body.lr-page-booking table td:not([aria-selected="true"]) {
    background-color: transparent !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    border-color: transparent !important;
    box-shadow: none !important;
    outline: none !important;
}

/* ONLY the selected day gets the blue background */
body.lr-page-booking table td[aria-selected="true"] .lr-booking__cal-day,
body.lr-page-booking table td[aria-selected="true"] {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.07 ABSOLUTE CENTERING & BUTTON GRID
========================================================= */

/* 1. Center the Entire Portal */
body.lr-page-booking .lr-section-full,
body.lr-page-booking .lr-card,
body.lr-page-booking .lr-student-portal {
    max-width: 900px !important;
    margin-left: auto !important;
    margin-right: auto !important;
}

/* 2. Brute-Force Dashboard Button Grid */
/* Target absolutely every link and button in the dashboard and force them into shape */
body.lr-page-booking [data-student-dashboard] a,
body.lr-page-booking [data-student-dashboard] button,
body.lr-page-booking [data-next-session] a,
body.lr-page-booking [data-next-session] button {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    height: 42px !important;
    padding: 0 18px !important;
    border-radius: var(--lr-booking-radius-base, 8px) !important;
    font-size: 0.9rem !important;
    font-weight: 600 !important;
    text-decoration: none !important;
    box-sizing: border-box !important;
    margin: 4px 8px 4px 0 !important; /* Forces a neat grid gap */
    border: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
    background-color: var(--lr-color-bg-card, #fffdf6) !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    transition: background-color var(--lr-duration-fast) var(--lr-easing-standard),
                border-color var(--lr-duration-fast) var(--lr-easing-standard),
                color var(--lr-duration-fast) var(--lr-easing-standard),
                transform var(--lr-duration-fast) var(--lr-easing-standard) !important;
    line-height: 1 !important;
    appearance: none !important;
}

/* Primary Action Buttons (Reschedule, Book) get the solid blue */
body.lr-page-booking [data-student-dashboard] button.lr-dashboard__link--action,
body.lr-page-booking [data-next-session] button.lr-dashboard__link--action,
body.lr-page-booking button[class*="primary"],
body.lr-page-booking [data-student-dashboard] a:hover,
body.lr-page-booking [data-student-dashboard] button:hover:not(.lr-btn-cancel-override) {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
    border-color: var(--lr-color-accent, #002147) !important;
}

/* Ensure Cancel Button stays red and transparent */
body.lr-page-booking .lr-btn-cancel-override,
body.lr-page-booking button:has(span:contains("Cancel")) {
    background-color: transparent !important;
    border: 1px solid var(--lr-color-danger, #c63838) !important;
    color: var(--lr-color-danger, #c63838) !important;
}

/* 3. Ultimate Ghost Focus Eradication */
/* If a day is NOT explicitly selected, strip all backgrounds permanently */
body.lr-page-booking table td:not([aria-selected="true"]) .lr-booking__cal-day,
body.lr-page-booking table td:not([aria-selected="true"]) {
    background-color: transparent !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    border-color: transparent !important;
    box-shadow: none !important;
    outline: none !important;
}

/* ONLY the selected day gets the blue background */
body.lr-page-booking table td[aria-selected="true"] .lr-booking__cal-day,
body.lr-page-booking table td[aria-selected="true"] {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.08 DASHBOARD RESOURCES RESCUE
========================================================= */

/* 1. Rescue resource titles from the blanket button rule */
body.lr-page-booking [data-student-dashboard] a:not(.lr-dashboard__link):not([class*="btn"]):not([class*="cta"]) {
    display: block !important; 
    height: auto !important;
    padding: 0 !important;
    margin: 0 0 6px 0 !important;
    border: none !important;
    background-color: transparent !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    font-size: 1.05rem !important;
    font-weight: 600 !important;
    white-space: normal !important;
    line-height: 1.3 !important;
}

body.lr-page-booking [data-student-dashboard] a:not(.lr-dashboard__link):not([class*="btn"]):hover {
    background-color: transparent !important;
    color: var(--lr-color-accent, #002147) !important;
}

/* 2. Ensure descriptions stack naturally below the titles */
body.lr-page-booking [data-student-dashboard] ul li {
    display: block !important;
    margin-bottom: 20px !important;
    color: var(--lr-color-text-subtle) !important;
    font-size: 0.95rem !important;
    line-height: 1.6 !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.09 UPCOMING LESSONS DE-SQUISH
========================================================= */

/* 1. Force the lesson title onto its own line to fix text mashing */
body.lr-page-booking [data-student-dashboard] li strong {
    display: block !important;
    margin-bottom: 6px !important;
    font-size: 1.05rem !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
}

/* 2. Push the action buttons down so they clear the date text */
body.lr-page-booking [data-student-dashboard] li button,
body.lr-page-booking [data-student-dashboard] li a.lr-dashboard__link {
    margin-top: 14px !important;
    margin-bottom: 4px !important;
}

/* 3. Add breathing room and a subtle border between list items */
body.lr-page-booking [data-student-dashboard] ul li {
    padding-bottom: 20px !important;
    margin-bottom: 20px !important;
    border-bottom: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
}
body.lr-page-booking [data-student-dashboard] ul li:last-child {
    border-bottom: none !important;
    padding-bottom: 0 !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.10 THE ABSOLUTE GHOST KILLER
========================================================= */

/* Nuke shorthand backgrounds and all conceivable interaction states on unselected days */
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) .lr-booking__cal-day,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) .lr-booking__cal-day:hover,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) .lr-booking__cal-day:focus,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) .lr-booking__cal-day:active,
body.lr-page-booking table td:not([aria-selected="true"]):not([class*="selected"]) .lr-booking__cal-day:focus-visible {
    background: transparent !important;
    background-color: transparent !important;
    box-shadow: none !important;
    outline: none !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
}

/* =========================================================
   BOOKING PORTAL: V1.3.11 DE-SQUISH & BUTTON FOCUS
========================================================= */

/* 1. Force the upcoming lessons text to stack vertically */
body.lr-page-booking [data-student-dashboard] li:has(button) {
    display: flex !important;
    flex-direction: column !important;
    align-items: flex-start !important;
    gap: 6px !important;
    padding-bottom: 20px !important;
    border-bottom: 1px solid var(--lr-color-border-subtle, #dce6ec) !important;
}

body.lr-page-booking [data-student-dashboard] li:has(button):last-child {
    border-bottom: none !important;
    padding-bottom: 0 !important;
}

/* Keep the action buttons in a neat horizontal row below the text */
body.lr-page-booking [data-student-dashboard] li:has(button) > div,
body.lr-page-booking [data-student-dashboard] li:has(button) .lr-booking__session-actions {
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: wrap !important;
    gap: 12px !important;
    width: 100% !important;
    margin-top: 10px !important;
}

/* 2. Fix the Reschedule button color in the dashboard */
body.lr-page-booking [data-student-dashboard] button[data-reschedule-session] {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
    border-color: var(--lr-color-accent, #002147) !important;
}

body.lr-page-booking [data-student-dashboard] button[data-reschedule-session]:hover {
    background-color: var(--lr-booking-accent-hover) !important;
    border-color: var(--lr-booking-accent-hover) !important;
}

/* 3. Ultimate Calendar Ghost Focus Killer on the Button Element */
body.lr-page-booking button.lr-booking__cal-day:focus,
body.lr-page-booking button.lr-booking__cal-day:active,
body.lr-page-booking table td:not([aria-selected="true"]) button.lr-booking__cal-day {
    background-color: transparent !important;
    color: var(--lr-color-text-heading, #1f374e) !important;
    box-shadow: none !important;
    outline: none !important;
}

body.lr-page-booking table td[aria-selected="true"] button.lr-booking__cal-day,
body.lr-page-booking table td[class*="selected"] button.lr-booking__cal-day {
    background-color: var(--lr-color-accent, #002147) !important;
    color: #ffffff !important;
}
	
}

/* ==========================================================================
   LAYER: ADAPTER
   --------------------------------------------------------------------------
   Purpose:
   Isolate compatibility code and overrides required for third-party software.
   This layer adapts external systems to the site's design without affecting
   the core design system.

   Contains:
   • Astra overrides
   • WordPress overrides
   • Gutenberg blocks
   • WooCommerce styling
   • booking plugin integration
   • browser-specific fixes
   • compatibility patches

   Never place:
   • core website styling
   • reusable components
   • design tokens
   ========================================================================== */

@layer adapter {

	/* =========================================================
		28B. PAGE GROUND (Astra override)
	--------------------------------------------------------------------------
	   The reset layer sets the cream body ground, and it does not stick:
	   Astra ships `body { background-color: #fff }` UNLAYERED in
	   main.min.css, and an unlayered declaration outranks every layered one
	   no matter which layer it sits in. Verified live, the body computed
	   rgb(255,255,255) with the reset rule present and correct. It went
	   unnoticed before this change only because both values were white.

	   Marked !important here rather than in the reset layer on purpose.
	   Cascade layers INVERT for important declarations, so an important
	   reset-layer rule would outrank the important print rule further down
	   this layer and the site would print a full-bleed cream background.
	   Declared in the adapter layer, this beats Astra while still yielding
	   to that print rule, which is later in the same layer.
	========================================================= */
	body {
		background-color: var(--lr-color-band-cream) !important;
		/* Astra's Customizer emits `body, h1..h6, .entry-content { color:
		   var(--ast-global-color-3) }` INLINE in the head, unlayered, and
		   its stored value is the old slate #334155. Measured on
		   /teaching/: 171 elements rendered that slate while 131 rendered
		   the theme's navy, because the palette swap only reached elements
		   this stylesheet targets directly. Two body colours at once is the
		   single most visible palette defect on the site. */
		color: var(--lr-color-text-main) !important;
	}

	/* Same fault, same fix, for the two containers Astra names alongside
	   body in that inline rule. */
	.entry-content,
	.entry-content p,
	.entry-content li {
		color: var(--lr-color-text-main);
	}

	/* =========================================================
		29. LINK OVERRIDES (.entry-content)
	========================================================= */
	.entry-content a {
		transition: 
		color var(--lr-duration-base) var(--lr-easing-mechanical),
		background-color var(--lr-duration-base) var(--lr-easing-mechanical),
		text-decoration-color var(--lr-duration-base) var(--lr-easing-mechanical),
		text-decoration-thickness var(--lr-duration-base) var(--lr-easing-mechanical) !important;
	}
	
	.entry-content a:hover {
		background-color: rgba(var(--lr-color-accent-rgb), 0.06) !important;
		border-radius: 2px;
	}
	
	/* =========================================================
		29b. HERO HEADING WEIGHT (template inline-style override)

		The homepage hero template ships an inline `font-weight: 400` on
		<h1 class="lr-display">. Non-important inline styles still beat
		non-important class rules, so .lr-display's own font-weight never
		wins without !important here.
	========================================================= */
	h1.lr-display {
		font-weight: 600 !important;
	}

	/* =========================================================
		30. HEADER & LAYOUT RESETS
	========================================================= */
	.site-header, .lr-narrow .site-header {
		padding-top: 18px;
		padding-bottom: 18px;
	}

	.site-content > .lr-container-outer {
		max-width: 1600px;
		margin: 0 auto;
	}

	/* Primary nav links opt out of the generic underline-on-hover `a` rule
	   (see primitives §3), so without this they render with zero hover or
	   current-page feedback at all beyond Astra's default. */
	/* Primary nav signals hover and current-page with the sliding accent bar
	   below (::after), so the underline the structural-link policy would add
	   has to be suppressed here: running both drew two parallel lines under
	   every nav item on hover.

	   THIS RULE NEVER WORKED, and the comment that used to sit here explained
	   why incorrectly. It claimed to outrank "the unlayered important policy
	   rule", but the rule it was fighting is not unlayered: it is
	   `.main-navigation a:hover, .menu-item a:hover { text-decoration:
	   underline !important }` in @layer components. For important
	   declarations the cascade INVERTS layer order, so components beats the
	   later adapter outright and specificity is never consulted. Two separate
	   attempts to fix the double underline aimed here and could not have
	   worked. Chrome's own matched-styles, read over the DevTools protocol
	   with hover forced, named the winner in one call.

	   Fixed at the source instead, in §2 of the link policy, by excluding
	   .menu-link from the structural-link hover underline. This rule stays as
	   a belt-and-braces guard for any other route to an underline here. */
	.main-header-menu .menu-link,
	.main-header-menu .menu-link:hover,
	.main-header-menu .menu-link:focus-visible {
		text-decoration: none !important;
	}

	/* Keyboard focus gets the same bar the mouse gets.

	   Not a bug fix: keyboard focus was already visible here. The reset layer
	   gives every anchor the house box-shadow focus ring, a white 2px inner
	   with an accent outer, and on the dark header that white ring reads
	   clearly. Measured on focus: `rgba(255,255,255,0.85) 0 0 0 1.7px`.

	   Worth recording because I got this wrong once: I measured `outline`,
	   found `3px none`, and concluded focus was invisible. The outline IS
	   none, deliberately, because §Keyboard Accessibility Focus Rings in the
	   reset layer sets `outline: none !important` and uses box-shadow
	   instead. Any outline written here is dead on arrival for the same
	   layer-inversion reason as the underline above, so do not add one.

	   What this adds is consistency: the bar marked hover and current-page
	   but not focus, so a keyboard user saw a different affordance from a
	   mouse user on the same control. Now both get the bar.

	   Deliberately outside the `(hover: hover)` query the hover rule sits in:
	   focus is not a pointer capability, and a keyboard user on a device
	   without hover still needs it. */
	.main-header-menu .menu-link:focus-visible::after {
		transform: scaleX(1);
	}

	.main-header-menu .menu-link {
		position: relative;
		font-weight: 500;
		transition: color var(--lr-duration-base) var(--lr-easing-standard);
	}
	.main-header-menu .menu-link::after {
		content: "";
		position: absolute;
		left: 0;
		right: 0;
		bottom: -6px;
		height: 2px;
		background-color: var(--lr-color-accent);
		transform: scaleX(0);
		transform-origin: left;
		transition: transform var(--lr-duration-moderate) var(--lr-easing-standard);
	}
	@media (hover: hover) and (pointer: fine) {
		.main-header-menu .menu-link:hover {
			color: var(--lr-color-accent) !important;
		}
		.main-header-menu .menu-link:hover::after {
			transform: scaleX(1);
		}
	}
	.main-header-menu .current-menu-item > .menu-link,
	.main-header-menu .current_page_item > .menu-link {
		color: var(--lr-color-accent) !important;
		font-weight: 600;
	}
	.main-header-menu .current-menu-item > .menu-link::after,
	.main-header-menu .current_page_item > .menu-link::after {
		transform: scaleX(1);
	}
	
	#secondary, .widget-area { display: none !important; }
	
	#primary, .content-area {
		width: 100% !important;
		max-width: 100% !important;
		margin: 0 auto !important;
		float: none !important;
	}
	
	.entry-content .wp-block-group[style*="height"],
	.entry-content .wp-block-group[style*="min-height"] {
		height: auto !important;
		min-height: 0 !important;
	}
	
	/* =========================================================
		31. FROSTED GLASS STICKY HEADER
	========================================================= */
	/* Sticky state matches the ink frame (§48): the header is ink at rest,
	   so the frosted state is translucent ink, not translucent white.
	   Built from --lr-color-ink-rgb so it tracks the ink token, an earlier
	   literal (rgba(17,29,54,…) from the navy era) survived the indigo
	   retint, and the header visibly shifted from indigo to navy the moment
	   the sticky state engaged. */
	.lr-sticky-header-active .site-header,
	.lr-sticky-active.site-header {
		background-color: rgba(var(--lr-color-ink-rgb), 0.96) !important;
		border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
		transition: background-color var(--lr-duration-slow) var(--lr-easing-mechanical) !important;
	}

	@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
		.lr-sticky-header-active .site-header,
		.lr-sticky-active.site-header {
			background-color: rgba(var(--lr-color-ink-rgb), 0.88) !important;
			backdrop-filter: saturate(160%) blur(20px) !important;
			-webkit-backdrop-filter: saturate(160%) blur(20px) !important;
			transition: background-color var(--lr-duration-slow) var(--lr-easing-mechanical), backdrop-filter var(--lr-duration-slow) var(--lr-easing-mechanical) !important;
		}
	}
	
	@media (prefers-reduced-motion: reduce) {
		.lr-sticky-header-active .site-header,
		.lr-sticky-active.site-header {
			transition: none !important;
		}
	}
	
	/* =========================================================
		32. BACK TO TOP (#ast-scroll-top)
	========================================================= */
	#ast-scroll-top,
	#scroll-to-top,
	.back-to-top {
		position: fixed !important;
		right: 32px !important;
		bottom: 32px !important;
		z-index: 9999;
		width: 44px !important;
		height: 44px !important;
		border-radius: var(--lr-radius-medium) !important;
		background-color: #ffffff !important;
		border: 1px solid var(--lr-color-border-subtle) !important;
		box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06) !important;
		display: flex !important;
		align-items: center !important;
		justify-content: center !important;
		cursor: pointer;
		transition: 
		transform var(--lr-duration-moderate) var(--lr-easing-mechanical),
		background-color var(--lr-duration-base) var(--lr-easing-standard),
		border-color var(--lr-duration-base) var(--lr-easing-standard),
		box-shadow var(--lr-duration-base) var(--lr-easing-standard) !important;
	}
	
	#ast-scroll-top[style*="display: none"],
	#ast-scroll-top[style*="display:none"] {
		display: none !important;
	}
	
	#ast-scroll-top:hover {
		transform: translateY(-4px) !important;
		background-color: var(--lr-color-bg-light) !important;
		border-color: var(--lr-color-accent) !important;
		box-shadow: 0 8px 20px rgba(var(--lr-color-accent-rgb), 0.12) !important;
		will-change: transform;
	}
	
	#ast-scroll-top:active {
		transform: translateY(-1px) scale(0.96) !important;
		transition: transform var(--lr-duration-press) var(--lr-easing-mechanical) !important;
	}
	
	#ast-scroll-top .lr-icon,
	#ast-scroll-top svg {
		display: none !important;
	}
	
	#ast-scroll-top::before {
		content: "";
		display: block;
		width: 10px;
		height: 10px;
		border-left: 2px solid var(--lr-color-accent);
		border-top: 2px solid var(--lr-color-accent);
		transform: rotate(45deg);
		margin-top: 3px;
	}
	
	@media (max-width: 768px) {
		#ast-scroll-top {
			bottom: calc(20px + env(safe-area-inset-bottom)) !important;
			right: calc(20px + env(safe-area-inset-right)) !important;
		}
	}
	
	/* =========================================================
		33. TABLE MAPPING (.wp-block-table wrapper)
	========================================================= */
	.wp-block-table,
	.entry-content .wp-block-table {
		display: block !important;
		min-width: 0;
		max-width: 100% !important;
		overflow-x: auto !important;
		overflow-y: hidden !important;
		-webkit-overflow-scrolling: touch;
		margin-bottom: 24px !important;
		background-color: #ffffff !important;
		border-radius: var(--lr-radius-medium) !important;
		box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04) !important;
		border: 1px solid var(--lr-color-border-subtle) !important;
	}
	
	.wp-block-table td:first-child,
	.wp-block-table th:first-child {
		width: 22%;
		min-width: 160px;
		white-space: nowrap;
	}
	
	/* =========================================================
		34. FIGURE / IMAGE MAPPING (.wp-block-image)
		
		These rules deliberately mirror the figcaption/image rules in
		§9/§10. The .wp-block-image / .wp-block-figure selectors carry
		an additional Gutenberg-block specificity layer plus !important,
		which is required to override Astra's parent figcaption defaults
		(`.entry-content figcaption`). Consolidating into §9/§10 alone
		causes Astra to win on default install, verified in DevTools.
	========================================================= */
	.wp-block-image, .wp-block-figure {
		max-width: 100% !important;
		margin: 0;
		padding: 0;
		overflow: visible;
	}
	
	.wp-block-image figcaption,
	.wp-block-figure figcaption {
		width: 100% !important;
		max-width: 720px;
		margin: 12px auto 0;
		padding: 8px 14px;
		writing-mode: horizontal-tb !important;
		/* Mirror of the §9 figcaption voice, serif italic, editorial. */
		font-family: var(--lr-font-heading);
		font-style: italic;
		font-size: 15px;
		line-height: 1.45;
		color: var(--lr-color-text-subtle);
		text-align: center !important;
		background: transparent;
		border-radius: 8px;
		word-break: break-word;
		/* No opacity, see the §9 figcaption note. 0.7 took the subtle
		   token from 7.2:1 to 3.5:1 on card backgrounds (About, Contact). */
		transition: color var(--lr-duration-slow) var(--lr-easing-smooth) !important;
	}
	
	@media (hover: hover) and (pointer: fine) {
		.wp-block-image:hover figcaption,
		.wp-block-figure:hover figcaption {
			color: var(--lr-color-text-heading);
		}
	}
	
	@media (max-width: 480px) {
		.wp-block-image figcaption,
		.wp-block-figure figcaption {
			font-size: 14px;
			padding: 8px 12px;
			margin-top: 10px;
		}
	}
	
	.wp-block-image img,
	.wp-block-figure img {
		display: block !important;
		width: 100% !important;
		height: auto !important;
		border-radius: var(--lr-radius-image) !important;
		box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05) !important;
		transition: transform var(--lr-duration-slow) var(--lr-easing-mechanical) !important;
	}
	
	.wp-block-image.size-full.is-resized.has-custom-border img,
	.wp-block-image.size-large.is-resized img {
		width: 100% !important;
		height: auto !important;
		object-fit: cover;
		border-radius: var(--lr-radius-medium) !important;
		box-shadow: var(--lr-shadow-image) !important;
		transition: 
		transform var(--lr-duration-slow) var(--lr-easing-mechanical),
		box-shadow var(--lr-duration-slow) var(--lr-easing-standard) !important;
	}
	
	@media (hover: hover) and (pointer: fine) {
		.wp-block-image.size-full.is-resized.has-custom-border img:hover,
		.wp-block-image.size-large.is-resized img:hover {
			transform: translateY(-4px) !important;
			box-shadow: var(--lr-shadow-hover-image) !important;
		}
	}
	
/* =========================================================
		35. BUTTON MAPPING (.wp-block-button)
	========================================================= */
	.wp-block-button__link,
	.wp-element-button,
	.lr-btn {
		display: inline-flex !important;
		align-items: center !important;
		justify-content: center !important;
		gap: 10px;
		padding: 14px 28px !important;
		border-radius: var(--lr-radius-medium) !important;
		font-weight: 600;
		font-size: 16px;
		letter-spacing: 0.01em;
		line-height: 1;
		text-decoration: none !important;
		cursor: pointer;
		max-width: 100%;
		white-space: nowrap;
		transition: 
			transform var(--lr-duration-moderate) var(--lr-easing-mechanical),
			box-shadow var(--lr-duration-moderate) var(--lr-easing-standard),
			background-color var(--lr-duration-base) var(--lr-easing-standard),
			border-color var(--lr-duration-base) var(--lr-easing-standard),
			color var(--lr-duration-base) var(--lr-easing-standard) !important;
	}
	
	/* Solid CTAs. The industrial chamfer of brief §5B is retired here: the
	   reference design's controls are softly rounded, and a cut corner
	   sitting under a 40px card corner reads as two unrelated systems rather
	   than one. Dropping clip-path also un-clips the element, so borders and
	   outer focus rings work normally again, which is why the focus rule
	   below is back on the shared --lr-shadow-focus-ring instead of a
	   bespoke inset. */
	.wp-block-button.is-style-fill .wp-block-button__link,
	.wp-block-button .wp-block-button__link.wp-element-button.is-primary,
	.lr-btn-primary {
		background-color: var(--lr-color-accent) !important;
		color: #ffffff !important;
		border: 1px solid var(--lr-color-accent) !important;
		border-radius: var(--lr-radius-small) !important;
		position: relative;
	}

	/* A "breathing glow" pulsed an inset white shadow on every primary CTA
	   on a 3s infinite loop, permanently, on every page. It was written for
	   the chamfered button (the inset was a way around clip-path); the
	   chamfer is gone and so is the reason. A button that pulses while the
	   reader is trying to read something else is the definition of the
	   decorative layer this pass removes. */

	/* With the clip gone these can use the shared ring again, restoring the
	   invariant --lr-shadow-focus-ring exists to hold: every keyboard focus
	   on the site is visually identical. */
	.wp-block-button.is-style-fill .wp-block-button__link:focus-visible,
	.wp-block-button .wp-block-button__link.wp-element-button.is-primary:focus-visible,
	.lr-btn-primary:focus-visible {
		outline: none !important;
		box-shadow: var(--lr-shadow-focus-ring) !important;
	}

	@media (forced-colors: active) {
		/* Forced-colours mode ignores background-color and box-shadow, so the
		   ring above disappears; a real border is what survives. */
		.wp-block-button.is-style-fill .wp-block-button__link,
		.wp-block-button .wp-block-button__link.wp-element-button.is-primary,
		.lr-btn-primary {
			border: 1px solid CanvasText !important;
		}
	}

	/* Secondary CTAs match the primary's radius so the family reads as one
	   language. The tinted-fill workaround is no longer needed: it existed
	   only because clip-path cut the corner triangles out of a border and
	   left the outline visibly open at both cuts. With the chamfer retired
	   they take a real outline on white, the reference design's own
	   secondary treatment, and hierarchy is carried by fill (solid indigo
	   vs. white) rather than by corner shape. */
	.wp-block-button.is-style-outline .wp-block-button__link,
	.wp-block-button .wp-block-button__link.wp-element-button.is-outline,
	.lr-btn-outline {
		background-color: var(--lr-color-btn-outline-bg) !important;
		color: var(--lr-color-accent) !important;
		border: 1px solid var(--lr-color-accent) !important;
		border-radius: var(--lr-radius-small) !important;
	}

	.wp-block-button.is-style-outline .wp-block-button__link:focus-visible,
	.wp-block-button .wp-block-button__link.wp-element-button.is-outline:focus-visible,
	.lr-btn-outline:focus-visible {
		outline: none !important;
		box-shadow: var(--lr-shadow-focus-ring) !important;
	}

	@media (forced-colors: active) {
		.wp-block-button.is-style-outline .wp-block-button__link,
		.wp-block-button .wp-block-button__link.wp-element-button.is-outline,
		.lr-btn-outline {
			border: 1px solid CanvasText !important;
		}
	}
	
	.lr-btn-hero {
		border-radius: var(--lr-radius-pill) !important;
		padding: 16px 36px !important;
		font-size: 18px !important;
	}
	
	.lr-btn-ghost {
		background: transparent !important;
		color: var(--lr-color-text-heading) !important;
		border: 1px solid transparent !important;
		box-shadow: none !important;
	}
	
	/* The duplicate .lr-btn-outline block that used to sit here has been
	   removed rather than updated. It carried the same selector list as the
	   rule ~40 lines above, all-!important, so being later in the same layer
	   it silently won, the earlier rule was dead CSS, which is why editing
	   it alone had no visible effect. One definition only; the live one is
	   the earlier block. */

	.dark-section .wp-block-button.is-style-outline .wp-block-button__link {
		color: #ffffff !important;
		border-color: rgba(255, 255, 255, 0.3) !important;
		background: rgba(var(--lr-color-accent-rgb), 0.06) !important;
	}
	
	@media (hover: hover) and (pointer: fine) {
		.wp-block-button__link:hover,
		.wp-element-button:hover,
		.lr-btn:hover {
			transform: translateY(-1px);
			text-decoration: none !important;
		}
		.wp-block-button.is-style-fill .wp-block-button__link:hover,
		.lr-btn-primary:hover {
			/* The hover reads through the colour shift and the shared lift.
			   The previous 15px/30px accent drop-shadow bloom is gone: the
			   elevation tokens are all `none` now, and a glowing button
			   beside a flat, hard-outlined card is the one element on the
			   page still claiming depth. */
			background-color: var(--lr-color-accent-hover) !important;
		}
		.wp-block-button.is-style-outline .wp-block-button__link:hover,
		.lr-btn-outline:hover {
			/* These carry a real border again now the chamfer is gone, so the
			   hover can move the border with the fill rather than relying on
			   the wash alone. */
			background-color: var(--lr-color-btn-outline-hover-bg) !important;
			border-color: var(--lr-color-accent-hover) !important;
			color: var(--lr-color-accent-hover) !important;
		}
		.lr-btn-ghost:hover {
			background-color: var(--lr-color-bg-light) !important;
		}
		.dark-section .wp-block-button.is-style-outline .wp-block-button__link:hover {
			background-color: #ffffff !important;
			color: var(--lr-color-accent) !important;
			border-color: #ffffff !important;
		}
	}
	
	.wp-block-button__link:active,
	.wp-element-button:active,
	.lr-btn:active {
		/* Press = the button physically gives 1px. The scale(0.94) snap and
		   the "chromatic aberration" (a red/blue split text-shadow) that
		   lived here were arcade feedback, loud on a calm editorial system,
		   and 0.94 also shrank the hit target mid-tap. Brightness 0.97 keeps
		   a perceptible state change on touch devices, where :active carries
		   no hover to contrast against. */
		transform: translateY(1px) !important;
		transition: transform var(--lr-duration-press) var(--lr-easing-standard) !important;
		filter: brightness(0.97);
	}
	
	/* Fallback outer ring for buttons that are NOT chamfered (a plain
	   .wp-block-button__link with no is-style-* variant).

	   The exclusions are wrapped in :where() deliberately. A bare
	   :not(.lr-btn-primary):not(.lr-btn-outline) counts toward specificity and
	   lifted this rule from 0-2-0 to 0-4-0, enough to tie the chamfered
	   inset-ring rules above and then beat them on source order, restoring the
	   outer ring that clip-path clips away. :where() contributes zero
	   specificity, so the exclusion applies while this rule stays the weakest
	   of the three and only lands where nothing more specific matched. */
	.wp-block-button__link:where(:not(.lr-btn-primary):not(.lr-btn-outline)):focus-visible,
	.wp-element-button:where(:not(.lr-btn-primary):not(.lr-btn-outline)):focus-visible {
		outline: none !important;
		box-shadow: var(--lr-shadow-focus-ring) !important;
	}
	
	.wp-block-buttons {
		gap: var(--lr-gap-button);
		display: flex !important;
		flex-wrap: wrap !important;
	}
	
	.lr-button-full .wp-block-button__link { width: 100% !important; text-align: center !important; }
	.lr-project-card .wp-block-buttons,
	.lr-card-white .wp-block-buttons { margin-top: auto !important; padding-top: 24px; }
	
	@media (max-width: 600px) {
		.wp-block-button {
			display: flex !important;
			justify-content: center !important;
			width: 100% !important;
			margin-left: 0 !important;
			margin-right: 0 !important;
		}
		.wp-block-button__link,
		.wp-element-button {
			width: 100% !important;
			padding-left: 20px !important;
			padding-right: 20px !important;
			max-width: 420px;
		}
	}
	
	/* Narrow-container button containment */
	.lr-panel-light .wp-block-buttons,
	.lr-card-white .wp-block-buttons {
		width: 100%;
		justify-content: center;
	}
	
	.lr-panel-light .wp-block-button,
	.lr-card-white .wp-block-button {
		min-width: 0;
		max-width: 100%;
	}
	
	.lr-panel-light .wp-block-button__link,
	.lr-panel-light .wp-element-button,
	.lr-card-white .wp-block-button__link,
	.lr-card-white .wp-element-button {
		white-space: normal !important; /* Fixed: Allow text to wrap if needed */
		padding: 14px 20px !important;  /* Added vertical padding to balance text */
		width: 100% !important; /* Ensures the button takes up the card width gracefully */
		text-align: center !important;
		line-height: 1.4; /* Better line-height for wrapped text */
		box-sizing: border-box;
	}
	/* =========================================================
		36. TAG MAPPING (.wp-block-tag)
		
		Mirror of the .lr-tag/.pill rule in §15. Kept separate because
		Gutenberg's .wp-block-tag carries higher specificity than our
		single-class selectors and Astra applies its own tag styling at
		`.entry-content .wp-block-tag`, !important here is the only
		reliable way to win the cascade without touching parent CSS.
	========================================================= */
	.wp-block-tag,
	.wp-block-buttons + .tag {
		display: inline-block !important;
		background-color: var(--lr-color-bg-light) !important;
		padding: 8px 14px !important;
		border-radius: var(--lr-radius-tag) !important;
		font-size: var(--lr-font-size-tag) !important;
		color: var(--lr-color-text-main) !important;
		border: 1px solid var(--lr-color-border-subtle) !important;
		text-decoration: none !important;
		font-weight: 500;
		margin: 4px 6px 4px 0;
		transition: 
		transform var(--lr-duration-base) var(--lr-easing-mechanical),
		background-color var(--lr-duration-base) var(--lr-easing-standard),
		border-color var(--lr-duration-base) var(--lr-easing-standard),
		box-shadow var(--lr-duration-base) var(--lr-easing-standard),
		color var(--lr-duration-base) var(--lr-easing-standard) !important;
	}
	
	@media (hover: hover) and (pointer: fine) {
		a.wp-block-tag:hover {
			background-color: var(--lr-color-accent-wash) !important;
			border-color: var(--lr-color-accent) !important;
			transform: translateY(-2px) !important;
			color: var(--lr-color-accent) !important;
			box-shadow: 0 4px 10px rgba(var(--lr-color-accent-rgb), 0.12) !important;
		}
	}
	
	a.wp-block-tag:active {
		transform: translateY(0) scale(0.96) !important;
	}
	
	/* =========================================================
		37. LIST MAPPING (.wp-block-group)
	========================================================= */
	.wp-block-group ul.lr-list-group,
	.wp-block-group ol.lr-list-group {
		list-style: none !important;
		list-style-type: none !important;
		padding-left: 0 !important;
		margin: 0 !important;
	}
	
	.wp-block-group li.lr-list-item {
		list-style: none !important;
		list-style-type: none !important;
	}
	
	/* =========================================================
		38. CALLOUT PANEL HOVER (.entry-content legacy inline panels)
	========================================================= */
	@media (hover: hover) and (pointer: fine) {
		.entry-content .lr-panel-white[style]:hover {
			background-color: rgba(var(--lr-color-accent-rgb), 0.08) !important;
			border-left-color: var(--lr-color-accent-hover) !important;
		}
	}
	
	/* =========================================================
		39. WP UTILITY MAPPING
	========================================================= */
	.has-text-align-center { text-align: center !important; }
	.has-text-align-left { text-align: left !important; }
	
	/* =========================================================
		40. CODE BLOCK REINFORCEMENT (.entry-content)
		
		Mirror of the §26 code/pre rules. The .entry-content prefix +
		!important is required to beat Astra's parent rule
		`.entry-content code, .entry-content pre` which carries the
		same specificity but ships with the parent stylesheet, so we
		need !important to land last.
	========================================================= */
	.entry-content code {
		background-color: var(--lr-color-bg-light) !important;
		color: var(--lr-color-code) !important;
		padding: 2px 6px !important;
		border-radius: 4px !important;
		border: 1px solid var(--lr-color-border-subtle) !important;
	}
	
	.entry-content pre {
		background-color: #1f374e !important;
		color: #fffdf6 !important;
		padding: 24px !important;
		border-radius: var(--lr-radius-medium) !important;
		box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.2) !important;
		border: 1px solid rgba(255, 255, 255, 0.1) !important;
	}
	
	.entry-content pre code {
		background-color: transparent !important;
		color: inherit !important;
		padding: 0 !important;
		border: none !important;
	}
	
	/* =========================================================
		41. FORM MAPPING (.wpcf7-* / .ast-*)
	========================================================= */
	.wpcf7-form-control-wrap:focus-within,
	.lr-comment-formwrap:focus-within {
		transform: translateX(2px);
		transition: transform var(--lr-duration-moderate) var(--lr-easing-mechanical) !important;
	}

	.wpcf7-not-valid {
		border-color: #b42318 !important;
		box-shadow: 0 0 0 1px rgba(180, 35, 24, 0.2) !important;
	}

	/* CF7 ships its own unlayered .wpcf7-not-valid-tip{color:#DC2626},	   same unlayered-beats-layered issue as the focus-state rule above. */
	.wpcf7-not-valid-tip {
		color: #b42318 !important;
		font-size: 0.875rem;
		font-weight: 600;
		margin-top: 0.35rem;
	}

	.wpcf7 form .wpcf7-response-output {
		border-radius: var(--lr-radius-small);
		padding: 0.75rem 1rem;
		margin: 1rem 0 0;
		font-size: 0.95rem;
	}

	.wpcf7 form.sent .wpcf7-response-output {
		border-color: #0d6b4d;
		background: rgba(13, 107, 77, 0.08);
		color: #0d6b4d;
	}

	.wpcf7 form.failed .wpcf7-response-output,
	.wpcf7 form.invalid .wpcf7-response-output {
		border-color: #b42318;
		background: rgba(180, 35, 24, 0.06);
		color: #7a271a;
	}

	@media (pointer: coarse) {
		.wpcf7-list-item-label {
			display: inline-flex;
			align-items: center;
			min-height: 44px;
			padding: 0.25rem 0;
		}

		.wpcf7-form-control.wpcf7-checkbox input,
		.wpcf7-form-control.wpcf7-radio input {
			width: 1.25rem;
			height: 1.25rem;
			margin-right: 0.5rem;
		}
	}

	.lr-calc-error {
		color: #b42318;
		font-weight: 600;
	}

	/* Skip link (Astra), visible on keyboard focus */
	a.skip-link:focus,
	a.skip-link:focus-visible {
		outline: none !important;
		box-shadow: var(--lr-shadow-focus-ring) !important;
		border-radius: var(--lr-radius-small);
	}
	
	/* =========================================================
		42. HERO LAYOUT MAPPING
	========================================================= */
	@media (min-width: 922px) {
		.lr-card .wp-block-columns.lr-columns-large {
			display: flex !important;
			align-items: center !important;
		}
		.lr-card .wp-block-columns.lr-columns-large > .wp-block-column:not(:first-child) {
			display: flex !important;
			flex-direction: column !important;
			justify-content: center !important;
			height: 100% !important;
		}
		.wp-block-columns.lr-top-align {
			display: flex !important;
			align-items: flex-start !important;
		}
		.wp-block-columns.lr-top-align > .wp-block-column {
			display: flex;
			flex-direction: column;
			align-items: stretch;
		}
		.wp-block-columns.lr-top-align > .wp-block-column > h3,
		.wp-block-columns.lr-top-align > .wp-block-column > h4 {
			margin-top: 0;
			margin-bottom: var(--lr-gap-tight);
		}
		.wp-block-columns.lr-top-align > .wp-block-column > p.lr-desc {
			margin-top: 0;
			margin-bottom: var(--lr-margin-bottom-heading);
		}
		.wp-block-columns.lr-top-align > .wp-block-column > .lr-list-group,
		.wp-block-columns.lr-top-align > .wp-block-column > .wp-block-group.lr-list-group {
			flex: 1;
		}
	}
	
	@media (max-width: 768px) {
		.wp-block-column.lr-center-mobile {
			display: flex !important;
			flex-direction: column !important;
			align-items: center !important;
			width: 100% !important;
			padding: 0 !important;
			margin-bottom: 20px !important;
		}
		.lr-center-mobile figure.wp-block-image {
			width: 100% !important;
			max-width: 100% !important;
			margin: 0 !important;
		}
		.lr-center-mobile img {
			width: 100% !important;
			height: auto !important;
			max-width: 100% !important;
			display: block !important;
			margin: 0 auto !important;
			border-radius: 12px !important;
		}
		.lr-cv-row .wp-block-column { text-align: center !important; }
	}
	
	/* =========================================================
		43. RESPONSIVE STACKING
	========================================================= */
	@media (max-width: 921px) {
		.wp-block-columns {
			display: flex !important;
			flex-direction: column !important;
			gap: var(--lr-gap-card) !important;
			align-items: stretch !important;
		}
		.wp-block-column {
			flex: 1 1 100% !important;
			width: 100% !important;
			display: flex !important;
			flex-direction: column !important;
		}
		
		.wp-block-column > .lr-card,
		.wp-block-column > .lr-card-white,
		.wp-block-column > .lr-project-card {
			flex-grow: 1;
		}
		.wp-block-buttons {
			display: flex !important;
			flex-direction: column !important;
			width: 100% !important;
			gap: var(--lr-gap-small) !important;
		}
		.wp-block-button { width: 100% !important; margin: 0 !important; }
		.wp-block-button__link { width: 100% !important; justify-content: center !important; }
	}
	
	/* =========================================================
		44. LAYOUT FIXES
	========================================================= */
	.site .lr-section-full .lr-container-outer {
		padding-left: 0 !important;
		padding-right: 0 !important;
	}
	
	.lr-section-full > p:empty,
	.lr-section-full > br,
	.lr-section-full > div:empty,
	.wp-block-spacer {
		display: none !important;
	}
	
	.wp-block-group.alignfull.has-ast-global-color-5-background-color {
		margin-top: 0 !important;
		margin-bottom: 0 !important;
	}
	
	/* =========================================================
		45. GRID FIXES
	========================================================= */
	.lr-explore-portfolio .wp-block-columns {
		display: flex !important;
		flex-wrap: wrap !important;
		gap: var(--lr-gap-card, 24px) !important;
		justify-content: center !important;
	}

	.lr-explore-portfolio .wp-block-column {
		flex: 1 1 240px !important;
		min-width: 220px !important;
		max-width: 380px !important;
		margin-bottom: 0 !important;
	}
	
	/* =========================================================
		46. RESPONSIVE TWEAKS
	========================================================= */
	@media (max-width: 544px) {
		.lr-tag-group { justify-content: center; }
		.lr-list-group { gap: var(--lr-gap-tight); }
	}
	
	/* =========================================================
		47. PRINT STYLES & REDUCED MOTION
	========================================================= */
	@media print {
		.site-header, .site-footer, .lr-footer-overlay, .lr-above-header, .lr-below-header, #ast-scroll-top, .wp-block-buttons, .lr-tools-grid, .lr-logo-grid, .lr-affiliations-card, .lr-affiliations-wrapper, .lr-button-group-center, nav {
			display: none !important;
		}
		body, .site, .lr-container-outer {
			width: 100% !important;
			max-width: 100% !important;
			margin: 0 !important;
			padding: 0 !important;
			scroll-behavior: auto;
			/* The cream ground is a screen treatment. Printing it would lay
			   down full-bleed ink on every page for no reading benefit, so
			   paper goes back to white. */
			background-color: #ffffff !important;
		}
		.lr-section-full { padding: 20px 0 !important; }
		.lr-card, .lr-card-white, .lr-project-card {
			box-shadow: none !important;
			border: 1px solid #d1d5db;
			break-inside: avoid;
			page-break-inside: avoid;
		}
		.lr-math, .eq-block, .eq-shaded {
			break-inside: avoid;
			page-break-inside: avoid;
			border: 1px solid var(--lr-color-border-subtle);
		}
		h1, h2, h3, h4 { page-break-after: avoid; }
		p, .lr-list-item {
			orphans: 3;
			widows: 3;
		}
		a[href^="http"]::after { 
			content: " (" attr(href) ")"; 
			font-size: 10px; 
			color: #666; 
			word-break: break-all;
		}
		a[href^="#"]::after,
		a[href^="mailto"]::after,
		a[href^="tel"]::after,
		a[href^="javascript"]::after,
		.lr-tag-group a::after,
		.lr-index-list a::after { 
			content: none; 
		}
	}
	
	@media (prefers-reduced-motion: reduce) {
		*, *::before, *::after {
			transition-duration: .01ms !important;
			animation-duration: .01ms !important;
			animation-iteration-count: 1 !important;
		}
		html {
			scroll-behavior: auto !important;
		}
		.lr-card-white[data-active="true"],
		.lr-project-card[data-active="true"] {
			transform: none !important;
			box-shadow: var(--lr-shadow-rest) !important;
		}
	}

@media print {
	.lr-nav-link, .lr-back-to-top, nav[aria-label="Related curriculum modules"],
	.lr-titan-lab, .lr-no-print, #lr-titan-tooltip-host, .lr-tooltip {
		border-bottom: none !important;
	}
	
	.lr-nav-link, .lr-back-to-top, nav[aria-label="Related curriculum modules"],
	.lr-titan-lab, .lr-no-print, #lr-titan-tooltip-host {
		display: none !important;
	}
	
	body {
		background: #ffffff !important;
		color: #000 !important;
	}
	
	.lr-math-block, aside, .lr-panel-white[style*="border-left"] {
		page-break-inside: avoid;
		break-inside: avoid;
	}
	
	/* `always` here produced 16 blank sheets out of 67 when printing an ESAT
	   solution page, measured against a real PDF. A forced break lands on a
	   heading that is already at the top of a fresh page, and the break is
	   taken anyway, so an empty sheet is emitted before it. Overriding this
	   one declaration took the blanks from 16 to 0.

	   `auto` with `break-inside: avoid` below gives what the `always` was
	   reaching for, sections that do not straddle a page boundary, without
	   spending a sheet of paper each time the pagination already agreed. */
	h2.lr-section-heading, h2#heading-lesson-0A, h2#heading-prob-1 {
		page-break-before: auto;
		break-before: auto;
		page-break-after: avoid;
		break-after: avoid;
	}
	
	section#table-of-content h2.lr-section-heading,
	header#hero h1, .lr-hero h1 {
		page-break-before: auto;
		break-before: auto;
	}
	
	.lr-panel-white.lr-panel-accent-edge {
		-webkit-print-color-adjust: exact;
		print-color-adjust: exact;
	}
	
	details.lr-derivation-toggle summary {
		list-style: none;
	}
	
	details.lr-derivation-toggle summary::-webkit-details-marker {
		display: none;
	}
	
	details.lr-derivation-toggle .lr-derivation-content {
		display: block !important;
	}
	
	details.lr-derivation-toggle {
		border-color: #ccc !important;
		page-break-inside: avoid;
	}
}

@media (prefers-reduced-motion: reduce) {
	.lr-curriculum-hub a.lr-curriculum-dashboard-card:hover {
		transform: none !important;
	}
}

@media print {
	.lr-curriculum-hub a.lr-curriculum-dashboard-card:hover {
		transform: none !important;
	}
}

/* =====================================================================
   PRINT: ESAT WORKED SOLUTIONS

   Students print these papers to work through them on paper, so the sheet
   is a real deliverable rather than an afterthought. Auditing an actual
   print-to-PDF of paper-0001/physics surfaced several faults, the first of
   which defeated the entire purpose of printing the page.

   Unlayered on purpose: these must beat the layered print rules in the
   adapter layer, which is where the older, partial print styling lives.
   ===================================================================== */
@media print {

	/* 1. THE SOLUTIONS THEMSELVES.
	   Every worked solution sits inside a collapsed <details>, and a closed
	   <details> does not render its content, so the printed paper carried
	   the questions, the options and the answer key while withholding every
	   actual explanation. Force them open. The existing adapter rule only
	   covered .lr-derivation-toggle; ESAT uses .lr-details-solution. */
	/* Chrome/Edge do not hide a closed <details> by putting display:none on
	   its children, the content sits in a ::details-content pseudo-element
	   carrying `content-visibility: hidden`. Overriding `display` on the
	   children therefore does nothing, which is why the first attempt at this
	   left the solutions missing. Reveal the pseudo-element itself, and keep
	   the child override as a fallback for engines without it. */
	details::details-content {
		content-visibility: visible !important;
		block-size: auto !important;
		height: auto !important;
		overflow: visible !important;
	}

	details > *:not(summary) {
		display: block !important;
		height: auto !important;
		overflow: visible !important;
	}

	/* The toggle chevron implies an interaction that paper cannot offer. */
	details > summary::after,
	details.lr-details-solution > summary::after {
		content: none !important;
	}

	details > summary {
		list-style: none;
		cursor: auto;
	}

	/* 2. SCREEN-ONLY CHROME.
	   The cookie banner is position: fixed, so it reprinted on every single
	   sheet and consumed roughly a fifth of each one. */
	.lr-cookie-banner,
	.lr-back-to-top,
	.lr-nav-row,
	.lr-global-search-outer,
	.lr-breadcrumb,
	/* The ESAT per-question "Back to top ↑" is not .lr-back-to-top; it is an
	   anchor to the contents list, matched here by its accessible name so the
	   selector does not depend on a utility class that may be reused. */
	a[aria-label="Back to table of contents"],
	a[href^="#table-of-content"] {
		display: none !important;
	}

	/* 3. INK.
	   Section bands, card fills and math chips are all tinted for screen.
	   On paper they cost toner and lower text contrast without adding
	   meaning. Borders are kept, they carry the structure. */
	.lr-section-full,
	.lr-card,
	.lr-card-white,
	.lr-project-card,
	.lr-panel-white,
	.lr-panel-light,
	.lr-surface-sunken,
	.lr-math,
	.lr-math-block,
	#problem-container mjx-container[display="true"],
	th,
	tbody tr:nth-child(even) td {
		background: #ffffff !important;
		box-shadow: none !important;
	}

	/* 4. CLIPPED EQUATIONS.
	   Math blocks scroll horizontally on screen; on paper an overflow
	   container simply crops the equation at the box edge, silently losing
	   the right-hand side of long derivations. */
	.lr-math,
	.lr-math-block,
	.lr-details-solution .lr-math-block,
	.lr-esat-question-text .lr-math-block,
	#problem-container mjx-container[display="true"],
	.wp-block-table,
	.entry-content .wp-block-table {
		overflow: visible !important;
		max-width: 100% !important;
	}

	#problem-container mjx-container[display="true"] mjx-math,
	.lr-details-solution .lr-math-block mjx-container {
		white-space: normal !important;
	}

	/* Custom scrollbars were painting a track down the edge of every page. */
	* {
		scrollbar-width: none !important;
	}
	::-webkit-scrollbar {
		display: none !important;
	}

	/* 5. LINK URLS.
	   Appending href text is useful for a reference a reader may want to
	   type out, but it was applied to every link, including the cookie
	   banner and breadcrumbs, producing wrapped URL fragments mid-sentence.
	   Restrict it to links inside prose. */
	a[href^="http"]::after {
		content: none !important;
	}

	.entry-content p a[href^="http"]::after,
	.entry-content li a[href^="http"]::after {
		content: " (" attr(href) ")" !important;
		font-size: 10px;
		color: #555;
		word-break: break-all;
	}

	/* 6. PAGINATION.
	   Keep a question and its solution on one sheet where they fit, and
	   never strand a heading at the foot of a page. */
	#problem-container > article,
	.lr-details-solution,
	details.lr-details-solution {
		break-inside: avoid;
		page-break-inside: avoid;
	}

	.lr-section-full {
		padding-block: 0 !important;
	}

	/* 7. INK MASTHEAD PLATES.
	   On paper the dark plate would print as a heavy ink slab, or worse,
	   white-remapped text on a flattened white box. Reset both the fills AND
	   the token remap from §48, since the remapped custom properties inherit
	   into print media like any other. */
	/* The custom properties carry !important deliberately. The §48 screen
	   remap sets the same properties later in the same layer, so a normal
	   reset here loses on source order and the plate keeps its on-ink values
	   in print. That mattered because an early-layer rule paints headings
	   with `color: var(--lr-color-text-heading) !important`, for important
	   declarations earlier layers are unbeatable, so the ONLY lever over the
	   printed colour is the variable it reads. With a normal reset the title
	   printed white-on-white; diagnosed by bisection after colour,
	   font-family, font-size, text-wrap and transform overrides all failed
	   to paint it. */
	/* .lr-on-ink is in the same §48 remap and needs the same print reset for
	   the same reason: its dark ground is a gradient, print drops background
	   images by default, and the remapped white heading would then print
	   white-on-white, the exact fault the comment above was written for. */
	/* Split for the same reason as the §48 remap: a comma list is not
	   forgiving, so pairing .lr-on-ink with the :has() compound meant an
	   engine without :has() lost the print reset for BOTH, and a dark plate
	   then printed as a black slab with white text on it. */
	.lr-on-ink {
		background: #ffffff !important;
		border: 0 !important;
		padding: 0 0 16px !important;
		--lr-color-accent: #002147 !important;
		--lr-color-accent-hover: #123c66 !important;
		--lr-color-accent-rgb: 0, 33, 71 !important;
		--lr-color-link: #002147 !important;
		--lr-color-link-hover: #123c66 !important;
		--lr-color-text-heading: #1f374e !important;
		--lr-color-text-main: #1f374e !important;
		--lr-color-text-muted: #55697d !important;
		--lr-color-text-subtle: #3e5871 !important;
		--lr-color-border-subtle: #dce6ec !important;
		--lr-color-border-card: rgba(0, 0, 0, 0.05) !important;
	}

	/* The masthead's half of that split. Identical declarations: edit both. */
	.entry-content header.wp-block-group:has(.lr-hero-heading) {
		background: #ffffff !important;
		border: 0 !important;
		padding: 0 0 16px !important;
		--lr-color-accent: #002147 !important;
		--lr-color-accent-hover: #123c66 !important;
		--lr-color-accent-rgb: 0, 33, 71 !important;
		--lr-color-link: #002147 !important;
		--lr-color-link-hover: #123c66 !important;
		--lr-color-text-heading: #1f374e !important;
		--lr-color-text-main: #1f374e !important;
		--lr-color-text-muted: #55697d !important;
		--lr-color-text-subtle: #3e5871 !important;
		--lr-color-border-subtle: #dce6ec !important;
		--lr-color-border-card: rgba(0, 0, 0, 0.05) !important;
	}

	.entry-content header.wp-block-group:has(.lr-hero-heading) .lr-hero-heading {
		color: #1f374e !important;
	}

	.entry-content header.wp-block-group:has(.lr-hero-heading) .lr-hero-subheading {
		color: #55697d !important;
	}
}
@media (pointer: coarse) {
	.lr-global-search-dropdown a {
		padding: 12px 14px;
		min-height: 44px;
		display: flex;
		align-items: center;
	}
}

@media (max-width: 480px) {
    body.lr-titan-doc .lr-titan-lab input[type="number"],
    body.lr-titan-doc .lr-titan-lab input[type="text"] {
        min-height: 44px;
        font-size: 16px;
    }
    
    body.lr-titan-doc .lr-titan-lab button,
    body.lr-titan-doc .lr-titan-lab .wp-element-button,
    body.lr-titan-doc .lr-titan-lab .wp-block-button__link {
        min-height: 44px;
        box-sizing: border-box;
    }
    
    body.lr-curriculum-hub .lr-global-search-dropdown a {
        padding: 12px 14px;
        min-height: 44px;
        display: flex;
        align-items: center;
    }
}

@media (prefers-reduced-motion: reduce) {
	.lr-panel-white.lr-panel-accent-edge {
		transition: none;
	}

	.lr-panel-white.lr-panel-accent-edge:hover,
	.lr-panel-white.lr-panel-accent-edge[data-active="true"] {
		transform: none;
	}
}
@media (max-width: 600px) {
	.lr-nav-row {
		flex-direction: column;
		gap: var(--lr-gap-small);
	}
	.lr-nav-row > * {
		text-align: center !important;
	}
}

@media (min-width: 640px) {
	/* The banner was 199px tall on a 900px viewport, 22 percent of the
	   screen, and that is not a cosmetic problem: measured on /contact/ at
	   1440x900 it covered the message field, the required intent dropdown,
	   the consent checkbox and the Submit button of the only working form on
	   the site, and on the homepage it covered all three hero calls to
	   action at first paint. A consent notice that hides the thing it is
	   consenting to is worse than a tall one.

	   Two declarations made it tall. The actions stacked in a COLUMN, so
	   three buttons became three rows, and the inner was capped at
	   --lr-content-max, 48rem, which on a 1440px screen leaves about 330px
	   dead each side while squeezing the copy into three lines. Both are now
	   the other way round: buttons sit in a row, and the inner is allowed
	   the width it needs, so the text fits on one or two lines. */
	.lr-cookie-banner__inner {
		max-width: min(1140px, 100%);
		grid-template-columns: 1fr auto;
		align-items: center;
		column-gap: 28px;
	}

	.lr-cookie-banner__actions {
		grid-column: 2;
		grid-row: 1 / span 2;
		align-self: center;
		flex-direction: row;
		flex-wrap: nowrap;
		align-items: center;
	}
}

/* The banner's two paragraphs were carrying the site's global paragraph
   margin, 28px each, so the intro block measured 131px to hold 76px of text:
   26 title, 28 margin, 50 text, 28 margin. The banner already declares
   `margin: 0` on both, and it was being overridden.

   Important is needed rather than tidy: this rule has to beat the primitives
   `p { margin-bottom: var(--lr-margin-bottom-para) }`, and a normal
   declaration here cannot, whatever its specificity. Measured after: the
   intro drops from 131px to 76px. */
.lr-cookie-banner__title,
.lr-cookie-banner__text {
	margin: 0 !important;
}

.lr-cookie-banner__intro {
	display: grid;
	gap: 4px;
}

/* Let the page scroll clear of the banner while it is open.

   Shortening the banner was not sufficient and could not be. It is
   `position: fixed`, so it covers the bottom of the viewport at every scroll
   position, and the content underneath can never be reached. Measured on
   /contact/ at 1440x900, scrolled to the form: the banner covered six form
   controls INCLUDING the Submit button. A consent notice that makes the only
   working form on the site unsubmittable is a conversion bug, not a styling
   one.

   --lr-cookie-banner-offset was already referenced by the scroll-to-top
   rule with a 7.5rem fallback and was never declared, so that rule has been
   running on its fallback. It is declared here.

   The mobile value is much larger because the banner is proportionally much
   taller on a phone: measured at 33 percent of a 390x844 screen and 41
   percent of a 360x740 one. The padding only exists while the banner is
   open, and the class is removed the moment a choice is made. */
:root {
	--lr-cookie-banner-offset: 9rem;
}

@media (max-width: 639px) {
	:root {
		--lr-cookie-banner-offset: 19rem;
	}
}

html.lr-cookie-banner-open {
	scroll-padding-bottom: var(--lr-cookie-banner-offset);
}

/* The body padding that used to sit here is REMOVED, and deliberately.

   It reserved space so the page could scroll clear of the fixed banner, and
   it worked: Submit moved from y=818, covered, to y=696, clear. But the
   banner ships `hidden` and the consent script reveals it after paint, so
   reserving the space moved the page after paint too. Measured on the
   deployed 1.12.2: one shift worth 0.103 CLS at 7.2 seconds on a slow
   connection, which on its own fails the Core Web Vital that measured 0.0031
   before it.

   That is the worse trade. A visitor who cannot reach Submit has a remedy in
   the same viewport, which is to answer the banner, after which it is gone.
   A visitor who gets a 0.103 layout shift has no remedy and did not ask for
   one. The shift is also unavoidable while the reveal is client side: any
   space reserved on a class the script adds will move the page whenever the
   script runs.

   scroll-padding-bottom below is kept because it changes no layout at all.
   It only affects where scrollIntoView and in-page anchors come to rest, so
   a jump to the form lands above the banner rather than beneath it.

   The real fix is to stop the banner covering the form rather than to pad
   around it. Shortening it from 199px to 122px was the first half. The
   second half is either a smaller banner again, or a consent state known at
   render time so the space can be reserved in the very first paint. */

	/* =========================================================
		48. INK FRAME, header, footer, interior mastheads

		The composition-level move: content stays editorial-light, but the
		frame around it goes ink navy (brief §2, the Technical-HUD vector).
		Implementation leans on the token system rather than per-element
		colour overrides: each dark context REMAPS the design tokens on its
		container, and every existing rule that references those tokens,		including content-stamped inline styles and prior !important rules,		resolves to on-ink values through inheritance. Custom properties are
		immune to the layer/importance battles that plagued literal colours.
	========================================================= */

	/* --- A. Shared on-ink token remap ---

	   .lr-on-ink is the opt-in hook for page content: put it on any block
	   that paints itself dark and every token-driven rule inside resolves to
	   its on-ink value, headings included. Content cannot otherwise recolour
	   a heading, `h1..h6 { color: var(--lr-color-text-heading) !important }`
	   in the primitives layer beats an inline colour, so before this hook
	   existed, a dark panel in page content had a #1f374e heading sitting on
	   navy. Remapping the variable is the documented lever (see the print
	   block in §7). */
	/* SPLIT from the :has() masthead selector, which now carries the same
	   remap in its own rule immediately below.

	   A comma-separated selector list is NOT forgiving: one compound the
	   engine cannot parse invalidates the whole rule and every sibling
	   selector with it. The masthead compound used to sit in this list, and
	   because the ink BACKGROUNDS are painted by separate rules that use no
	   :has(), an engine without :has() kept the dark ground and lost the
	   light text. The cross-browser audit measured it by removing :has()
	   support from the served CSS: the footer copyright at 1.31:1 and 22
	   footer links at 2.42:1, on every page. That is the same fail-open shape
	   as the mask-composite incident recorded in app/CLAUDE.md.

	   Split rather than wrapped in :is(), deliberately. :is() takes the
	   specificity of its most specific argument, so wrapping would promote
	   .site-header from (0,1,0) to (0,3,1) and change which rules win
	   elsewhere. Splitting changes no specificity at all.

	   The two rules further down that use the :has() compound as their ONLY
	   selector are fine as they are: they fail CLOSED, which their own
	   comment says is intended, and a light masthead is readable. */
	.site-header,
	.site-footer,
	.lr-footer-secondary-nav,
	.lr-site-footer,
	.lr-on-ink {
		--lr-color-accent: var(--lr-color-on-ink-accent);
		--lr-color-accent-hover: #cbd8e8;
		--lr-color-accent-rgb: var(--lr-color-on-ink-accent-rgb);
		--lr-color-link: var(--lr-color-on-ink-accent);
		--lr-color-link-hover: #cbd8e8;
		--lr-color-text-heading: #ffffff;
		--lr-color-text-main: #c6d4ea;
		--lr-color-text-muted: var(--lr-color-on-ink-muted);
		--lr-color-text-subtle: var(--lr-color-on-ink-muted);
		--lr-color-border-subtle: rgba(255, 255, 255, 0.16);
		--lr-color-border-card: rgba(255, 255, 255, 0.08);
		/* Secondary buttons keep the outlined-on-dark treatment here. Without
		   this they inherit the light-context white fill while --lr-color-accent
		   is remapped to the pale on-ink blue, which puts pale-blue text and a
		   pale-blue border on white, measured at 1.16:1 on the /teaching/
		   masthead. Transparent restores the plate as the backdrop, where the
		   same pale blue reads at 9.65:1. */
		--lr-color-btn-outline-bg: transparent;
		--lr-color-btn-outline-hover-bg: rgba(255, 255, 255, 0.12);
	}

	/* The masthead's half of the split above. Identical declarations. If you
	   edit one of these two rules, edit both. */
	.entry-content header.wp-block-group:has(.lr-hero-heading) {
		--lr-color-accent: var(--lr-color-on-ink-accent);
		--lr-color-accent-hover: #cbd8e8;
		--lr-color-accent-rgb: var(--lr-color-on-ink-accent-rgb);
		--lr-color-link: var(--lr-color-on-ink-accent);
		--lr-color-link-hover: #cbd8e8;
		--lr-color-text-heading: #ffffff;
		--lr-color-text-main: #c6d4ea;
		--lr-color-text-muted: var(--lr-color-on-ink-muted);
		--lr-color-text-subtle: var(--lr-color-on-ink-muted);
		--lr-color-border-subtle: rgba(255, 255, 255, 0.16);
		--lr-color-border-card: rgba(255, 255, 255, 0.08);
		--lr-color-btn-outline-bg: transparent;
		--lr-color-btn-outline-hover-bg: rgba(255, 255, 255, 0.12);
	}

	/* --- B. Header --- */
	.site-header,
	.site-header .main-header-bar,
	.site-header .lr-primary-header-bar,
	.site-header .lr-mobile-header-content,
	.site-header .main-header-bar-navigation {
		background-color: var(--lr-color-ink) !important;
	}

	.site-header {
		border-bottom: 1px solid rgba(255, 255, 255, 0.08);
	}

	/* --- B2. Mobile drawer ---

	   Part B paints .lr-mobile-header-content and .main-header-bar-navigation
	   ink, but Astra's Customizer CSS paints the menu LIST inside them white
	   (`.lr-mobile-menu .main-navigation .main-header-menu` →
	   --ast-global-color-4, under max-width: 921px). That rule is unlayered,
	   so it outranks every layered rule here on normal declarations, and the
	   white ul stacked on top of our ink container. The link colour, meanwhile,
	   is forced on-ink white by part B, so every item in the open mobile menu
	   rendered #f4f7fb on #ffffff, 1.07:1. Invisible; reported 2026-08-25 from
	   a phone screenshot, and missed by the automated sweeps because the
	   drawer is display:none until opened and hidden subtrees were skipped.

	   Hover needs overriding for the same reason and it is not optional: Astra
	   pairs its hover background (#F0F5FA) with its own dark hover colour, but
	   part B's `color: ... !important` wins over that unlayered colour, so the
	   hover state would land white-on-#F0F5FA, the identical fault one step
	   along. Ink hover plate, accent label, matching the desktop nav.

	   Not gated on a media query on purpose: .lr-mobile-menu only
	   ever renders inside the drawer, which Astra hides above the breakpoint,
	   so scoping by element survives a breakpoint change and mirroring
	   Astra's 921px would not. */
	.site-header .lr-mobile-menu .main-navigation .main-header-menu,
	.site-header .lr-mobile-menu .main-header-menu .sub-menu,
	.site-header .lr-mobile-menu .menu-item,
	.site-header .lr-mobile-menu .menu-link {
		background-color: var(--lr-color-ink) !important;
	}

	.site-header .lr-mobile-menu .menu-item {
		border-color: rgba(255, 255, 255, 0.12) !important;
	}

	.site-header .lr-mobile-menu .menu-link:hover,
	.site-header .lr-mobile-menu .menu-link:focus-visible,
	.site-header .lr-mobile-menu .menu-item:hover > .menu-link {
		/* A translucent white lift rather than a literal navy, so the hover
		   plate tracks whatever --lr-color-ink is set to rather than going
		   out of step the next time the palette moves. */
		background-color: rgba(255, 255, 255, 0.10) !important;
		color: var(--lr-color-on-ink-accent) !important;
	}

	.site-header .site-title,
	.site-header .site-title a,
	.site-header .menu-link,
	.site-header .menu-toggle,
	.site-header .lr-menu-toggle {
		color: var(--lr-color-on-ink) !important;
	}

	/* Social icons carry per-network inline --color values (dark blues) that
	   would vanish on ink; stylesheet !important beats inline normal. The
	   direct fill override is belt-and-braces for however Astra routes the
	   variable into the SVG. */
	.site-header .lr-social-element,
	.site-footer .lr-social-element {
		--color: #cbd8e8 !important;
		color: #cbd8e8 !important;
	}

	.site-header .lr-social-element svg,
	.site-header .lr-social-element svg path,
	.site-footer .lr-social-element svg,
	.site-footer .lr-social-element svg path {
		fill: #cbd8e8 !important;
	}

	/* Hover/current colours + the sliding bar pick up the remapped accent
	   automatically; these only need to exist because the earlier nav rules
	   live at lower specificity. */
	.site-header .main-header-menu .current-menu-item > .menu-link,
	.site-header .main-header-menu .current_page_item > .menu-link {
		color: var(--lr-color-on-ink-accent) !important;
	}
	@media (hover: hover) and (pointer: fine) {
		.site-header .main-header-menu .menu-link:hover {
			color: var(--lr-color-on-ink-accent) !important;
		}
	}

	/* --- C. Footer --- */
	.site-footer {
		background-color: var(--lr-color-ink) !important;
		color: var(--lr-color-on-ink-muted);
	}

	/* Astra paints each footer band on child row wrappers with their own
	   opaque backgrounds, which sit over the ink and left the remapped
	   light text on a light band (unreadable). Clear the children so the
	   ink parent shows through everywhere. */
	.site-footer .lr-grid-row,
	.site-footer .site-footer-section,
	.site-footer [class*="site-footer-primary-section"],
	.site-footer [class*="site-footer-below-section"],
	.site-footer [class*="site-footer-above-section"],
	.site-footer .site-primary-footer-wrap,
	.site-footer .site-below-footer-wrap,
	.site-footer .site-above-footer-wrap,
	.site-footer .lr-footer-copyright {
		background: transparent !important;
	}

	.site-footer p,
	.site-footer li,
	.site-footer .lr-footer-copyright {
		color: var(--lr-color-on-ink-muted) !important;
	}

	.site-footer a,
	.site-footer .lr-site-footer__manage {
		color: var(--lr-color-on-ink-accent) !important;
	}

	@media (hover: hover) and (pointer: fine) {
		.site-footer a:hover,
		.site-footer .lr-site-footer__manage:hover {
			color: #cbd8e8 !important;
		}
	}

	/* The secondary nav and legal block are rendered AFTER Astra's
	   </footer>, separate elements, so the .site-footer ink never reached
	   them and the footer shipped two-tone. Same ink, gap closed, and the
	   literal-colour hover (#0274be via Astra's global var) overridden;
	   everything var-based recolours itself through the part-A remap. */
	.lr-footer-secondary-nav,
	.lr-site-footer {
		background-color: var(--lr-color-ink) !important;
		margin-top: 0 !important;
	}

	.lr-footer-secondary-nav {
		border-top: 1px solid rgba(255, 255, 255, 0.08) !important;
		padding-top: 1.5rem;
	}

	@media (hover: hover) and (pointer: fine) {
		.lr-footer-secondary-nav__list a:hover,
		.lr-footer-secondary-nav__list a:focus-visible,
		.lr-site-footer a:hover {
			color: #cbd8e8 !important;
		}
	}

	/* --- D. Interior masthead plates ---
	   Every interior page opens with header.wp-block-group containing
	   h1.lr-hero-heading. That block becomes a plate in the accent colour,
	   one per page. Engines without :has() simply keep the light masthead.

	   The plate previously carried two stacked textures blended with
	   `overlay`: an inline SVG fractal-noise field and a 32px drafting grid.
	   Both are gone. The plate now sits on the blue band rather than on the
	   page ground, so the plate/band edge is doing the work the texture was
	   there to do, and neither reference design uses surface texture at all. */
	.entry-content header.wp-block-group:has(.lr-hero-heading) {
		background-color: var(--lr-color-ink);
		border: 1px solid rgba(255, 255, 255, 0.07);
		border-radius: var(--lr-radius-large);
		padding: clamp(40px, 7vw, 80px) clamp(24px, 5vw, 64px);
	}

	.entry-content header.wp-block-group:has(.lr-hero-heading) .lr-hero-heading {
		color: #ffffff;
		/* Static 700. "Kinetic typography" (weight animating 700→900 while
		   the pointer was anywhere on the masthead) is removed: a variable-
		   weight jump is a demo of the font axis rather than a page signal,
		   and the shifting glyph widths made the plate's line breaks twitch.
		   High-end editorial headings hold their weight. */
		font-variation-settings: 'wght' 700;
	}

	.entry-content header.wp-block-group:has(.lr-hero-heading) .lr-hero-subheading {
		color: var(--lr-color-on-ink-muted);
		opacity: 1;
	}

	/* Tag pills on the plate: quiet outline chips instead of light fills. */
	.entry-content header.wp-block-group:has(.lr-hero-heading) .lr-tag,
	.entry-content header.wp-block-group:has(.lr-hero-heading) .lr-tag-group span,
	.entry-content header.wp-block-group:has(.lr-hero-heading) .pill {
		background-color: transparent;
		border-color: rgba(255, 255, 255, 0.22);
		color: var(--lr-color-on-ink-muted);
	}

}

/* =========================================================
   LINK UNDERLINE TREATMENT

   The underlines themselves are not negotiable and are not touched here.
   §22 draws them because WCAG 1.4.1 requires a non-colour cue wherever a
   link sits inside surrounding prose, and the block above §23b records the
   arithmetic proving no link colour can satisfy both 3:1 against the body
   copy and 4.5:1 against the page. Removing them would reintroduce a real
   failure.

   What IS wrong is how they were drawn. Every underline was rendering at
   the browser default: `text-decoration-thickness: auto`, which is a heavy
   2-3px slab at 16px, sitting on a fixed 3px offset that clips the
   descenders of g, y and p. That is what reads as dated and noisy.

   Three changes, all cosmetic:
     - thickness pinned to 1px instead of `auto`
     - offset in em rather than px, so it scales with the type instead of
       crowding large text and floating away from small text
     - the line drawn at a fraction of the text colour at rest, full
       strength on hover. The cue is still permanently present, which is
       what 1.4.1 asks for; it simply stops competing with the words.

   60%, not 50%. At 50% the line measured 2.70:1 against the container tint,
   2.76:1 on cream and 2.80:1 on white, under the 3:1 that 1.4.11 asks of a
   graphical object carrying meaning, and this line is the whole non-colour
   cue. 60% gave 3.46 / 3.56 / 3.58 across those same three grounds, which
   cleared it with headroom and was barely darker to the eye.

   70%, not 60%. That measurement was taken while currentColor was still the
   indigo accent. The link tone is now the azure hyperlink token (#1a56c4),
   which is lighter, and at 60% the mixed line fell back under the bar:
   2.82:1 on cream, 2.63:1 on the band. 70% restores the margin, 3.47:1 on
   cream, 3.18:1 on the band, 3.48:1 on white, 3.22:1 on the wash, and the
   rest line still reads quieter than the full-strength hover line.

   `!important` is required: §22 sets the `text-decoration` SHORTHAND with
   important, and the shorthand resets thickness and colour to their initial
   values at that priority. Longhands at normal priority would be discarded.
   color-mix degrades safely, an engine without it drops the declaration
   and the underline stays full-strength, which is the accessible direction
   to fail in.
   ========================================================= */
/* MUST be layered, and specifically in a layer EARLIER than `components`.

   The rule these override, `p a, li a, .entry-content li a, … {
   text-decoration: underline !important; text-underline-offset: 3px
   !important }`, lives in @layer components. For IMPORTANT declarations
   the cascade inverts layer order: an important declaration in an earlier
   layer beats one in a later layer, and both beat any unlayered important
   declaration. Written unlayered, this block lost outright, not on
   specificity (it has (0,7,1) against their (0,1,2)) and not on source
   order (it is 70KB later in the file), but purely on layer position.
   Verified: the element matched the selector and every declaration was
   discarded, while the identical values applied instantly when set inline.

   Only the drawing longhands are set here. `text-decoration-line` is
   deliberately left to §22, so the accessibility decision about WHICH
   links are underlined stays in one place and this block cannot silently
   remove an underline. */
@layer primitives {
	a:not(.wp-block-button__link):not(.lr-btn):not(.lr-portal__cta):not(.lr-dashboard__link):not(.lr-card):not(.button):not(.menu-link) {
		text-decoration-thickness: 1px !important;
		text-underline-offset: 0.16em !important;
		text-decoration-skip-ink: auto !important;
		/* 70%, not 60%. The line is drawn at a fraction of currentColor, and
		   currentColor is now the azure link (#1a56c4), which is lighter than
		   the indigo this block was last measured against: at 60% the mixed
		   line measured 2.82:1 on cream and 2.63:1 on the band, under the
		   3:1 that 1.4.11 asks of this cue. 70% gives 3.47:1 / 3.18:1 on
		   those grounds (3.48:1 on white, 3.22:1 on the wash) and is still
		   clearly quieter than the full-strength hover line. */
		text-decoration-color: color-mix(in srgb, currentColor 70%, transparent) !important;
	}

	a:not(.wp-block-button__link):not(.lr-btn):not(.lr-portal__cta):not(.lr-dashboard__link):not(.lr-card):not(.button):not(.menu-link):hover,
	a:not(.wp-block-button__link):not(.lr-btn):not(.lr-portal__cta):not(.lr-dashboard__link):not(.lr-card):not(.button):not(.menu-link):focus-visible {
		text-decoration-thickness: 2px !important;
		text-decoration-color: currentColor !important;
	}
}

/* =========================================================
   HEADER: remove the stray rule under the nav

   The primary nav signals hover and current-page with a 2px accent bar on
   .menu-link::after, positioned at `bottom: -6px`. Astra also paints
   `border-bottom: 1px #d1d5db` on .lr-primary-header-bar. Those two land a
   few pixels apart, so a hovered nav item shows the accent bar AND a
   full-width grey rule directly beneath it, the "two underlines" on the
   nav, which is a different fault from the two-parallel-lines case §31
   already fixed (that one was text-decoration stacking on the bar).

   The grey rule is Astra's default for a light header and has no job on an
   ink one: the header/page colour change already draws the boundary. */
.lr-primary-header-bar,
.lr-primary-header-bar.main-header-bar,
.main-header-bar {
	border-bottom-width: 0 !important;
}

/* =========================================================
   HEADER: one underline per mode, never two

   The rule above removes the grey rule from the header BAR. It does not
   touch the one Astra draws on each menu LINK, which is a different
   selector and the actual source of the remaining double line:

     .lr-narrow .main-navigation ul .menu-item .menu-link
       { border-width: 0 0 1px; border-color: var(--ast-border-color) }

   That is scoped to `.lr-narrow`, the class Astra puts on
   <body> only while the header is collapsed, so it never fires on a full
   desktop nav. Measured on the desktop nav with the current stylesheet:
   text-decoration-line none, border-bottom 0px, and the accent bar at
   scaleX(0) until current or hover. One affordance, as intended.

   Collapsed is where it doubles up. The menu becomes a vertical list, Astra
   draws its divider under every item, and our sliding accent bar still
   renders 6px below the same link. Two lines, a few pixels apart.

   The fix keeps ONE affordance per mode rather than deleting a line from
   each. A horizontal nav is signalled by the sliding bar; a vertical list is
   already segmented by Astra's dividers, where a sliding bar means nothing,
   so the bar stands down and current-page keeps the colour and weight
   change it already carries from the block near line 6255.

   Unlayered !important deliberately: Astra's declaration is unlayered and
   normal, which outranks any layered normal declaration of ours no matter
   where it sits in the layer order. */
.lr-narrow .main-header-menu .menu-link::after {
	content: none !important;
}

/* =========================================================
   CONTAINER CARDS

   A card that holds other cards is a GROUND, not a card, and it was not
   being styled as one. Measured on /teaching/: nine .lr-card containers,
   each holding three .lr-card-white children, and container and child were
   pixel-identical, both `#ffffff` with the same 2px #1f374e outline and
   the same 40px radius. Nesting an object inside a visually identical
   object gives the eye nothing to group by, which is why the inner cards
   only read as objects once they lift on hover.

   The fix is hierarchy rather than more decoration: the container takes a
   soft tint and a hairline that recedes, the children keep the white fill
   and the full-strength outline and therefore come forward. That restores
   the three steps the palette was designed around, cream page, tinted
   container, white card, and it is the same structure the reference
   design uses for a card sitting on a band.

   Scoped with :has() so it only ever applies to cards that actually
   contain cards; a plain content card is untouched. */
.lr-card:has(.lr-card-white),
.lr-card:has(.lr-project-card),
.lr-card:has(.lr-surface-raised-white) {
	background-color: var(--lr-color-bg-light);
	border: 1px solid var(--lr-color-border-subtle);
}

/* The child keeps the full-strength card treatment against that tint. */
.lr-card:has(.lr-card-white) > * .lr-card-white,
.lr-card:has(.lr-card-white) .lr-card-white,
.lr-card:has(.lr-project-card) .lr-project-card {
	background-color: #ffffff;
	border: var(--lr-border-card);
}

/* =========================================================
   MEASURE (line length)

   Brief §4: body copy sits at 65 to 70ch so the eye can find the next line.
   The content shell is --lr-max-width-card (1140px); at the 16px body size
   that is ~125 characters per line, roughly double the target, and it is
   the readability problem the brief singles out on long technical pages.

   Capping with max-width is safe to apply broadly because max-width can
   only ever shrink a box: multi-column card grids are already narrower than
   the cap and are unaffected, so the only blocks that change are the wide
   single-column ones this is meant to fix.

   This replaces an earlier `max-width: none !important` override on card
   paragraphs. That rule was defensive, nothing in the stylesheet sets a
   paragraph max-width today, so it capped nothing and only stood to block
   this.
   ========================================================= */
.entry-content p,
.entry-content li,
.lr-card p,
.lr-card-white p,
.lr-project-card p,
.lr-panel-white p,
.lr-surface-sunken p,
/* Added after auditing all 51 page templates at 1440px: these two ran the
   widest prose on the site. .lr-intent-guide is the "Why visitors arrive"
   lead that opens most pages, measured at 134 characters; .lr-card holds
   the same lead on the pages that wrap it in a card. */
.lr-intent-guide p,
.lr-card > p,
.lr-card-white > p {
	max-width: var(--lr-prose-max-width);
}

/* Text that is deliberately centred must keep its optical centre once the
   measure is capped, otherwise the shortened line box hugs the left edge of
   a much wider container. Only explicit centring hooks are matched, adding
   auto margins to left-aligned copy would silently centre it. */
.entry-content .has-text-align-center,
.entry-content .lr-section-centered p,
.lr-section-centered p {
	margin-inline: auto;
}

/* Tables, figures, math and code are not prose and must keep the full
   column width, several ESAT solution tables are already tight at 1140px. */
.entry-content .wp-block-table p,
.entry-content figure p,
.entry-content .lr-math p,
.entry-content .lr-math-block p,
.entry-content pre p,
.entry-content .lr-esat-section li,
.entry-content .lr-table-wrap p {
	max-width: none;
}

/* … but the annotation under an equation IS prose, and the exemption above
   was catching it because it sits inside .lr-math-block. Measured at 120
   characters, repeating down a page, 9 times on
   /viscous-flow-fluid-mechanics/, 6 on /dimensional-analysis-fluid-mechanics/.
   One class deeper than the exemption, so it wins on specificity without
   needing !important, and the equation itself keeps the full column. */
.entry-content .lr-math-block .lr-math-annotation p,
.entry-content .lr-math-annotation p {
	max-width: var(--lr-prose-max-width);
}

/* =========================================================
   ESAT Question Cards: Math Block Horizontal Scroll Fix
   
   The global .lr-card rule uses `contain: layout` which creates
   a layout containment boundary that clips child overflow.
   For ESAT question articles, we must relax containment so that
   .lr-math-block children can trigger their own overflow-x: auto
   scrollbar on narrow / mobile viewports.
   ========================================================= */
#problem-container > article.lr-card {
	contain: none;
}

/* Ensure the details/solution expand area doesn't clip math overflow */
.lr-details-solution,
.lr-details-solution .lr-details-content,
.lr-details-solution .lr-esat-section {
	overflow: visible;
}

/* Math block: guaranteed scrollable container for wide equations */
.lr-details-solution .lr-math-block,
.lr-esat-question-text .lr-math-block {
	overflow-x: auto;
	overflow-y: hidden;
	max-width: 100%;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: thin;
	scrollbar-color: rgba(var(--lr-color-accent-rgb), 0.4) rgba(240, 243, 248, 0.8);
}

/* MathJax rendered content must not wrap, it must overflow the container */
.lr-details-solution .lr-math-block mjx-container,
.lr-esat-question-text .lr-math-block mjx-container {
	display: inline-block !important;
	white-space: nowrap !important;
	min-width: 0;
}

/* =========================================================
   ESAT: Direct MathJax Display Equation Scroll Fix
   
   Many equations are rendered inside <p> or <li> elements
   (not inside .lr-math-block wrappers) because they are
   embedded within bullet list items in the JSON source.
   
   MathJax 3 sets display="true" on mjx-container for all
   display-mode equations ($$...$$). We target these directly
   so the equation block itself becomes the scroll container,
   regardless of what HTML parent wraps it.
   ========================================================= */

/* Every display-mode equation rendered by MathJax becomes 
   a scrollable block that never clips horizontally */
#problem-container mjx-container[display="true"] {
	display: block !important;
	overflow-x: auto !important;
	overflow-y: hidden !important;
	max-width: 100% !important;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: thin;
	scrollbar-color: rgba(var(--lr-color-accent-rgb), 0.4) rgba(240, 243, 248, 0.8);
	
	/* Grey box styling (matches .lr-math-block) */
	background-color: var(--lr-color-bg-light);
	border: var(--lr-border-card);
	border-radius: var(--lr-radius-medium, 10px);
	padding: clamp(16px, 4vw, 24px);
	padding-bottom: clamp(20px, 4vw, 28px);
	margin: clamp(16px, 3vw, 24px) 0;
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03);
}

/* Prevent double-boxing if the equation is already wrapped in a .lr-math-block */
#problem-container .lr-math-block mjx-container[display="true"] {
	background-color: transparent !important;
	border: none !important;
	box-shadow: none !important;
	padding: 0 !important;
	margin: 0 !important;
}

/* The inner math element must not wrap or be constrained */
#problem-container mjx-container[display="true"] mjx-math {
	white-space: nowrap !important;
	display: inline-block !important;
}

/* Ensure the <p> and <li> parent elements don't clip */
#problem-container .lr-esat-section p,
#problem-container .lr-esat-section li,
#problem-container .lr-esat-question-text p {
	overflow-x: auto;
	overflow-y: hidden;
	max-width: 100%;
	-webkit-overflow-scrolling: touch;
}

/* Webkit scrollbar styling for display equations */
#problem-container mjx-container[display="true"]::-webkit-scrollbar {
	height: 5px;
}
#problem-container mjx-container[display="true"]::-webkit-scrollbar-track {
	background: rgba(240, 243, 248, 0.9);
	border-radius: 3px;
}
#problem-container mjx-container[display="true"]::-webkit-scrollbar-thumb {
	background: rgba(var(--lr-color-accent-rgb), 0.35);
	border-radius: 3px;
}
#problem-container mjx-container[display="true"]::-webkit-scrollbar-thumb:hover {
	background: rgba(var(--lr-color-accent-rgb), 0.55);
}

/* =====================================================================
   HERO & MASTHEAD, decorative layer removed

   This section carried three effects, all deleted:

     1. An "aurora mesh gradient glow": three stacked radial gradients on a
        ::before, 200% x 200%, run through `filter: blur(40px)`. A blurred
        full-bleed layer behind every interior masthead.
     2. A "fluid text shimmer" on every section h2, a moving gradient
        clipped to the text via `background-clip: text`, which requires
        `-webkit-text-fill-color: transparent`. Headings were literally
        transparent text relying on a background to be readable, animated
        on a 6s infinite loop.
     3. An "ink masthead scan-line": a 1px highlight sweeping top to bottom
        on another 6s infinite loop.

   Two infinite animations were running on every page at all times. The
   reference designs run none. Removing them also removes the transparent-
   text failure mode in #2, so the print and @supports fallbacks that
   existed to catch it are gone too, headings are now simply painted in
   their own colour.

   `position: relative` is retained below because later rules position
   against this element; nothing else here was structural.
   ===================================================================== */
.entry-content header.wp-block-group:has(.lr-hero-heading) {
	position: relative;
}

/* 4. HERO LINK GRADIENT UNDERLINES
   Links inside the ink masthead get a gradient underline that transitions
   from on-ink-accent to a lighter blue, more editorial and premium than
   a flat colour. Implemented via animated background-size. */
.entry-content header.wp-block-group:has(.lr-hero-heading) a:not(.wp-block-button__link):not(.lr-tag) {
	text-decoration: none !important;
	background-image: linear-gradient(
		90deg,
		var(--lr-color-on-ink-accent),
		#cbd8e8
	);
	background-size: 0% 2px;
	background-repeat: no-repeat;
	background-position: left bottom;
	padding-bottom: 2px;
	transition: background-size var(--lr-duration-moderate) var(--lr-easing-standard);
}

@media (hover: hover) and (pointer: fine) {
	.entry-content header.wp-block-group:has(.lr-hero-heading) a:not(.wp-block-button__link):not(.lr-tag):hover {
		background-size: 100% 2px;
	}
}

@media (prefers-reduced-motion: reduce) {
	.entry-content header.wp-block-group:has(.lr-hero-heading) a:not(.wp-block-button__link):not(.lr-tag) {
		transition: none;
	}
}

/* =====================================================================
   SCROLL-DRIVEN EFFECTS, removed

   Four scroll-timeline effects lived here and are all deleted:

     1. "Cinematic parallax" on the hero masthead, which drove it to
        `translateY(80px) scale(0.92) rotateX(-5deg)` and `opacity: 0.3`
        as the page scrolled, i.e. the page title faded to a third of
        its opacity and tilted away while you read.
     2. A highlighter-pen reveal on `.lr-highlight`.
     3. A `mask-composite` unmask of the hero plate.
     4. "Structural inertia": every card in a grid unfolding from
        `rotateX(15deg) translateY(60px)` as it entered the viewport.

   These were the largest single source of motion on the site and the
   reason a page felt like it was still settling after it had loaded.
   Scroll-timeline animations also ignore `animation-duration`, so the
   global reduced-motion kill-switch never reached them; each needed
   disabling by name, and that whole apparatus goes away with them.

   `.lr-highlight` is kept as a static utility, since page content uses
   the class and an undefined class would silently render as plain text.
   ===================================================================== */
.lr-highlight {
	background: linear-gradient(90deg,
		rgba(var(--lr-color-accent-rgb), 0.18),
		rgba(var(--lr-color-accent-rgb), 0.18));
	border-radius: 3px;
	padding: 0 2px;
}

/* =====================================================================
   PREMIUM UPGRADE v1.8, QUANTUM TIER OVERHAUL
   ===================================================================== */

/* 1. VIEW TRANSITIONS API (SPA NAVIGATION)
   Instructs modern browsers to morph elements rather than flashing white during navigation. */
@view-transition {
	navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
	animation-duration: 0.6s;
	animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Cross-document view transitions are motion like any other: the global
   reduced-motion kill-switch does not reach ::view-transition-* pseudos
   (they live outside the page's element tree), so without this guard
   reduce users get a 600ms morph on every navigation. Snapping the group
   animations to near-zero effectively restores an instant navigation. */
@media (prefers-reduced-motion: reduce) {
	::view-transition-group(*),
	::view-transition-image-pair(*),
	::view-transition-old(*),
	::view-transition-new(*) {
		animation-duration: 0.01ms !important;
	}
}

.lr-hero-heading {
	view-transition-name: hero-heading;
}

/* 2. SCROLL-DRIVEN NATIVE HEADER, REMOVED (was live-broken).
   The idea (GPU-interpolated glass header via scroll-timeline) could not
   work as written and its only observable effect was a layout bug:
   - `!important` inside @keyframes is invalid per spec and silently
     dropped, so the animated ink/glass background never applied;
   - the static `background: transparent !important` was itself defeated
     by the adapter-layer ink rule (layered important beats unlayered);
   - which left exactly one live effect: `position: fixed` with no body
     offset compensation, overlapping the top ~110px of every page,     breadcrumb hidden, first heading sliced (verified on production).
   Astra's sticky header + the §31 translucent-ink frosted state already
   provide the intended effect. If the scroll-driven version is revisited,
   it needs: body scroll-margin/padding compensation, the background
   animated WITHOUT important, and a same-gate check that Astra's sticky
   JS is disabled to avoid double-management. */

/* 3. GLOBAL CONTAINER QUERIES
   Enables component autonomy. .lr-container-outer holds all layout. */
.lr-container-outer, #content, .entry-content {
	container-type: inline-size;
}

/* =========================================================
   WIDE-EQUATION SCROLL AFFORDANCE

   Unlayered on purpose, matching the §44 math rules above:
   unlayered normal declarations outrank every @layer block,
   and .lr-scrollx is a new class so nothing here fights an
   existing selector.

   §44 turns every display-mode equation into its own styled
   scroll box. Inline math (\( ... \)) written inside solution
   prose is NOT matched by that rule, so the surrounding <p> or
   <li> becomes the scroll container instead, via the
   `overflow-x: auto` rule in §44. Measured on
   /teaching/esat/paper-0001/maths/ at 375x812:

     - 91 of 940 paragraphs scrolled horizontally with no
       affordance whatsoever. Mobile overlay scrollbars are
       invisible at rest, so a wide equation reads as a
       clipped/broken one rather than a scrollable one.
     - `overflow-y: hidden` (needed to suppress the vertical
       scrollbar that `overflow-x: auto` otherwise implies)
       shaved 3-6px off 14 paragraphs, clipping fraction bars
       and radical overbars.

   lr-scrollx is applied by the tagger in includes/lr-mathjax.php
   to exactly the elements that actually overflow, so the padding
   and the shadow never touch the ~850 paragraphs that fit.
========================================================= */
.lr-scrollx {
	/* Restores the ascender/descender room that overflow-y:hidden
	   clips. Vertical only: horizontal padding would change the
	   scroll extent and fight the equation's own box. */
	padding-block: 6px;

	scrollbar-width: thin;
	scrollbar-color: rgba(var(--lr-color-accent-rgb), 0.4) rgba(240, 243, 248, 0.8);
}

/* Affordance note: the shadow-gradient approach (cover gradients pinned to
   the content, shadow gradients pinned to the box) was tried and dropped.
   It has to repaint the surface colour behind each paragraph to hide the
   shadow at rest, and these paragraphs sit on at least two different
   surfaces, so a wrong match shows as a banded edge. The styled scrollbar
   below is what §44 already uses for display math, needs no surface
   colour, and cannot regress that way.

   Caveat: iOS Safari keeps overlay scrollbars regardless of this styling,
   so on iPhone the scroll cue is the clipped equation itself plus the
   restored padding. Chrome/Android and desktop get the persistent bar. */

.lr-scrollx::-webkit-scrollbar {
	height: 4px;
}

.lr-scrollx::-webkit-scrollbar-track {
	background: rgba(240, 243, 248, 0.9);
	border-radius: 3px;
}

.lr-scrollx::-webkit-scrollbar-thumb {
	background: rgba(var(--lr-color-accent-rgb), 0.35);
	border-radius: 3px;
}

/* =========================================================
   FORM CONTROLS: iOS ZOOM-ON-FOCUS

   Astra's Customizer sets `html { font-size: 91.2% }` (stored in
   the database, not this repo), so 1rem resolves to 14.59px
   site-wide. The contact form's `font-size: 1rem !important`
   therefore lands at 14.59px, and Mobile Safari zooms the page
   whenever a focused field measures under 16px, the viewport
   jumps on first tap and never zooms back out, which on the
   enquiry form is the one interaction that matters most.

   An absolute 16px is required: 1rem cannot reach the threshold
   while the root stays at 91.2%, and changing the root would
   rescale every page on the site.

   Cascade: this is deliberately declared INSIDE @layer components
   rather than unlayered. The rule it must beat
   (`.lr-contact-form-wrap .wpcf7-form-control:not(...)`, ~line
   4650) is important and lives in that layer, and for !important
   declarations the layer order reverses, unlayered important is
   the WEAKEST author origin, so an unlayered override would lose.
   Reopening the layer puts this later in the same layer, and the
   leading `body` also raises specificity to 0-5-1 over 0-5-0, so
   it wins on specificity alone even if the layers were equal.
========================================================= */
@layer components {
	@media (max-width: 782px), (pointer: coarse) {
		body .lr-contact-form-wrap .wpcf7-form-control:not([type="submit"]):not([type="checkbox"]):not([type="radio"]),
		body .lr-contact-form-wrap select,
		body .lr-contact-form-wrap textarea,
		body .lr-contact-intent-field select {
			/* 16px exactly, not larger: the threshold is what matters and
			   anything more would visibly break the form's type scale. */
			font-size: 16px !important;
		}
	}
}

/* =========================================================
   TOUCH TARGET SIZES (mobile)

   Measured on the live homepage at 375px: 26 controls sat under
   the 44px minimum. The footer was the worst of it, the
   secondary nav's 12 links and the legal links rendered 18px and
   15px tall, and the social icons 18x18, which on a phone is a
   coin-toss tap between neighbours.

   Scope is deliberately narrow. Only genuine navigation controls
   are enlarged; inline prose links and the card heading links
   (h3.lr-subheading a, 28px) are left alone, because padding a
   link that sits inside a sentence would wreck the line box for
   no accessibility gain, WCAG 2.5.8 exempts inline targets.

   The footer does get taller on mobile as a result. That is the
   intended trade: the links are the only way to reach the legal
   pages from a phone.

   Cascade: inside @layer components because
   `.lr-site-footer__links a, .lr-site-footer__manage` sets
   `padding: 0 !important` in that layer, and unlayered important
   is the weakest author origin (see the ZOOM-ON-FOCUS note).
   min-height carries the size here rather than padding, so it
   does not need to fight that declaration at all.
========================================================= */
@layer components {
	@media (max-width: 782px) {
		body nav.lr-footer-secondary-nav .lr-footer-secondary-nav__list a,
		body footer.lr-site-footer .lr-site-footer__links a,
		body footer.lr-site-footer .lr-site-footer__manage,
		body footer.lr-site-footer .lr-site-footer__contact a {
			display: inline-flex;
			align-items: center;
			min-height: 44px;
		}

		/* Icons are square targets: they need the width too, and
		   centring keeps the glyph optically where it was. */
		body .footer-social-inner-wrap a.lr-social-element {
			display: inline-flex;
			align-items: center;
			justify-content: center;
			min-width: 44px;
			min-height: 44px;
		}

		/* 42x42 -> 44x44. Two pixels, but it is the control every
		   mobile visitor uses first. */
		body .site-header .lr-menu-trigger-minimal {
			min-width: 44px;
			min-height: 44px;
		}
	}
}

/* =========================================================
   ESAT SOLUTION PAGES: RECLAIM THE READING COLUMN (mobile)

   Measured on /teaching/esat/paper-0001/maths/ at 375px: the
   nested shell consumed 134px, 36% of the viewport, leaving
   body copy 241px wide, about 33 characters per line against a
   comfortable 45-75. On the site's most reading-heavy pages,
   where a student is working through a full worked solution.

   Where the 134px went, and what this reclaims:

     .lr-container-outer        40px   left alone (Astra global)
     .lr-section-full      32px -> 16px
     article.lr-card       36px -> 24px  (4px is border, kept)
     details               2px    border, kept
     .lr-details-content   24px -> 16px

   Net +36px, so ~277px and ~38 characters per line.

   Astra's own .lr-container-outer gutter is deliberately NOT touched:
   it sets the outer margin on every page of the site, and 20px
   is a reasonable phone gutter. The waste here is the THREE
   nested boxes inside it each adding their own padding, not the
   outer one.

   Scoping: `:has(#problem-container)` keeps the .lr-section-full
   change on solution pages only, so no other page's rhythm
   shifts. It sits in its own rule block rather than grouped with
   the others, because a parser without :has() support would
   otherwise discard the whole selector list along with it.

   Cascade: @layer components beats @layer primitives (where
   .lr-section-full's padding lives) for normal declarations, and
   the ID selectors outrank the existing 600px overrides in this
   same layer, so none of this needs !important.
========================================================= */
@layer components {
	@media (max-width: 600px) {
		.lr-section-full:has(#problem-container) {
			padding-inline: 8px;
		}
	}

	@media (max-width: 600px) {
		#problem-container > article.lr-card {
			padding-inline: 10px;
		}

		#problem-container .lr-details-content {
			padding-inline: 8px;
		}
	}
}

/* =========================================================
	43. ESAT DIAGNOSTIC & EMAIL CAPTURE
	--------------------------------------------------------
	Layout only. Text inputs, buttons and focus rings are
	already handled by §25 FORM CONTROLS and the .lr-btn-*
	primitives, so nothing here restates them: a capture form
	dropped on any page inherits the same controls the contact
	form uses.

	Everything sits in @layer components. Astra ships no rule
	for these class names, so there is nothing unlayered to
	outrank and no !important is needed.
========================================================= */
@layer components {

	.lr-diagnostic {
		max-width: var(--lr-max-width-centered, 900px);
		margin-inline: auto;
	}

	.lr-diagnostic__actions {
		margin-top: var(--lr-gap-medium);
	}

	.lr-diagnostic__resume {
		font-size: var(--lr-font-size-small);
		color: var(--lr-color-text-muted);
		margin-top: var(--lr-gap-small);
	}

	/* Progress and clock share one rule: on a phone the clock drops under the
	   counter rather than squeezing it, because the counter is the line that
	   carries meaning and the clock is only reassurance. */
	.lr-diagnostic__bar {
		display: flex;
		flex-wrap: wrap;
		justify-content: space-between;
		align-items: baseline;
		gap: var(--lr-gap-small);
		margin-bottom: var(--lr-gap-medium);
		padding-bottom: var(--lr-gap-small);
		border-bottom: 1px solid var(--lr-color-border-subtle);
	}

	.lr-diagnostic__progress {
		margin: 0;
		font-size: var(--lr-font-size-small);
		color: var(--lr-color-text-muted);
	}

	.lr-diagnostic__clock {
		margin: 0;
		font-family: var(--lr-font-mono);
		font-size: var(--lr-font-size-small);
		letter-spacing: var(--lr-letter-spacing-mono);
		color: var(--lr-color-accent);
		font-variant-numeric: tabular-nums;
	}

	/* The question panel takes focus on every advance so a screen reader and a
	   keyboard user land on the new question rather than back at the top of the
	   document. It is programmatically focusable, never tab-stop clutter. */
	.lr-diagnostic__question:focus {
		outline: none;
	}
	.lr-diagnostic__question:focus-visible {
		outline: 2px solid var(--lr-color-focus-ring);
		outline-offset: 4px;
	}

	.lr-diagnostic__fieldset {
		border: 0;
		margin: 0;
		padding: 0;
	}

	.lr-diagnostic__legend {
		display: block;
		width: 100%;
		padding: 0;
	}

	.lr-diagnostic__prompt {
		display: block;
		margin-top: var(--lr-gap-small);
		font-size: var(--lr-font-size-lead);
		line-height: var(--lr-line-height-body);
		color: var(--lr-color-text-main);
	}

	/* A wide equation in a prompt or an option scrolls inside its own row
	   rather than pushing the page sideways. Same reasoning as the ESAT
	   solution pages, which is where these prompts come from. */
	.lr-diagnostic__prompt,
	.lr-diagnostic__answer {
		overflow-x: auto;
		max-width: 100%;
		-webkit-overflow-scrolling: touch;
	}

	.lr-diagnostic__options {
		list-style: none;
		margin: var(--lr-gap-medium) 0 0;
		padding: 0;
		display: grid;
		gap: var(--lr-gap-small);
	}

	.lr-diagnostic__option {
		margin: 0;
	}

	/* The radio itself is visually replaced by the lettered badge, but stays in
	   the accessibility tree and in the tab order: :focus-visible on it paints
	   the ring on the label, so keyboard position is never invisible. */
	.lr-diagnostic__option input[type="radio"] {
		position: absolute;
		width: 1px;
		height: 1px;
		padding: 0;
		margin: -1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
		border: 0;
	}

	.lr-diagnostic__option label {
		display: flex;
		align-items: flex-start;
		gap: var(--lr-gap-small);
		padding: 14px 18px;
		border: 1px solid var(--lr-color-border-card);
		border-radius: var(--lr-radius-medium);
		background-color: var(--lr-color-bg-card);
		cursor: pointer;
		transition:
		border-color var(--lr-duration-moderate) var(--lr-easing-mechanical),
		background-color var(--lr-duration-moderate) var(--lr-easing-standard);
	}

	@media (hover: hover) and (pointer: fine) {
		.lr-diagnostic__option label:hover {
			border-color: rgba(var(--lr-color-accent-rgb), 0.3);
		}
	}

	.lr-diagnostic__option input[type="radio"]:focus-visible + label {
		outline: 2px solid var(--lr-color-focus-ring);
		outline-offset: 2px;
	}

	.lr-diagnostic__option input[type="radio"]:checked + label {
		border-color: var(--lr-color-accent);
		background-color: var(--lr-color-accent-wash);
	}

	.lr-diagnostic__letter {
		flex: 0 0 auto;
		font-family: var(--lr-font-mono);
		font-size: var(--lr-font-size-small);
		font-weight: 600;
		letter-spacing: var(--lr-letter-spacing-mono);
		color: var(--lr-color-accent);
		min-width: 1.5em;
	}

	.lr-diagnostic__answer {
		flex: 1 1 auto;
		min-width: 0;
	}

	.lr-diagnostic__nav {
		display: flex;
		gap: var(--lr-gap-button);
		margin-top: var(--lr-gap-medium);
		flex-wrap: wrap;
	}

	.lr-diagnostic__nav button {
		flex: 1 1 auto;
		min-width: 8rem;
	}

	.lr-diagnostic__hint {
		margin-top: var(--lr-gap-small);
	}

	/* Shown once, before scoring a paper that still has blanks on it. Uses the
	   accent edge rather than the danger colour: leaving a blank is a habit
	   worth correcting, not an error state. */
	.lr-diagnostic__warning {
		margin-top: var(--lr-gap-medium);
		padding: var(--lr-gap-small) var(--lr-gap-medium);
		border-left: 3px solid var(--lr-color-accent);
		background-color: var(--lr-color-accent-wash);
		border-radius: var(--lr-radius-small);
		font-size: var(--lr-font-size-small);
	}

	.lr-diagnostic__warning p:last-child {
		margin-bottom: 0;
	}

	.lr-diagnostic__result:focus {
		outline: none;
	}

	.lr-diagnostic__score {
		text-align: center;
		padding: var(--lr-gap-medium) 0 var(--lr-gap-large);
		border-bottom: 1px solid var(--lr-color-border-subtle);
		margin-bottom: var(--lr-gap-large);
	}

	.lr-diagnostic__headline {
		font-family: var(--lr-font-heading);
		font-size: var(--lr-font-size-h1);
		line-height: 1;
		margin: var(--lr-gap-small) 0;
		font-variant-numeric: tabular-nums;
	}

	.lr-diagnostic__headline strong {
		color: var(--lr-color-accent);
	}

	.lr-diagnostic__capture {
		margin-top: var(--lr-gap-large);
		padding: var(--lr-padding-card);
		background-color: var(--lr-color-bg-section);
		border: 1px solid var(--lr-color-border-subtle);
		border-radius: var(--lr-radius-large);
	}

	/* ---- capture forms (newsletter page and diagnostic result) ---- */

	.lr-capture-form__row {
		display: grid;
		gap: var(--lr-gap-tight);
		margin-bottom: var(--lr-gap-small);
	}

	.lr-capture-form__row label {
		font-weight: 600;
		font-size: var(--lr-font-size-small);
	}

	.lr-capture-form__actions {
		margin-top: var(--lr-gap-medium);
		margin-bottom: 0;
	}

	/* Honeypot. Kept out of sight, out of the tab order (tabindex in the
	   markup) and out of the accessibility tree (aria-hidden in the markup),
	   but still submitted, which is the whole point. display:none is avoided
	   because some bots skip fields that are not rendered. */
	.lr-capture-form__trap {
		position: absolute;
		left: -9999px;
		width: 1px;
		height: 1px;
		overflow: hidden;
	}

	.lr-capture-form__status {
		margin-top: var(--lr-gap-small);
		font-size: var(--lr-font-size-small);
		min-height: 1.5em;
	}

	.lr-capture-form__status[data-state="error"] {
		color: var(--lr-color-danger);
	}

	.lr-capture-form__status[data-state="success"] {
		color: var(--lr-color-accent);
		font-weight: 600;
	}

	@media (prefers-reduced-motion: reduce) {
		.lr-diagnostic__option label {
			transition: none;
		}
	}
}
