/* =============================================================================
   redlamp.org — child theme stylesheet
   Parent: twentytwentyfive (block theme). Pure CSS, no build step.

   Enqueue order assumption: this file loads AFTER the parent theme AND after
   the database-generated global styles.

   Specificity notes, because this is a block theme and it matters:
   - WordPress global styles are emitted as `:root :where(...)`. `:where()`
     contributes nothing, so those rules sit at (0,1,0) — a single class beats
     them. That is why most rules below need no heroics.
   - Block-supports CSS is emitted as `.wp-elements-<hash> a:where(...)`, which
     is (0,1,1). Anything overriding a link colour has to clear that bar, so
     link rules are anchored on `main#wp--skip-link--target` or on a two-class
     chain inside the card.
   - Exactly one `!important` exists in this file. It is justified in place.
   ============================================================================= */


/* -----------------------------------------------------------------------------
   1. TOKENS
   The palette is greyscale plus a single chromatic accent. Nothing else.
   -------------------------------------------------------------------------- */

:root {
	/* Colour */
	--accent:      #990000;   /* the only chromatic colour in the system */
	--ink:         #3B3B3B;   /* primary text */
	--ink-2:       #444444;   /* body copy */
	--ink-3:       #666666;   /* display headings, muted body */
	--ink-4:       #999999;   /* labels, meta */
	--bg:          #eeeeee;
	--surface:     #ffffff;

	/* Rules */
	--rule:        #3B3B3B33; /* section hairline */
	--rule-soft:   #3B3B3B1f; /* row divider */
	--rule-strong: #3B3B3B55; /* input underline, resting */

	/* Motion scale. Every duration in this file is calc(<base> * var(--motion)),
	   so the whole sheet can be slowed, sped up, or stopped from one place.
	   The reduced-motion block at the end sets this to 0.001. */
	--motion: 1;
}

/* The animatable custom property behind the link underline sweep. Registering
   it is what makes `--sweep` interpolate instead of snapping. */
@property --sweep {
	syntax: '<percentage>';
	inherits: false;
	initial-value: 0%;
}


/* -----------------------------------------------------------------------------
   2. TYPE ROLES
   Barlow / Montserrat / Epilogue are already served by the Font Library.
   No @font-face here — that layer is handled elsewhere.
   -------------------------------------------------------------------------- */

/* Display headings — Barlow 200, uppercase, very tight leading. */
main#wp--skip-link--target h1,
.rl-display {
	font-family: Barlow, sans-serif;
	font-weight: 200;
	font-size: clamp(4rem, 7vw, 8rem);
	line-height: 0.9;
	letter-spacing: 1px;
	text-transform: uppercase;
	color: var(--ink-3);
	/* At the 4rem floor a long single word can exceed a 320px viewport.
	   break-word only breaks words that would otherwise overflow. */
	overflow-wrap: break-word;
}

/* Project detail titles sit a step below section heads. */
main#wp--skip-link--target h1.wp-block-post-title {
	font-size: clamp(3.2rem, 6vw, 7rem);
	line-height: 0.86;
}

/* Body copy. Scoped away from anything carrying a preset font-size class so
   the CV's deliberately-sized meta lines keep their own scale. */
main#wp--skip-link--target p:not([class*="font-size"]),
main#wp--skip-link--target .wp-block-list li:not([class*="font-size"]) {
	font-family: Epilogue, sans-serif;
	font-weight: 400;
	font-size: 15.5px;
	line-height: 1.75;
	color: var(--ink-2);
	overflow-wrap: break-word;
}

/* Labels, nav, meta, buttons — Montserrat, uppercase, tracked out. */
.wp-block-navigation .wp-block-navigation-item__content,
.coblocks-form .coblocks-label,
li.wp-block-post .wp-block-mfb-meta-field-block,
.wp-block-button__link {
	font-family: Montserrat, sans-serif;
	font-weight: 600;
	letter-spacing: 0.05rem;
	text-transform: uppercase;
}

li.wp-block-post .wp-block-mfb-meta-field-block {
	color: var(--ink-4);
}

/* Section hairline. Also the hook the CV row dividers coordinate with. */
main#wp--skip-link--target hr.wp-block-separator {
	border: 0;
	border-block-start: 1px solid var(--rule);
}


/* -----------------------------------------------------------------------------
   3. LINKS — accent colour with an underline sweep
   The line is a background gradient, not text-decoration, so it can animate
   its width. `--sweep` drives background-size; the parent theme forces
   text-decoration-thickness with !important, which this technique sidesteps.
   -------------------------------------------------------------------------- */

main#wp--skip-link--target p a:not(.wp-element-button),
main#wp--skip-link--target .wp-block-list a:not(.wp-element-button),
main#wp--skip-link--target figcaption a:not(.wp-element-button),
.wp-block-navigation .wp-block-navigation-item__content {
	--sweep: 0%;
	color: var(--accent);
	text-decoration: none;
	background-image: linear-gradient(var(--accent), var(--accent));
	background-repeat: no-repeat;
	background-position: 0 100%;
	background-size: var(--sweep) 1px;
	transition: --sweep calc(0.34s * var(--motion)) cubic-bezier(.2, .7, .2, 1);
}

main#wp--skip-link--target p a:not(.wp-element-button):hover,
main#wp--skip-link--target p a:not(.wp-element-button):focus-visible,
main#wp--skip-link--target .wp-block-list a:not(.wp-element-button):hover,
main#wp--skip-link--target .wp-block-list a:not(.wp-element-button):focus-visible,
main#wp--skip-link--target figcaption a:not(.wp-element-button):hover,
main#wp--skip-link--target figcaption a:not(.wp-element-button):focus-visible,
.wp-block-navigation .wp-block-navigation-item__content:hover,
.wp-block-navigation .wp-block-navigation-item__content:focus-visible {
	--sweep: 100%;
}

/* Fallback for engines without @property.

   @property has no direct CSS feature test. The brief asked for the
   paint()-based proxy, but paint() alone over-matches: Safari and Firefox
   both support @property and neither ships the Houdini Paint API, so they
   would wrongly take the fallback. Pairing it with color-mix() — which
   landed in the same era as @property in every engine — narrows the match
   to genuinely old browsers.

   `background-image: none` makes the two paths mutually exclusive by
   construction: if this block ever does apply somewhere the sweep also
   works, the gradient is removed and you get one underline, not two. */
@supports not ((background: paint(x)) or (color: color-mix(in srgb, red, blue))) {

	main#wp--skip-link--target p a:not(.wp-element-button),
	main#wp--skip-link--target .wp-block-list a:not(.wp-element-button),
	main#wp--skip-link--target figcaption a:not(.wp-element-button),
	.wp-block-navigation .wp-block-navigation-item__content {
		background-image: none;
		text-decoration: underline;
		text-decoration-color: transparent;
		text-underline-offset: 0.18em;
		transition: text-decoration-color calc(0.34s * var(--motion)) ease;
	}

	main#wp--skip-link--target p a:not(.wp-element-button):hover,
	main#wp--skip-link--target p a:not(.wp-element-button):focus-visible,
	main#wp--skip-link--target .wp-block-list a:not(.wp-element-button):hover,
	main#wp--skip-link--target .wp-block-list a:not(.wp-element-button):focus-visible,
	main#wp--skip-link--target figcaption a:not(.wp-element-button):hover,
	main#wp--skip-link--target figcaption a:not(.wp-element-button):focus-visible,
	.wp-block-navigation .wp-block-navigation-item__content:hover,
	.wp-block-navigation .wp-block-navigation-item__content:focus-visible {
		text-decoration-color: var(--accent);
	}
}


/* -----------------------------------------------------------------------------
   4. WORK GRID
   Mobile-first: one column, widening to two at 782px (WordPress's own
   mobile breakpoint).

   The layout the theme generates lives on `.wp-container-core-post-template-
   is-layout-<hash>` at (0,1,0), so `ul.wp-block-post-template` at (0,1,1)
   is enough to take it over. No !important needed.
   -------------------------------------------------------------------------- */

ul.wp-block-post-template {
	display: grid;
	/* minmax(0, 1fr) rather than 1fr: grid items default to min-content
	   minimum, which lets a wide image or long word push the row wider than
	   the viewport. This pins the track and kills horizontal overflow. */
	grid-template-columns: minmax(0, 1fr);
	gap: 52px 36px;
	margin: 0;
	padding: 0;
	list-style: none;
}

@media (min-width: 782px) {
	ul.wp-block-post-template {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/* The project list at the foot of a work page runs three-up.
   It is the same reusable block as the homepage grid, so this cannot be a block
   attribute - only work singles carry `single-work`, which separates them.
   Held back until 1100px: at 782px three cards are too narrow to read, so the
   page steps down to the standard two rather than squeezing. */
@media (min-width: 1100px) {
	body.single-work ul.wp-block-post-template {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

li.wp-block-post {
	min-width: 0; /* same overflow guard, one level down */
}


/* -----------------------------------------------------------------------------
   5. WORK CARDS — hover treatment
   Deliberately no image scaling. The veil, the chip, and the title colour
   carry the whole interaction.

   Markup: li.wp-block-post > … > figure.wp-block-post-featured-image > a > img
   The <a> is the image wrapper and hosts both pseudo-elements.
   -------------------------------------------------------------------------- */

li.wp-block-post figure.wp-block-post-featured-image > a {
	position: relative;
	display: block;
	/* Not overflow:hidden — the img carries an inline box-shadow that would
	   be clipped. The veil rounds itself to match instead. */
}

li.wp-block-post figure.wp-block-post-featured-image > a > img {
	display: block; /* removes the inline-image descender gap under the <a> */
	width: 100%;
	height: auto;
	/* !important — genuinely unavoidable, and the only one in this file.
	   Core's post-featured-image block writes the ratio into the element's
	   style attribute: style="…aspect-ratio:16/9;…". An inline declaration
	   outranks every selector, so specificity cannot reach it; the only
	   remaining lever is importance. The alternative is editing the block
	   attribute in the database, which is a content change, not a style one. */
	aspect-ratio: 16 / 10 !important;
}

/* The veil. */
li.wp-block-post figure.wp-block-post-featured-image > a::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: 1;
	border-radius: 6px; /* matches the inline radius on the image */
	background: linear-gradient(180deg, rgba(15, 14, 12, 0) 40%, rgba(15, 14, 12, .66));
	opacity: 0;
	pointer-events: none; /* must never intercept the card link */
	transition: opacity calc(0.4s * var(--motion)) ease;
}

/* The "VIEW CASE" chip. */
li.wp-block-post figure.wp-block-post-featured-image > a::after {
	content: "VIEW CASE \2192";
	position: absolute;
	z-index: 2;
	inset-inline-start: 18px;
	inset-block-end: 16px;
	font-family: Montserrat, sans-serif;
	font-weight: 600;
	font-size: 11.5px;
	line-height: 1;
	letter-spacing: .18em;
	text-transform: uppercase;
	color: var(--surface);
	opacity: 0;
	transform: translateY(8px);
	pointer-events: none; /* must never intercept the card link */
	transition:
		opacity   calc(0.4s * var(--motion)) cubic-bezier(.2, .7, .2, 1),
		transform calc(0.4s * var(--motion)) cubic-bezier(.2, .7, .2, 1);
}

li.wp-block-post:hover figure.wp-block-post-featured-image > a::before,
li.wp-block-post:focus-within figure.wp-block-post-featured-image > a::before {
	opacity: 1;
}

li.wp-block-post:hover figure.wp-block-post-featured-image > a::after,
li.wp-block-post:focus-within figure.wp-block-post-featured-image > a::after {
	opacity: 1;
	transform: translateY(0);
}

/* Card title. Epilogue rather than the theme's Barlow heading family.
   The (0,3,2) hover chain clears the block-supports link colour at (0,1,1). */
li.wp-block-post .wp-block-post-title {
	font-family: Epilogue, sans-serif;
	font-weight: 600;
	letter-spacing: 0;
	text-transform: none;
	color: var(--ink);
}

li.wp-block-post .wp-block-post-title a {
	color: var(--ink);
	text-decoration: none;
	transition: color calc(0.3s * var(--motion)) ease;
}

li.wp-block-post:hover .wp-block-post-title a,
li.wp-block-post:focus-within .wp-block-post-title a {
	color: var(--accent);
}


/* -----------------------------------------------------------------------------
   6. CV WORK-HISTORY ROWS
   The CV renders each job as a vertical flex group whose first child holds
   an h3.redlamp-job-name. `:has()` pins the selector to that exact node —
   the child combinators stop it from also matching the wrapper groups above.

   The whole thing is wrapped in :is() on purpose: :is() is forgiving, so in
   an engine without :has() the invalid argument is dropped and the
   .rl-cv-row opt-in hook still works. Written as a plain selector list, one
   unsupported item would invalidate the entire rule.
   -------------------------------------------------------------------------- */

/* SCOPE FIX (2026-08-13)
   .redlamp-job-name is not unique to the CV: a project detail page uses the
   same class, in the same group structure, for its company / role / location
   header, so the :has() test matched there too and drew a hover bar that should
   not exist. CV rows render only on /cv/ and on the homepage that embeds it;
   project headers render only on work singles, which carry .single-work.
   Excluding that body class separates them exactly.

   The .rl-cv-row hook below remains the better long-term answer: add it to the
   CV row blocks and this :has() heuristic can be deleted outright. */
body:not(.single-work) main#wp--skip-link--target :is(.rl-cv-row, .wp-block-group.is-vertical:has(> .wp-block-group > .redlamp-job-name)) {
	position: relative;
	padding-inline-start: 18px;
	padding-block: 16px;
	border-block-end: 1px solid var(--rule-soft);
}

/* The accent bar, pinned to the row's leading edge. */
body:not(.single-work) main#wp--skip-link--target :is(.rl-cv-row, .wp-block-group.is-vertical:has(> .wp-block-group > .redlamp-job-name))::before {
	content: "";
	position: absolute;
	inset-block: 0;
	inset-inline-start: 0;
	width: 2px;
	background: var(--accent);
	transform: scaleY(0);
	transform-origin: 50% 0;
	pointer-events: none;
	/* Retract: shorter and eased-in. */
	transition: transform calc(0.3s * var(--motion)) ease-in;
}

body:not(.single-work) main#wp--skip-link--target :is(.rl-cv-row, .wp-block-group.is-vertical:has(> .wp-block-group > .redlamp-job-name)):hover::before,
body:not(.single-work) main#wp--skip-link--target :is(.rl-cv-row, .wp-block-group.is-vertical:has(> .wp-block-group > .redlamp-job-name)):focus-within::before {
	transform: scaleY(1);
	/* Extend: longer and eased-out. */
	transition: transform calc(0.4s * var(--motion)) cubic-bezier(.2, .7, .2, 1);
}

/* The CV already ships an <hr> between job entries. Now that each row draws
   its own divider, that separator would read as a second line — so it is
   made transparent rather than removed, which keeps its vertical rhythm. */
main#wp--skip-link--target .wp-block-group:has(.redlamp-job-name) > hr.wp-block-separator {
	border-block-start-color: transparent;
}


/* -----------------------------------------------------------------------------
   7. CONTACT FORM (CoBlocks)
   CoBlocks renders label + hidden inputs + field as flat siblings — there is
   no per-field wrapper to hang an ::after on, and <input>/<textarea> are
   replaced elements that cannot host pseudo-elements at all.

   So the underline is a background gradient on the field itself, growing
   from background-position 0 100%. That is the same visual as a left-origin
   scaleX(0) → scaleX(1), and it reverses on blur for free because the
   transition runs in both directions.
   -------------------------------------------------------------------------- */

.coblocks-form .coblocks-label {
	display: block;
	margin-block-end: 6px;
	font-size: 12px;
	letter-spacing: .12em;
	color: var(--ink-4);
}

.coblocks-form .coblocks-label .required {
	color: var(--accent);
}

.coblocks-form input[type="text"],
.coblocks-form input[type="email"]:not(.verify),
.coblocks-form textarea {
	box-sizing: border-box;
	width: 100%;
	max-width: 100%;
	padding: 12px 14px;
	border: 0;
	border-radius: 0;
	border-block-end: 1px solid var(--rule-strong);
	font-family: Epilogue, sans-serif;
	font-size: 15.5px;
	line-height: 1.75;
	color: var(--ink-2);
	background-color: var(--bg);
	background-image: linear-gradient(var(--accent), var(--accent));
	background-repeat: no-repeat;
	background-position: 0 100%;
	background-size: 0% 2px;
	transition: background-size calc(0.45s * var(--motion)) cubic-bezier(.2, .7, .2, 1);
}

.coblocks-form textarea {
	resize: vertical;
}

.coblocks-form input[type="text"]:focus,
.coblocks-form input[type="email"]:not(.verify):focus,
.coblocks-form textarea:focus,
.coblocks-form .rl-field:focus-within input[type="text"],
.coblocks-form .rl-field:focus-within input[type="email"],
.coblocks-form .rl-field:focus-within textarea {
	background-size: 100% 2px;
}

/* Focus stays visible. The parent theme already draws a 2px solid outline on
   :focus; this replaces it with the accent, it does not remove it. */
.coblocks-form :is(input, textarea):focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}


/* -----------------------------------------------------------------------------
   8. REVEAL HOOKS
   The start state is set by JS, NOT here. Nothing in this section hides
   anything — no opacity:0 — so a failed or blocked script leaves the page
   fully readable.
   -------------------------------------------------------------------------- */

.rl-reveal-ready {
	transition:
		opacity   calc(0.6s * var(--motion)) cubic-bezier(.2, .7, .2, 1),
		transform calc(0.6s * var(--motion)) cubic-bezier(.2, .7, .2, 1);
	/* JS should drop .rl-reveal-ready once the reveal has run, so the
	   compositor hint does not stay pinned for the life of the page. */
	will-change: opacity, transform;
}


/* -----------------------------------------------------------------------------
   9. REDUCED MOTION
   States are preserved, timings collapse. Hover still changes what it
   changed — it just arrives instantly instead of travelling.
   -------------------------------------------------------------------------- */

/* ==========================================================================
   Focus visibility
   --------------------------------------------------------------------------
   Twenty Twenty-Five ships:

       :where(.wp-site-blocks :focus){outline-style:solid;outline-width:2px}

   It sets a width and a style but no colour, so the ring inherits currentColor.
   Two consequences showed up in use:

   1. The router focuses <main> after a route change so screen readers announce
      the new page. :focus matched, and main's currentColor is the body ink, so
      a dark ring was drawn around the entire content area on every navigation.
   2. Clicking a prev/next link matched :focus too, and those links are now the
      accent, so the ring came out red around the text.

   Neither is a keyboard user getting a focus indicator - that is a mouse click
   and a programmatic focus. So restrict the ring to :focus-visible, where it
   belongs, and make it deliberate rather than inherited. Keyboard focus becomes
   MORE obvious than before, not less.
   ========================================================================== */

:where(.wp-site-blocks) :focus:not(:focus-visible) {
	outline: none;
}

/* The container is tabindex="-1" and unreachable by Tab, so it never needs a
   ring - it is focused only to move the screen reader's reading position. */
main#wp--skip-link--target:focus,
main#wp--skip-link--target:focus-visible {
	outline: none;
}

:where(.wp-site-blocks) :focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
	border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {

	:root {
		--motion: 0.001;
	}

	/* --motion already collapses every duration in this sheet, since each one
	   is calc()'d against it. These are belt-and-braces for the four effects
	   the brief calls out, in case a rule is ever written with a literal
	   duration. The properties still change — only the travel is removed. */
	li.wp-block-post figure.wp-block-post-featured-image > a::before,
	li.wp-block-post figure.wp-block-post-featured-image > a::after,
	li.wp-block-post .wp-block-post-title a,
	body:not(.single-work) main#wp--skip-link--target :is(.rl-cv-row, .wp-block-group.is-vertical:has(> .wp-block-group > .redlamp-job-name))::before,
	.coblocks-form input[type="text"],
	.coblocks-form input[type="email"],
	.coblocks-form textarea,
	main#wp--skip-link--target p a:not(.wp-element-button),
	.wp-block-navigation .wp-block-navigation-item__content,
	.rl-reveal-ready {
		transition-duration: 1ms;
		animation-duration: 1ms;
		animation-iteration-count: 1;
	}

	/* The chip arrives in place rather than rising. */
	li.wp-block-post figure.wp-block-post-featured-image > a::after {
		transform: none;
	}

	/* Nothing may depend on motion to become visible. If the reveal script
	   sets its start state with a class rather than an inline style, this
	   restores the end state outright. */
	.rl-reveal-ready {
		opacity: 1;
		transform: none;
		will-change: auto;
	}
}
