/*
Theme Name: Karadventures
Theme URI: https://karadventures.com/
Author: Kara Barros
Author URI: https://karadventures.com/
Description: A standalone travel, food and life blog theme built for Karadventures. Full-bleed photography, a transparent hamburger nav, and tall purple category panels. No parent theme required.
Version: 1.0.0
Requires at least: 6.0
Tested up to: 6.6
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: karadventures
Tags: blog, one-column, custom-menu, custom-logo, featured-images, translation-ready, full-width-template

Karadventures is distributed under the terms of the GNU GPL v2 or later.

NOTE: there is deliberately NO "Template:" header here. This is a standalone
theme, not a child theme. Adding a Template: line would make WordPress look for
a parent theme that does not exist on the server and the theme would refuse to
activate.
*/

/* ============================================================
   0. WEBFONT — Bodoni Moda (the Didot twin)
   Kara asked for Didot across the whole site, then chose to serve the twin to
   everyone rather than only to the devices that happen to have Didot.

   Why the twin at all: Didot is not a webfont anyone can license and serve. It
   ships preinstalled on Macs, iPhones and iPads and nowhere else, so on
   Windows and Android there is nothing to fall back to that looks like it.

   Bodoni Moda is the same class of typeface — a Didone: flat hairline serifs,
   extreme thick/thin contrast, vertical stress — and is the closest free match
   to Didot. Serving it to everybody means the site reads identically on every
   device instead of splitting into an Apple version and a non-Apple version.

   Self-hosted rather than linked from Google: no third-party request on every
   page load, and the files sit behind the same CDN cache as the rest of the
   theme.

   These are VARIABLE fonts — one file covers weights 400 through 900, which is
   why there are two @font-face rules here instead of eight. Latin subset only,
   ~56KB for both.

   font-display: swap means text paints immediately in the fallback and
   re-renders in Bodoni Moda, instead of the page showing nothing while it
   loads.
   ============================================================ */

@font-face {
	font-family: 'Bodoni Moda';
	font-style: normal;
	font-weight: 400 900;
	font-display: swap;
	src: url('assets/fonts/bodoni-moda-roman.woff2') format('woff2');
}

@font-face {
	font-family: 'Bodoni Moda';
	font-style: italic;
	font-weight: 400 900;
	font-display: swap;
	src: url('assets/fonts/bodoni-moda-italic.woff2') format('woff2');
}

/* DM Sans — used ONLY for the body text on the /about/ page, at Kara's
   request. Everything else on the site stays Bodoni Moda; the About title
   does too, so the page reads as a sans-serif story under a serif heading.

   Same self-hosted, variable-font arrangement as above: one file per style
   covering weights 100-1000, latin subset, ~139KB for the pair. It is only
   fetched by browsers that actually render text using it, so visitors who
   never open the About page never download it.

   The `opsz` axis is baked in at its default; DM Sans ships an optical-size
   axis that subtly adjusts letterforms for large versus small text, and the
   browser applies it automatically from the rendered font-size. */

@font-face {
	font-family: 'DM Sans';
	font-style: normal;
	font-weight: 100 1000;
	font-display: swap;
	src: url('assets/fonts/dm-sans-roman.woff2') format('woff2');
}

@font-face {
	font-family: 'DM Sans';
	font-style: italic;
	font-weight: 100 1000;
	font-display: swap;
	src: url('assets/fonts/dm-sans-italic.woff2') format('woff2');
}

/* ============================================================
   1. DESIGN TOKENS
   Ported from the Astro prototype (src/styles/global.css).
   Brand purple was sampled from Kara's actual logo.
   ============================================================ */

:root {
	/* Brand — #6f3fa3 is the logo purple. Do not change it casually. */
	--color-brand: #6f3fa3;
	--color-brand-dark: #52306f;
	--color-brand-light: #a97fd1;

	/* The three category panel purples — one per panel, progressively lighter
	   left to right. Every one is lighter than the logo purple (#6f3fa3) and
	   still clears WCAG AA (4.5:1) against the white panel text:
	       #8c4fa8  5.53:1  (Destinations — nudged toward pink, hue 281 vs
	                         the 267-269 of the others and the logo)
	       #8258b4  5.24:1
	       #a07fca  3.29:1  (Inspiration — pastel, see the note below)

	   PANEL 3 IS A DELIBERATE TRADE, made at Kara's request: she wants it
	   pastel AND wants white text on all three.

	   WCAG needs 4.5:1 for normal text but only 3:1 for "large" text, which
	   is 18.66px+ bold. The category NAME is 24-34px bold, so at 3.29:1 it
	   clears the large-text bar properly. The BROWSE label is 11.5px, counts
	   as normal text, and does NOT reach 4.5:1 on this pastel — so panel 3
	   gets a text shadow (--panel-shadow) to keep it legible. The shadow is
	   real-world mitigation, not something the WCAG ratio credits.

	   #8d61bf (4.56:1) was the previous value and is the lightest shade that
	   passes 4.5:1 outright. Going back to it would remove the need for the
	   shadow. Do not push panel 3 past ~#a688c7 (3.01:1) — below 3:1 even
	   the large name text fails. */
	--color-block-purple-1: #8c4fa8;
	--color-block-purple-2: #8258b4;
	--color-block-purple-3: #a07fca;

	/* Panel 4 (Updates) closes the row on the logo purple itself. Panel 3 is
	   already at the pale end of what stays legible (see the note above), so a
	   fourth, lighter step was not available; ending on the brand colour instead
	   makes the run read as deliberate and ties the row back to the wordmark.
	   White on it is 7.3:1, the strongest contrast of the four. */
	--color-block-purple-4: #6f3fa3;

	/* Kept as the generic fallback for any panel beyond the third. */
	--color-block-purple: #8258b4;

	/* --color-brand is the *fill* purple (solid buttons, white text on top).
	   --color-accent is the purple used for TEXT on the page background.
	   They diverge in dark mode: #6f3fa3 on the dark background is only a
	   2.4:1 ratio, which fails WCAG AA for body text. */
	--color-accent: #6f3fa3;

	/* Amber hero wash (scroll-driven) */
	--color-amber-start: #fdf6e9;
	--color-amber-end: #f2a35c;

	/* Surfaces & text — light mode */
	--color-bg: #ffffff;
	--color-bg-alt: #faf8f5;
	--color-text: #221a2b;
	--color-text-muted: #6b6270;
	--color-border: #e8e3ec;
	--color-surface: #ffffff;

	/* Bodoni Moda everywhere — Kara's choice. The self-hosted twin is listed
	   FIRST, ahead of Didot, on purpose: it means every visitor sees the exact
	   same letterforms whatever device they are on, instead of Apple owners
	   getting real Didot and everyone else getting the stand-in. Consistency
	   beats authenticity here, and the two are near-indistinguishable anyway.
	   Didot stays in the stack behind it as a same-look fallback in case the
	   font file ever fails to load. See section 0. */
	--font-display: 'Bodoni Moda', Didot, 'Bodoni 72', 'Bodoni MT', 'Playfair Display', Georgia, 'Times New Roman', serif;
	--font-body: 'Bodoni Moda', Didot, 'Bodoni 72', 'Bodoni MT', 'Playfair Display', Georgia, 'Times New Roman', serif;
	--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

	--radius: 6px;
	--container-max: 1180px;
	--measure: 68ch;

	/* Viewport gutter for the elements that are anchored to the SCREEN edges
	   rather than to the centred content column (the header logo and the
	   hamburger). Scales from 20px on a phone to 52px on a 2560px monitor, so
	   they stay comfortably inset without ever drifting toward the middle. */
	--gutter: clamp(1.25rem, 2.6vw, 3.25rem);

	/* Height of the fixed header. Overridden with the real measured height by
	   assets/js/navigation.js; this value is the pre-JS fallback and is what
	   #main padding and scroll-padding-top are based on.
	   1.1rem padding x2 (35.2px) + 175px logo at 955:400 (73.3px) = ~108px. */
	--header-h: 6.8rem;

	/* Visible height of the WordPress admin bar overlapping the top of the
	   viewport. Kept at the real value by navigation.js — the admin bar is
	   position:absolute on the front end, so this drops to 0 once it has
	   scrolled away and the fixed header slides up to meet the top edge. */
	--wp-admin-bar-offset: 0px;

	/* Hero scroll geometry.

	   --hero-h      the hero's height when it is a normal in-flow block, i.e.
	                 the no-JS and reduced-motion fallbacks. Not used by the
	                 pinned hero, which is sized by inset: 0.
	   --hero-pin-h  the pinned hero's on-screen height. It is pinned with
	                 inset: 0, so this is always exactly one viewport — it must
	                 NOT be --hero-h, whose 1000px cap would leave the top of
	                 the page already showing at rest on a 1440px-tall monitor.
	   --hero-reveal the run-up before the page starts sliding over the hero:
	                 the hero sits completely still while the amber wash and the
	                 bottom-left tagline finish arriving, and then HOLDS there
	                 so the line can be read before anything covers it.

	                 The tagline is at full opacity by ~10% of a viewport of
	                 scroll (see section 4). The rest of this value is dwell —
	                 roughly doubled from the original 55vh at Kara.s request, so
	                 the line can be read at an unhurried pace:
	                 the view does not change at all until the visitor has
	                 scrolled the full amount, which is what makes it feel
	                 paused on the words. Both numbers are in viewport units,
	                 so the relationship holds at every screen height.

	                 Raising this lengthens the pause; lowering it below ~15vh
	                 would start covering the line before it finished arriving. */
	--hero-h: clamp(480px, 88vh, 1000px);
	--hero-pin-h: 100vh;
	--hero-reveal: 110vh;
}

@supports (height: 100svh) {
	:root {
		--hero-pin-h: 100svh;
		--hero-reveal: 110svh;
	}
}

@media (max-width: 600px) {
	:root {
		/* 35.2px padding + 132px logo at 955:400 (55.3px) = ~91px. */
		--header-h: 5.7rem;
	}
}

/* Phones: Kara asked for the hero to fill the screen completely, top to
   bottom. 100vh is the wrong tool — mobile browsers resolve it against the
   LARGE viewport, so the bottom of the hero hides behind the URL bar. svh is
   the small viewport (chrome showing), which is the height that is always
   actually on screen. The plain vh line is the fallback for engines without
   svh; it is deliberately first so svh wins where supported. */
@media (max-width: 767px) {
	:root {
		--hero-h: 100vh;
		--hero-reveal: 100vh;
	}
}

@supports (height: 100svh) {
	@media (max-width: 767px) {
		:root {
			--hero-h: 100svh;
			--hero-reveal: 100svh;
		}
	}
}

/* Reduced motion never pins the hero, so the reveal run-up must not be added
   to #main either — see the reduced-motion block in section 4. */

:root[data-theme='dark'] {
	--color-accent: #a97fd1;
	--color-bg: #17131c;
	--color-bg-alt: #1e1824;
	--color-text: #f2eef7;
	--color-text-muted: #b7aec2;
	--color-border: #332a3d;
	--color-surface: #201a27;
	--color-amber-start: #1e1824;
	--color-amber-end: #6b3f22;
}

@media (prefers-color-scheme: dark) {
	:root:not([data-theme='light']) {
		--color-accent: #a97fd1;
		--color-bg: #17131c;
		--color-bg-alt: #1e1824;
		--color-text: #f2eef7;
		--color-text-muted: #b7aec2;
		--color-border: #332a3d;
		--color-surface: #201a27;
		--color-amber-start: #1e1824;
		--color-amber-end: #6b3f22;
	}
}

/* ============================================================
   2. RESET & BASE
   ============================================================ */

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

html {
	/* Two sizes larger across the whole site, at Kara's request.
	   Done here, once, rather than by editing dozens of font-size rules: the
	   theme sizes everything in rem, and rem is measured from this number, so
	   raising it scales headings, body copy, buttons and the nav together and
	   keeps every proportion the theme was designed with.
	   120% of the browser default = 19.2px instead of 16px, which puts body
	   copy at roughly 20px, up from 17px.
	   It is also why this is a percentage and not a pixel value: a visitor who
	   has set a larger default text size in their browser still gets a bigger
	   site, instead of having their setting overridden. */
	font-size: 120%;
	scroll-behavior: smooth;
	/* Anchor links and :target must not slide under the fixed header. */
	scroll-padding-top: calc(var(--header-h) + var(--wp-admin-bar-offset, 0px) + 1rem);
}

@media (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}

	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

body {
	margin: 0;
	background: var(--color-bg);
	color: var(--color-text);
	font-family: var(--font-body);
	font-size: 1.05rem;
	line-height: 1.65;
	-webkit-font-smoothing: antialiased;
	overflow-x: hidden;
}

h1,
h2,
h3,
h4,
h5,
h6 {
	font-family: var(--font-display);
	line-height: 1.18;
	margin: 0 0 0.5em;
	font-weight: 700;
}

a {
	color: var(--color-accent);
}

img,
video,
iframe,
embed,
object {
	max-width: 100%;
}

img {
	height: auto;
	display: block;
}

:focus-visible {
	outline: 3px solid var(--color-accent);
	outline-offset: 3px;
	border-radius: 2px;
}

/* The header sits on top of the photo, so its focus rings need to be
   visible against a photograph, not against the page background. */
.site-header--overlay :focus-visible {
	outline-color: #ffffff;
	box-shadow: 0 0 0 6px rgba(0, 0, 0, 0.35);
}

.wrap {
	width: 100%;
	max-width: var(--container-max);
	margin-inline: auto;
	padding-inline: 1.5rem;
}

.wrap--narrow {
	max-width: 760px;
}

.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

.skip-link {
	position: fixed;
	left: 1rem;
	top: -6rem;
	z-index: 200;
	background: var(--color-brand);
	color: #fff;
	font-weight: 700;
	text-decoration: none;
	padding: 0.7rem 1.15rem;
	border-radius: var(--radius);
}

.skip-link:focus {
	top: 1rem;
}

/* ============================================================
   3. HEADER / HAMBURGER NAV
   No horizontal text nav: logo left, hamburger right.

   The header is FIXED: it stays on screen for the whole page (Kara's
   request). On the front page it floats over the hero photo with no
   background, no border and no divider of any kind — that stays true for as
   long as the hero is behind it. Once the visitor scrolls past the hero the
   header would otherwise be a purple logo floating over bare page content,
   so `.is-solid` fades in the page background behind it. That class is added
   by assets/js/navigation.js.

   The header is deliberately NOT inside .wrap. .wrap is the centred, max-width
   content column; on a 2560px monitor that column is 1180px wide and pulls the
   logo and hamburger 690px in from each screen edge, while the hero photo
   behind them is full-bleed. Logo and hamburger are anchored to the VIEWPORT
   with --gutter instead, so they track the photo at every width.
   ============================================================ */

.site-header {
	position: fixed;
	inset-block-start: var(--wp-admin-bar-offset, 0px);
	inset-inline: 0;
	z-index: 60;
	background: var(--color-bg);
	transition: background-color 0.3s ease, box-shadow 0.3s ease;
}

.site-header--overlay {
	background: transparent;
}

/* Scrolled past the hero: bring the page background in behind the header so
   the logo and hamburger always have something to read against. Uses the
   theme's own --color-bg, so it is correct in light AND dark mode. The
   transition is neutralised globally under prefers-reduced-motion. */
.site-header--overlay.is-solid {
	background: var(--color-bg);
	box-shadow: 0 1px 0 var(--color-border);
}

/* Interior pages have no hero, so the header is opaque from the start; it
   only picks up the hairline once content has scrolled beneath it. */
.site-header:not(.site-header--overlay).is-solid {
	box-shadow: 0 1px 0 var(--color-border);
}

/* The fixed header is out of flow, so pages without a hero need the space
   reserved. .has-hero pages do not: the hero starts at the top of the page
   and the header is meant to sit on top of it. */
body:not(.has-hero) #main {
	padding-block-start: var(--header-h);
}

/* Without JS the drawer renders as a purple block in the flow at the top of
   the page, so an overlay header would land on purple instead of on the hero,
   and nothing would ever add `.is-solid`. Drop the header back into the flow
   in that case. The `js` class is set by an inline snippet in wp_head (see
   karadventures_js_class_snippet) so this never flashes. */
html:not(.js) .site-header {
	position: relative;
	background: var(--color-bg);
}

html:not(.js) body:not(.has-hero) #main {
	padding-block-start: 0;
}

/* .site-header establishes a stacking context (z-index: 60). The drawer sits
   at z-index 120 in the ROOT context, so a z-index on the toggle alone can
   never lift it above the panel — the whole header has to be raised. */
.nav-drawer-open .site-header {
	z-index: 130;
}

/* On interior pages the header carries a solid white background so the purple
   logo has something to read against. While the menu is open that background
   has nothing to do: the logo is hidden (see below) and the only thing left in
   the header is the close button, which is already white. So the white band
   just cut a stripe across the top of the whole screen — including across the
   top of the purple panel, because the header sits above it at z-index 130.
   Kara asked for it gone.

   Desktop only, deliberately: on a phone the drawer is 88vw and the layout
   reads differently, and she has not asked for a change there.

   The dark backdrop (z-index 110, inset: 0) already covers the full viewport
   underneath, so removing this reveals the dim overlay rather than bare page
   content. */
@media (min-width: 901px) {
	.nav-drawer-open .site-header,
	.nav-drawer-open .site-header--overlay.is-solid,
	.nav-drawer-open .site-header:not(.site-header--overlay).is-solid {
		background: transparent;
		box-shadow: none;
	}
}

.site-header__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1.5rem;
	padding-block: 1.1rem;
	/* Full viewport width with a responsive gutter — NOT a .wrap column. */
	padding-inline: var(--gutter);
}

.site-header__logo {
	display: inline-flex;
	align-items: center;
	line-height: 0;
	border-radius: var(--radius);
}

/* Logo size. Kara asked for this twice: 110px -> 130px -> 175px. Only the
   width is ever declared; `height: auto` keeps the intrinsic 955x400 ratio
   (191:80) exactly, and the width/height attributes in header.php carry that
   same ratio so the box is reserved before the PNG decodes — no layout shift,
   and no risk of the wordmark being squashed.

   175px renders 73.3px tall, which sets the header to ~108px. Room check at
   the narrowest breakpoint (375px viewport): 20px gutter + 132px logo + 24px
   gap + 46px hamburger + 20px gutter = 242px, so there is ~130px of slack
   before the two could ever meet. */
.site-header__logo img,
.site-header__logo .custom-logo {
	width: 175px;
	height: auto;
}

/* While the menu drawer is open the logo sits ON TOP of the purple panel and
   reads as a mistake — purple wordmark on purple, half of it cut off by the
   panel edge. Kara's call: hide it for as long as the menu is open. It fades
   rather than blinking out, and comes straight back on close.
   aria-hidden is not needed: the logo link is still in the DOM but the drawer
   traps focus while it is open, so it cannot be reached by keyboard anyway. */
body.nav-drawer-open .site-header__logo {
	opacity: 0;
	visibility: hidden;
	transition: opacity 0.18s ease, visibility 0s linear 0.18s;
}

.site-header__logo {
	transition: opacity 0.22s ease;
}

/* Tablets: step down before the header starts eating the screen. */
@media (max-width: 900px) {
	.site-header__logo img,
	.site-header__logo .custom-logo {
		width: 155px;
	}
}

.site-header__logo-text {
	font-family: var(--font-display);
	font-size: 1.6rem;
	color: var(--color-accent);
	text-decoration: none;
}

@media (max-width: 600px) {
	.site-header__logo img,
	.site-header__logo .custom-logo {
		width: 132px;
	}
}

/* --- Hamburger button --- */

.nav-toggle {
	appearance: none;
	-webkit-appearance: none;
	background: none;
	border: 0;
	padding: 0.55rem;
	/* The 0.55rem padding is the 46px touch target, not part of the shape.
	   Pull the button out by exactly that much so the three visible bars line
	   up on --gutter, the same optical inset as the left edge of the logo —
	   otherwise the hamburger reads ~9px further in than the logo. */
	margin: 0;
	margin-inline-end: -0.55rem;
	cursor: pointer;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 6px;
	width: 46px;
	height: 46px;
	border-radius: var(--radius);
	color: var(--color-accent);
}

.nav-toggle__bar {
	display: block;
	width: 28px;
	height: 3px;
	border-radius: 2px;
	background: currentColor;
	transition: transform 0.25s ease, opacity 0.2s ease;
}

/* Bars morph into an X while the drawer is open. Reduced motion just
   snaps between states (transition-duration is neutralised globally). */
.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(1) {
	transform: translateY(9px) rotate(45deg);
}

.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(2) {
	opacity: 0;
}

.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(3) {
	transform: translateY(-9px) rotate(-45deg);
}

/* When the drawer is open the toggle is drawn on top of the panel, so it
   needs the panel's foreground colour rather than the overlay colour.
   (The lift above the panel is done on .site-header — see above.) */
.nav-drawer-open .nav-toggle {
	color: #ffffff;
	position: relative;
}

/* --- Drawer --- */

/* The drawer is a plain block in the flow for no-JS visitors: without JS
   it is simply always visible below the header rather than a broken
   invisible panel. The `js` class (added by the enqueued script the moment
   it runs) is what switches it into an off-canvas panel. */
.nav-drawer {
	background: var(--color-brand);
	color: #ffffff;
}

.js .nav-drawer {
	position: fixed;
	inset-block: 0;
	inset-inline-end: 0;
	width: min(380px, 88vw);
	z-index: 120;
	padding: 6rem 2rem 2rem;
	overflow-y: auto;
	transform: translateX(100%);
	visibility: hidden;
	transition: transform 0.3s ease, visibility 0s linear 0.3s;
}

.js .nav-drawer.is-open {
	transform: translateX(0);
	visibility: visible;
	transition: transform 0.3s ease, visibility 0s linear 0s;
}

@media (prefers-reduced-motion: reduce) {
	.js .nav-drawer {
		transition: none;
	}
}

.nav-drawer__backdrop {
	display: none;
}

.js .nav-drawer__backdrop {
	display: block;
	position: fixed;
	inset: 0;
	z-index: 110;
	background: rgba(15, 8, 22, 0.55);
	opacity: 0;
	visibility: hidden;
	transition: opacity 0.3s ease, visibility 0s linear 0.3s;
	border: 0;
	padding: 0;
	width: 100%;
	cursor: pointer;
}

.js .nav-drawer__backdrop.is-open {
	opacity: 1;
	visibility: visible;
	transition: opacity 0.3s ease, visibility 0s linear 0s;
}

.nav-drawer__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
}

.nav-drawer__list ul {
	list-style: none;
	margin: 0.35rem 0 0;
	padding-inline-start: 1.1rem;
}

.nav-drawer__list a {
	display: block;
	color: #ffffff;
	text-decoration: none;
	font-family: var(--font-display);
	font-size: 1.5rem;
	padding: 0.6rem 0.25rem;
	border-bottom: 1px solid rgba(255, 255, 255, 0.18);
}

.nav-drawer__list a:hover,
.nav-drawer__list a:focus-visible {
	text-decoration: underline;
	text-underline-offset: 4px;
}

.nav-drawer__list .current-menu-item > a,
.nav-drawer__list .current_page_item > a {
	text-decoration: underline;
	text-underline-offset: 4px;
}

.js .nav-drawer :focus-visible {
	outline-color: #ffffff;
}

/* Prevents the page behind the drawer from scrolling while it is open. */
.nav-drawer-open {
	overflow: hidden;
}

/* ============================================================
   4. HERO
   Full-bleed photo, no text over it except the scroll-in bottom-left tagline.
   Fades into the page background at the TOP so there is no seam between the
   top of the page and the image; the bottom is a clean hard edge.

   With JS on, the hero is PINNED to the viewport and the rest of the page
   slides up over it — see "The pinned hero" below.
   ============================================================ */

.hero {
	position: relative;
	isolation: isolate;
	height: var(--hero-h);
	overflow: hidden;
	background: var(--color-bg);
}

.hero__photo {
	position: absolute;
	inset: 0;
	z-index: 1;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: 50% 35%;
}

/* Narrow portrait screens crop this photo hard on the HORIZONTAL axis — at
   375x812 only ~31% of the 1600px-wide original is on screen. Centred, that
   window lands on the empty sky to Kara's right and cuts her out of her own
   hero. She sits at roughly 68% across, so pull the crop window that way as
   the viewport narrows. */
@media (max-width: 900px) {
	.hero__photo {
		object-position: 62% 32%;
	}
}

@media (max-width: 600px) {
	.hero__photo {
		object-position: 67% 30%;
	}
}

/* --- The pinned hero ----------------------------------------------------
   Kara: "the hero should stay static while the rest of the site moves up when
   you do initial scroll."

   The hero is lifted out of flow and pinned to the viewport with inset: 0.
   Note there is no vh unit on it at all — a fixed box with inset: 0 is exactly
   the visual viewport, which is what makes it fill a phone screen edge to edge
   however the browser chrome is behaving. (background-attachment: fixed is
   deliberately NOT used: iOS Safari handles it badly.)

   #main then carries the scroll distance as padding, and everything after the
   hero is raised one layer with an opaque background so it slides up OVER the
   pinned photo.

   ONE scroll model, not three: --scroll-progress (set by hero-wash.js from
   scrollY / hero height) still drives the amber wash and the tagline fade, and
   the reveal distance is expressed in the same --hero-h units, so the three
   cannot disagree. The run-up (--hero-reveal) exists precisely so the wash and
   the tagline are done arriving before the page starts covering them.

   Scoped to .js: with no JS there is no --scroll-progress and no `.is-solid`
   header, so the hero stays an ordinary in-flow block. */
.js body.has-hero .hero {
	position: fixed;
	inset: 0;
	height: auto;
	z-index: 0;
}

.js body.has-hero #main {
	padding-block-start: calc(var(--hero-pin-h) + var(--hero-reveal));
}

/* The page content rides above the pinned hero. .intro, .category-panels and
   .site-footer already carry opaque backgrounds; .fullbleed-photo is only a
   wrapper around an image, so it gets one here — otherwise the pinned photo
   would be visible in the sliver above a not-yet-decoded lazy image. */
.js body.has-hero .hero ~ *,
.js body.has-hero .site-footer {
	position: relative;
	z-index: 1;
}

.js body.has-hero .hero ~ .fullbleed-photo {
	background-color: var(--color-bg);
}

/* Reduced motion: no reveal at all. The hero goes straight back to being a
   plain static block at the top of the page. */
@media (prefers-reduced-motion: reduce) {
	.js body.has-hero .hero {
		position: relative;
		inset: auto;
		height: var(--hero-h);
		z-index: auto;
	}

	.js body.has-hero #main {
		padding-block-start: 0;
	}

	.js body.has-hero .hero ~ * {
		position: static;
		z-index: auto;
	}
}

/* Scroll-driven amber wash. The base gradient is static, so the hero still
   reads correctly with JS off or reduced motion on; the script animates
   --scroll-progress to deepen it. Ported from Hero.astro. */
.hero__wash {
	position: absolute;
	inset: 0;
	z-index: 2;
	pointer-events: none;
	background: linear-gradient(
		180deg,
		color-mix(in srgb, var(--color-amber-start) 35%, transparent) 0%,
		color-mix(in srgb, var(--color-amber-end) calc(35% + var(--scroll-progress, 0) * 45%), transparent) 100%
	);
	mix-blend-mode: multiply;
	transition: background 0.05s linear;
}

:root[data-theme='dark'] .hero__wash {
	mix-blend-mode: normal;
	opacity: 0.55;
}

@media (prefers-color-scheme: dark) {
	:root:not([data-theme='light']) .hero__wash {
		mix-blend-mode: normal;
		opacity: 0.55;
	}
}

/* The seam killer. Sits ABOVE the wash so the very top pixel row of the
   hero is exactly the page background colour and dissolves downward into
   the photo — there is no visible line where the page meets the image.
   Two declarations: a plain rgba fallback for old engines, then a
   color-mix version that follows --color-bg (so it works in dark mode too)
   for anything modern. color-mix at 0% keeps the hue, which avoids the
   grey haze you get fading to rgba(255,255,255,0) in engines with
   non-premultiplied gradient interpolation.

   TOP EDGE ONLY. The bottom of the hero is a clean hard edge where the
   next section starts — Kara asked for no fade down there. */
.hero__fade {
	position: absolute;
	inset-inline: 0;
	z-index: 3;
	pointer-events: none;
}

.hero__fade--top {
	top: -1px; /* -1px guarantees no hairline gap from subpixel rounding. */
	height: 34%;
	background: linear-gradient(180deg, #fff 0%, rgba(255, 255, 255, 0.86) 22%, rgba(255, 255, 255, 0) 100%);
	background: linear-gradient(
		180deg,
		var(--color-bg) 0%,
		color-mix(in srgb, var(--color-bg) 86%, transparent) 22%,
		color-mix(in srgb, var(--color-bg) 0%, transparent) 100%
	);
}

/* --- Bottom-left tagline -----------------------------------------------
   Fades in on scroll, driven by the SAME --scroll-progress custom property
   the amber-wash script already sets on .hero. One scroll mechanism, not
   two.

   Legibility: the bottom-left of hero-golden-hour.jpg is bright (sampled
   mean rgb(187,162,135); brightest block rgb(254,250,225)). White text
   straight on the photo measures 1.05:1 — unusable. So the tagline carries
   its own dark corner vignette, which fades in with the text so the hero is
   clean on load. With the vignette at >=0.55 alpha where the glyphs sit,
   over the WORST-CASE brightest pixels plus the amber wash, white text
   measures 5.3:1 — above the 4.5:1 normal-text threshold, and well above
   the 3:1 large-text one. Over typical pixels in that corner it is higher
   still. */
.hero__tagline {
	position: absolute;
	inset: auto 0 0 0;
	z-index: 4;
	height: 62%;
	display: flex;
	align-items: flex-end;
	pointer-events: none;
	/* No JS / no scroll data: show it. Never permanently invisible. */
	opacity: 1;
}

/* The corner vignette. Anchored to the bottom-left so it reads as a
   photographic vignette rather than a box. */
.hero__tagline::before {
	content: '';
	position: absolute;
	inset: 0;
	pointer-events: none;
	background: radial-gradient(
		115% 105% at 0% 100%,
		rgba(20, 12, 8, 0.74) 0%,
		rgba(20, 12, 8, 0.62) 32%,
		rgba(20, 12, 8, 0.34) 58%,
		rgba(20, 12, 8, 0) 80%
	);
}

/* Lifted off the bottom edge so the line sits higher in the frame. Kept well
   below Kara's face, which the crop places in the upper third
   (object-position 50% 35%, 62% 32% on narrow screens). */
.hero__tagline .wrap {
	position: relative;
	padding-bottom: clamp(4.5rem, 14vh, 9rem);
}

.hero__tagline-text {
	font-family: var(--font-display);
	/* Between the removed hero H1 (clamp(2.75rem, 7vw, 5rem)) and the
	   removed section heading (clamp(1.9rem, 3.5vw, 2.6rem)). */
	font-size: clamp(2.2rem, 5vw, 3.4rem);
	line-height: 1.12;
	font-weight: 700;
	color: #ffffff;
	margin: 0;
	max-width: 15ch;
	text-wrap: balance;
}

/* Once the script has run, opacity tracks scroll off the same
   --scroll-progress the amber wash uses.

   The multiplier was x3.3 (full at ~30% progress) when the hero scrolled away
   normally. Now that the page slides UP over a pinned hero, the tagline lives
   in the strip the incoming content covers first, so it has to be fully there
   early: x10 reaches full opacity at ~10% of --hero-h, well before
   --hero-reveal (50-55% of a viewport) lets anything start covering it. The
   gap between those two numbers IS the pause — the words finish arriving in
   the first tenth, then nothing moves for the remainder. Change one number
   and you have to change the other. */
.js .hero__tagline {
	opacity: clamp(0, calc(var(--scroll-progress, 0) * 10), 1);
	transition: opacity 0.15s linear;
}

/* Reduced motion: full opacity immediately, no scroll dependency at all.
   Placed after the .js rule so it wins without !important. */
@media (prefers-reduced-motion: reduce) {
	.js .hero__tagline {
		opacity: 1;
		transition: none;
	}
}

/* ============================================================
   5. FRONT PAGE SECTIONS
   ============================================================ */

.intro {
	background: var(--color-bg);
	padding-block: clamp(3.5rem, 9vw, 7rem);
	text-align: center;
}

.intro__inner {
	max-width: 60ch;
	margin-inline: auto;
}

/* Three clear steps down: heading > tagline > intro sentence.
   The heading is deliberately smaller than the hero tagline
   (clamp(2.2rem, 5vw, 3.4rem)) so the hierarchy still reads top-to-bottom:
   photo, hero tagline, then this. */
.intro__heading {
	font-family: var(--font-display);
	font-size: clamp(1.75rem, 3.6vw, 2.6rem);
	margin: 0 0 0.35rem;
	color: var(--color-text);
}

.intro__tagline {
	font-family: var(--font-display);
	/* Scaled up with the lede below — Kara asked for the intro copy three sizes
	   larger and for this line to grow in proportion, so both carry the same
	   1.33x step and the intro keeps its original balance. */
	font-size: clamp(1.53rem, 2.93vw, 2.13rem);
	font-weight: 400;
	line-height: 1.3;
	color: var(--color-text-muted);
	margin: 0 0 1.5rem;
}

.intro__lede {
	font-size: clamp(1.36rem, 2vw, 1.6rem);
	line-height: 1.75;
	margin: 0;
	color: var(--color-text);
}

/* Full-bleed street photo: edge to edge, no margins, no rounded corners.
   This element is a direct child of <main>, which is already full width, so
   plain width:100% is correct here — no 100vw trick, and therefore no
   scrollbar-width overflow to hide. */
.fullbleed-photo {
	display: block;
	width: 100%;
	margin: 0;
	border-radius: 0;
}

.fullbleed-photo img {
	display: block;
	width: 100%;
	height: clamp(320px, 62vh, 760px);
	object-fit: cover;
	border-radius: 0;
}

/* Desktop only: Kara asked for this photo to be twice as tall. Every value in
   the clamp above is doubled so the rule keeps the same shape. Phones keep the
   original height — doubling there would push the rest of the page more than a
   full screen further down. */
@media (min-width: 768px) {
	.fullbleed-photo img {
		height: clamp(640px, 124vh, 1520px);
	}
}


/* --- Words fading over the street photo ---------------------------------
   The photo scrolls normally; nothing here pins or slows anything. The words
   are spread down the face of the photo and assets/js/scroll-words.js gives
   `is-visible` to whichever one is nearest the middle of the screen, so they
   arrive one at a time as the reader scrolls past. */

.fullbleed-photo--words {
	position: relative;
}

/* Desktop only, and deliberately so. On a phone the photo can only be a
   fraction of the height it is on a desktop, and the words are paced by that
   height — each one got about 80px of scrolling, which is a blink on a flick
   scroll. Rather than ship something that reads as broken on a phone, the
   words are simply not there: the photo goes back to being an ordinary picture
   at its original framing and height. The markup stays put either way, so a
   screen reader still reads the words on every device.

   The aspect-ratio half of the test matters as much as the width: a tablet
   held upright is over 1024px wide but crops this 3:2 photo so hard that Kara
   is all that is left in frame, with no clear wall for the words to sit on. */
.scroll-words {
	display: none;
	position: absolute;
	inset: 0;
	margin: 0;
	pointer-events: none;
}

@media (min-width: 1024px) and (min-aspect-ratio: 1 / 1) {
	.scroll-words {
		display: block;
	}

	/* Taller than the 124vh the photo has elsewhere. With the photo scrolling
	   at normal speed, its height is the only thing setting how long a word
	   stays readable, so the words pay for their own room. */
	.fullbleed-photo--words img {
		height: clamp(860px, 185vh, 1900px);
	}
}

/* Spread evenly down the photo, each one nudged to a different distance from
   the left edge so they do not feel like a list. The spacing is what sets the
   pace: the script derives its timing from the gap between them. */
.scroll-words__word {
	position: absolute;
	max-width: min(46%, 30rem);
	color: #fff;
	font-family: var(--font-display);
	font-size: clamp(1.5rem, 4.2vw, 3.2rem);
	font-weight: 500;
	line-height: 1.1;
	/* Soft dark halo. The photo is busy in places and pure white type can wash
	   out against the lighter areas; this keeps it readable without putting a
	   visible box behind the words. */
	text-shadow: 0 2px 28px rgba(0, 0, 0, 0.55), 0 0 4px rgba(0, 0, 0, 0.3);
	/* A plain fade of the whole phrase, no movement. Quicker than the old
	   pinned version had to be: with the photo scrolling at normal speed each
	   word's turn is shorter, so the fade has to finish inside it. */
	opacity: 0;
	transition: opacity 0.35s ease-in-out;
	will-change: opacity;
}

.scroll-words__word.is-visible {
	opacity: 1;
}

.scroll-words__word:nth-child(1) { top: 10%; left: 7%; }
.scroll-words__word:nth-child(2) { top: 29%; left: 13%; }
.scroll-words__word:nth-child(3) { top: 48%; left: 6%; }
.scroll-words__word:nth-child(4) { top: 67%; left: 11%; }
.scroll-words__word:nth-child(5) { top: 86%; left: 8%; }

/* No JavaScript: nothing ever gets `is-visible`, so without this the words
   would be invisible to everyone but a screen reader. Show them all — they are
   spread far enough apart down a tall photo to read as a set. */
html:not(.js) .scroll-words__word {
	opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
	.scroll-words__word {
		transition: none;
	}
}
/* --- Purple category panels --- */

.category-panels {
	background: var(--color-bg-alt);
	padding-block: clamp(3.5rem, 8vw, 6.5rem);
}

/* Wider than the page container everything else uses. Four names across at the
   site's normal 1180px left "Destinations" with about 16px to spare inside its
   tile — close enough that a slightly wider font render tipped it over and
   broke the word across two lines. The extra width, with the smaller name size
   below, turns that margin into something that cannot be nudged over.
   Panels are 3:5, so wider also means taller: the whole set grows a little. */
@media (min-width: 1024px) {
	.category-panels .wrap {
		max-width: 1320px;
	}
}

.category-panels__grid {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	/* Explicit equal tracks, never auto-fit/minmax — every panel is the same
	   width at every breakpoint. */
	grid-template-columns: repeat(4, 1fr);
	gap: clamp(1.25rem, 3vw, 2.5rem);
	align-items: stretch;
	justify-items: stretch;
}

/* DO NOT add `grid-auto-rows: 1fr` here.
   It gives each row a definite height, and because the <li> below also has an
   aspect-ratio, iOS Safari then derives the item's WIDTH from that height
   instead of stretching it to the column. The panels came out ~55% of the
   column width and hugged its left edge. Chrome stretches first and looks
   fine, so this does not reproduce on desktop. Equal columns plus the
   aspect-ratio already guarantee equal heights without it. */

/* One shade per panel: progressively lighter across the first three, then the
   logo purple to close the row. */
.category-panels__grid > li:nth-child(1) { --panel-color: var(--color-block-purple-1); }
.category-panels__grid > li:nth-child(2) { --panel-color: var(--color-block-purple-2); }
/* Panel 3 is pastel, so its text carries a shadow to stay legible — see the
   contrast note in section 1. */
.category-panels__grid > li:nth-child(3) {
	--panel-color: var(--color-block-purple-3);
	--panel-shadow: 0 1px 3px rgba(32, 16, 54, 0.55);
}

.category-panels__grid > li:nth-child(4) { --panel-color: var(--color-block-purple-4); }

/* THE EQUAL-SIZE RULE.
   The 3:5 proportion lives on the grid ITEM (the <li>), not on the panel
   inside it, and the panel then fills its <li> completely.

   Previously the aspect-ratio was on .category-panel while the <li> was the
   grid item. A long name ("Things to do when…") made that one panel taller
   than its ratio, which grew the whole grid row — but the other panels kept
   their own ratio-derived height and did NOT grow with it, so they ended up
   different heights. Now the <li> is the sized box: all panels have identical
   widths so they derive identical heights, and because grid items stretch,
   any row growth is applied to all of them equally. */
.category-panels__grid > li {
	display: flex;
	min-width: 0;
	/* An explicit width makes the WIDTH the definite dimension, so aspect-ratio
	   derives the height from it and never the other way round. This is what
	   stops iOS Safari from shrinking the panel and pinning it to the left. */
	width: 100%;
	aspect-ratio: 3 / 5;
}

@supports not (aspect-ratio: 3 / 5) {
	.category-panels__grid > li {
		min-height: 460px;
	}
}

/* TABLETS: two by two. Four panels divide evenly, so unlike the old three
   there is no orphan left on a row of its own — which is why this step did not
   exist before. */
@media (min-width: 761px) and (max-width: 1023px) {
	.category-panels__grid {
		grid-template-columns: repeat(2, 1fr);
	}
}

/* MOBILE: one column, and the panels become SQUARES rather than tall
   rectangles.

   A full-width 3:5 panel on a phone is roughly 620px tall — most of the
   screen for a single box, which is what Kara reported. 1:1 keeps a
   full-width panel to about a third of a typical phone screen, so all three
   are browsable with one short scroll.

   Note there is no 2-column tablet step: with three panels that would leave
   one orphan on its own row, which reads as "different sizes" even though the
   boxes are identical. Three across collapses straight to one. */
@media (max-width: 760px) {
	.category-panels__grid {
		grid-template-columns: 1fr;
	}

	/* Full column width, centred in the page's normal 24px gutters — the same
	   edges every other section on the page lines up to. justify-self is
	   explicit so the panel can never fall back to shrink-to-fit and sit off
	   to one side. */
	.category-panels__grid > li {
		aspect-ratio: 1 / 1;
		width: 100%;
		justify-self: stretch;
	}

	@supports not (aspect-ratio: 1 / 1) {
		.category-panels__grid > li {
			min-height: 0;
			height: 78vw;
		}
	}
}

/* Tall portrait panels on desktop, roughly 3:5; square on mobile (the ratio is
   set on the <li> above). Square corners. Content centred both ways. Each
   panel carries its own shade of purple, all lighter than the logo. */
.category-panel {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	text-align: center;
	gap: 0.9rem;
	/* Fill the <li> exactly — this is what keeps all four identical. */
	flex: 1 1 auto;
	width: 100%;
	height: 100%;
	min-width: 0;
	padding: 1.75rem 1.25rem;
	/* Per-panel shade, set on the <li> above. Falls back to the mid purple for
	   any panel past the third. */
	background: var(--panel-color, var(--color-block-purple));
	color: var(--panel-text, #ffffff);
	/* None by default; the pastel panel sets one to keep its small BROWSE
	   label readable. */
	text-shadow: var(--panel-shadow, none);
	text-decoration: none;
	border-radius: 0;
	transition: background 0.25s ease, transform 0.25s ease;
}

/* Hover darkens every panel to the logo purple. The text goes white here
   explicitly: the pastel panel's deep-plum text would only be 1.4:1 on that
   dark ground, so it has to flip. */
a.category-panel:hover,
a.category-panel:focus-visible {
	background: var(--color-brand);
	--panel-text: #ffffff;
	color: #ffffff;
	transform: translateY(-4px);
}

.category-panel__name {
	font-family: var(--font-display);
	font-size: clamp(1.3rem, 2vw, 1.8rem);
	font-weight: 700;
	line-height: 1.15;
	/* Inherit from .category-panel so the pastel panel's dark text and the
	   white-on-hover flip both carry through. */
	color: inherit;
	/* A long name must wrap inside the fixed-size panel rather than pushing it
	   out of shape — the panels are a uniform set. But it must never break in
	   the MIDDLE of a word: keep-all plus overflow-wrap:normal means a name too
	   long for one line wraps between its words, and a single word that still
	   does not fit runs on rather than losing its tail to the next line. */
	max-width: 100%;
	overflow-wrap: normal;
	word-break: keep-all;
	text-wrap: balance;
}

.category-panel__browse {
	text-transform: uppercase;
	letter-spacing: 0.22em;
	font-size: 0.72rem;
	font-weight: 700;
	color: inherit;
}

/* Panel for a category that does not exist in the database yet. Rendered as
   a non-interactive <div> rather than an anchor with a dead href. */
.category-panel--pending {
	opacity: 0.75;
	cursor: default;
}

/* ============================================================
   6. POST LISTS / ARCHIVES
   ============================================================ */

.page-head {
	padding-block: clamp(2.5rem, 6vw, 4.5rem) clamp(1.5rem, 3vw, 2.5rem);
}

/* "Category" / "Tag" label above the archive name. Same uppercase-tracked
   treatment as the BROWSE label on the homepage panels, so arriving from a
   panel feels continuous. --color-accent, NOT --color-brand: this is purple
   TEXT on the page background, and --color-brand (#6f3fa3) only measures 2.4:1
   against the dark-mode background. --color-accent is the token that lightens
   in dark mode. */
.page-head__kicker {
	margin: 0 0 0.5rem;
	font-size: 0.72rem;
	font-weight: 700;
	letter-spacing: 0.22em;
	text-transform: uppercase;
	color: var(--color-accent);
}

.page-head__title {
	font-size: clamp(2rem, 5vw, 3.25rem);
	margin: 0 0 0.4em;
}

.page-head__desc {
	color: var(--color-text-muted);
	max-width: var(--measure);
	margin: 0;
}

.page-head__count {
	margin: 0.75rem 0 0;
	font-size: 0.82rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--color-text-muted);
}

.post-list {
	list-style: none;
	margin: 0;
	padding: 0 0 clamp(3rem, 7vw, 5rem);
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
	gap: clamp(1.75rem, 4vw, 3rem);
}

.post-card {
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
}

.post-card__thumb {
	display: block;
	overflow: hidden;
	background: var(--color-bg-alt);
}

.post-card__thumb img {
	width: 100%;
	aspect-ratio: 3 / 2;
	object-fit: cover;
	transition: transform 0.4s ease;
}

.post-card__thumb:hover img,
.post-card__thumb:focus-visible img {
	transform: scale(1.03);
}

.post-card__title {
	font-size: 1.4rem;
	margin: 0;
}

.post-card__title a {
	color: var(--color-text);
	text-decoration: none;
}

.post-card__title a:hover,
.post-card__title a:focus-visible {
	color: var(--color-accent);
	text-decoration: underline;
	text-underline-offset: 3px;
}

.post-meta {
	font-size: 0.82rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--color-text-muted);
	margin: 0;
}

.post-meta a {
	color: inherit;
}

.post-card__excerpt {
	margin: 0;
	color: var(--color-text-muted);
}

.read-more {
	font-weight: 700;
	text-decoration: none;
	align-self: flex-start;
}

.read-more:hover,
.read-more:focus-visible {
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* Pagination */

.pagination {
	padding-block: 0 clamp(3rem, 7vw, 5rem);
}

.pagination .nav-links {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
}

.pagination .page-numbers {
	display: inline-block;
	padding: 0.55rem 0.95rem;
	border: 1px solid var(--color-border);
	border-radius: var(--radius);
	text-decoration: none;
	color: var(--color-accent);
}

.pagination .page-numbers.current {
	background: var(--color-brand);
	border-color: var(--color-brand);
	color: #ffffff;
}

.post-nav {
	display: flex;
	flex-wrap: wrap;
	gap: 1.5rem;
	justify-content: space-between;
	border-top: 1px solid var(--color-border);
	padding-block: 2rem;
	margin-top: 3rem;
}

.post-nav a {
	text-decoration: none;
	font-weight: 700;
	max-width: 46ch;
}

.post-nav a:hover,
.post-nav a:focus-visible {
	text-decoration: underline;
	text-underline-offset: 3px;
}

.post-nav__label {
	display: block;
	font-size: 0.75rem;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--color-text-muted);
	margin-bottom: 0.2rem;
}

/* ============================================================
   7. SINGLE POST / PAGE + WORDPRESS CONTENT STYLES
   This block is the one that matters most: Kara writes in wp-admin with
   the block editor (and sometimes the classic editor), and whatever she
   publishes has to look right with no extra effort. Everything below
   targets core WordPress output, not bespoke classes.
   ============================================================ */

.entry {
	padding-bottom: clamp(3rem, 7vw, 5rem);
}

.entry__header {
	padding-block: clamp(2.5rem, 6vw, 4.5rem) 1.5rem;
}

.entry__title {
	font-size: clamp(2rem, 5vw, 3.4rem);
	margin: 0 0 0.5rem;
}

.entry__featured {
	margin-block: 0 2.5rem;
}

.entry__featured img {
	width: 100%;
	max-height: 70vh;
	object-fit: cover;
}

/* The content column. Standard blocks sit on a comfortable measure;
   .alignwide and .alignfull break out of it. */
.entry-content {
	max-width: var(--measure);
	margin-inline: auto;
	font-size: 1.075rem;
}

.entry-content > * {
	margin-block: 0 1.5rem;
}

.entry-content > *:last-child {
	margin-bottom: 0;
}

.entry-content h2,
.entry-content h3,
.entry-content h4,
.entry-content h5,
.entry-content h6 {
	margin-top: 2.25em;
	margin-bottom: 0.6em;
}

.entry-content h2 {
	font-size: clamp(1.6rem, 3.2vw, 2.1rem);
}

.entry-content h3 {
	font-size: clamp(1.35rem, 2.4vw, 1.6rem);
}

.entry-content h4 {
	font-size: 1.2rem;
}

.entry-content p {
	/* Paragraph spacing. Kept comfortably larger than the 1.65 line-height so
	   paragraphs read as separate thoughts rather than one grey slab. */
	margin-block: 0 2.25rem;
}

.entry-content a {
	color: var(--color-accent);
	text-decoration: underline;
	text-underline-offset: 3px;
	text-decoration-thickness: 1px;
}

.entry-content a:hover,
.entry-content a:focus-visible {
	text-decoration-thickness: 2px;
}

.entry-content ul,
.entry-content ol {
	padding-inline-start: 1.5rem;
	margin-block: 0 2.25rem;
}

.entry-content li {
	margin-bottom: 0.5rem;
}

.entry-content li > ul,
.entry-content li > ol {
	margin-block: 0.5rem;
}

.entry-content blockquote,
.entry-content .wp-block-quote {
	margin-inline: 0;
	padding: 0.25rem 0 0.25rem 1.5rem;
	border-inline-start: 4px solid var(--color-brand);
	font-family: var(--font-display);
	font-size: 1.2rem;
	font-style: italic;
	color: var(--color-text);
}

.entry-content blockquote cite,
.entry-content .wp-block-quote cite {
	display: block;
	margin-top: 0.75rem;
	font-size: 0.9rem;
	font-style: normal;
	font-family: var(--font-body);
	color: var(--color-text-muted);
}

.entry-content .wp-block-pullquote {
	border-block: 4px solid var(--color-brand);
	padding: 2rem 1rem;
	text-align: center;
}

.entry-content code,
.entry-content kbd,
.entry-content samp {
	font-family: var(--font-mono);
	font-size: 0.9em;
	background: var(--color-bg-alt);
	border: 1px solid var(--color-border);
	border-radius: 3px;
	padding: 0.1em 0.35em;
}

.entry-content pre,
.entry-content .wp-block-code {
	font-family: var(--font-mono);
	background: var(--color-bg-alt);
	border: 1px solid var(--color-border);
	border-radius: var(--radius);
	padding: 1.25rem;
	overflow-x: auto;
	font-size: 0.92rem;
	line-height: 1.5;
}

.entry-content pre code,
.entry-content .wp-block-code code {
	background: none;
	border: 0;
	padding: 0;
	font-size: inherit;
}

.entry-content hr,
.entry-content .wp-block-separator {
	border: 0;
	border-top: 1px solid var(--color-border);
	margin-block: 2.5rem;
	max-width: 100%;
}

.entry-content .wp-block-separator.is-style-wide {
	width: 100%;
}

.entry-content table,
.entry-content .wp-block-table table {
	width: 100%;
	border-collapse: collapse;
	font-size: 0.95rem;
}

.entry-content .wp-block-table {
	overflow-x: auto;
}

.entry-content th,
.entry-content td {
	border: 1px solid var(--color-border);
	padding: 0.6rem 0.75rem;
	text-align: start;
}

.entry-content th {
	background: var(--color-bg-alt);
	font-weight: 700;
}

.entry-content figure {
	margin-inline: 0;
}

.entry-content img {
	height: auto;
	max-width: 100%;
}

/* Captions — both block editor and classic editor markup. */
.entry-content figcaption,
.entry-content .wp-caption-text,
.entry-content .wp-block-image figcaption,
.entry-content .blocks-gallery-caption {
	font-size: 0.88rem;
	color: var(--color-text-muted);
	text-align: center;
	margin-top: 0.6rem;
	line-height: 1.5;
}

.entry-content .wp-caption {
	max-width: 100%;
	margin-bottom: 1.5rem;
}

.entry-content .wp-caption img {
	margin-inline: auto;
}

/* Classic alignment classes. */
.entry-content .alignleft,
.entry-content .alignright,
.entry-content .aligncenter,
.entry-content .alignnone {
	margin-bottom: 1.5rem;
}

.entry-content .aligncenter,
.entry-content .alignnone {
	margin-inline: auto;
}

.entry-content .aligncenter {
	text-align: center;
}

@media (min-width: 600px) {
	.entry-content .alignleft {
		float: left;
		margin-inline: 0 1.75rem;
		max-width: 50%;
	}

	.entry-content .alignright {
		float: right;
		margin-inline: 1.75rem 0;
		max-width: 50%;
	}
}


/* WordPress wraps an ALIGNED image block in an extra div:

       <div class="wp-block-image">
         <figure class="alignleft size-large">…</figure>
       </div>

   The rules above float that inner <figure>, which does nothing useful — the
   wrapper div is still a full-width block, so the next paragraph starts below
   it and the picture ends up alone on its own row. That is the "why can't I
   put a small picture next to my words" problem.

   The fix is to float the WRAPPER and let the figure sit plainly inside it.
   The same fix already exists further down scoped to the About page; this is
   the general one, for posts and every other page.

   Both selectors are needed: WordPress puts the align class on the wrapper in
   some versions and on the inner figure in others. */
@media (min-width: 600px) {
	.entry-content .wp-block-image.alignleft,
	.entry-content .wp-block-image:has(> figure.alignleft) {
		float: left;
		margin-inline: 0 1.75rem;
		max-width: 50%;
	}

	.entry-content .wp-block-image.alignright,
	.entry-content .wp-block-image:has(> figure.alignright) {
		float: right;
		margin-inline: 1.75rem 0;
		max-width: 50%;
	}

	/* The wrapper is doing the floating now, so the figure must not also float
	   or re-apply its own width cap inside it. */
	.entry-content .wp-block-image > figure.alignleft,
	.entry-content .wp-block-image > figure.alignright {
		float: none;
		margin: 0;
		max-width: none;
	}
}

/* A floated image taller than the text beside it would otherwise hang out of
   the bottom of the post and run alongside whatever follows. */
.entry-content::after {
	content: '';
	display: block;
	clear: both;
}
/* Block editor wide/full alignment. .entry-content is a centred column, so
   .alignwide widens it and .alignfull breaks all the way to the viewport
   edges. Both need the negative-margin escape because the column is not
   the full page width. */
.entry-content .alignwide {
	width: min(1180px, 100vw - 3rem);
	max-width: none;
	margin-inline: auto;
	position: relative;
	inset-inline-start: 50%;
	transform: translateX(-50%);
}

.entry-content .alignfull {
	width: 100vw;
	max-width: 100vw;
	margin-inline: calc(50% - 50vw);
}

.entry-content .alignfull img {
	width: 100%;
}

/* Galleries — block editor. */
.entry-content .wp-block-gallery {
	margin-bottom: 1.75rem;
}

.entry-content .wp-block-gallery.has-nested-images figure.wp-block-image img {
	width: 100%;
	object-fit: cover;
}

/* Galleries — classic editor [gallery] shortcode. */
.entry-content .gallery {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
	gap: 0.75rem;
	margin-bottom: 1.75rem;
}

.entry-content .gallery-item {
	margin: 0;
	width: auto !important;
}

.entry-content .gallery-item img {
	width: 100%;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	border: 0 !important;
}

.entry-content .gallery-caption {
	font-size: 0.82rem;
	color: var(--color-text-muted);
}

/* Embeds — responsive-embeds theme support covers most of it; this catches
   bare iframes pasted into the classic editor. */
.entry-content iframe,
.entry-content object,
.entry-content embed {
	max-width: 100%;
}

.entry-content .wp-block-embed__wrapper {
	position: relative;
}

/* Buttons, cover, media-text, columns, group. */
.entry-content .wp-block-button__link {
	background: var(--color-brand);
	color: #ffffff;
	border-radius: var(--radius);
	padding: 0.8rem 1.6rem;
	text-decoration: none;
	font-weight: 700;
	display: inline-block;
}

.entry-content .wp-block-button.is-style-outline .wp-block-button__link {
	background: transparent;
	color: var(--color-accent);
	border: 2px solid currentColor;
}

.entry-content .wp-block-cover {
	margin-bottom: 1.75rem;
}

.entry-content .wp-block-columns {
	gap: 1.75rem;
}

.entry-content .wp-block-media-text {
	margin-bottom: 1.75rem;
}

.entry-content .has-drop-cap:not(:focus)::first-letter {
	font-family: var(--font-display);
	font-size: 3.4em;
	line-height: 0.68;
	float: left;
	margin: 0.1em 0.12em 0 0;
	color: var(--color-accent);
}

/* Post-content page splitting (<!--nextpage-->). */
.page-links {
	margin-top: 2rem;
	font-weight: 700;
}

.page-links a,
.page-links > span {
	display: inline-block;
	padding: 0.35rem 0.7rem;
	border: 1px solid var(--color-border);
	border-radius: var(--radius);
	margin-inline-end: 0.35rem;
	text-decoration: none;
}

/* Tag list under a post. */
.entry__tags {
	margin-top: 2.5rem;
	font-size: 0.9rem;
	color: var(--color-text-muted);
}

.entry__tags a {
	display: inline-block;
	margin-inline-end: 0.4rem;
}

/* Sticky post flag. */
.post-card.sticky .post-card__title::before {
	content: '\2605\00a0';
	color: var(--color-accent);
}

/* ============================================================
   8. COMMENTS
   ============================================================ */

.comments-area {
	max-width: var(--measure);
	margin-inline: auto;
	padding-block: 3rem;
	border-top: 1px solid var(--color-border);
}

.comments-title,
.comment-reply-title {
	font-size: 1.5rem;
}

.comment-list {
	list-style: none;
	margin: 0 0 2.5rem;
	padding: 0;
}

.comment-list ol.children {
	list-style: none;
	margin: 1.25rem 0 0;
	padding-inline-start: 1.5rem;
	border-inline-start: 2px solid var(--color-border);
}

.comment-body {
	padding: 1.25rem 0;
	border-bottom: 1px solid var(--color-border);
}

.comment-author img {
	border-radius: 50%;
	display: inline-block;
	vertical-align: middle;
	margin-inline-end: 0.6rem;
}

.comment-metadata {
	font-size: 0.8rem;
	color: var(--color-text-muted);
}

.comment-form label {
	display: block;
	font-weight: 700;
	margin-bottom: 0.3rem;
}

/* ============================================================
   9. FORMS
   ============================================================ */

input[type='text'],
input[type='email'],
input[type='url'],
input[type='search'],
input[type='password'],
input[type='number'],
textarea,
select {
	width: 100%;
	max-width: 100%;
	font: inherit;
	font-size: 1rem;
	color: var(--color-text);
	background: var(--color-surface);
	border: 1px solid var(--color-border);
	border-radius: var(--radius);
	padding: 0.65rem 0.8rem;
	margin-bottom: 1rem;
}

button,
input[type='submit'],
.button {
	font: inherit;
	font-weight: 700;
	background: var(--color-brand);
	color: #ffffff;
	border: 0;
	border-radius: var(--radius);
	padding: 0.75rem 1.5rem;
	cursor: pointer;
	text-decoration: none;
	display: inline-block;
	transition: background 0.2s ease;
}

button:hover,
input[type='submit']:hover,
.button:hover {
	background: var(--color-brand-dark);
}

.search-form {
	display: flex;
	gap: 0.6rem;
	align-items: flex-start;
	max-width: 480px;
}

.search-form input[type='search'] {
	margin-bottom: 0;
}

/* ============================================================
   10. FOOTER
   Centred stack: disclosure, tip button, copyright. Composed to read as a
   deliberate centred block (there is no illustration here by design).
   ============================================================ */

.site-footer {
	background: var(--color-bg-alt);
	border-top: 1px solid var(--color-border);
	padding-block: clamp(3.5rem, 8vw, 5.5rem) clamp(2.5rem, 5vw, 3.5rem);
	text-align: center;
}

.site-footer__inner {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 1.75rem;
}

.site-footer__mark {
	font-family: var(--font-display);
	font-size: 1.35rem;
	letter-spacing: 0.02em;
	color: var(--color-accent);
	text-decoration: none;
	margin: 0;
}

.site-footer__rule {
	width: 64px;
	height: 2px;
	background: var(--color-brand);
	opacity: 0.5;
	border: 0;
	margin: 0;
}

.site-footer__disclosure {
	color: var(--color-text-muted);
	max-width: 62ch;
	font-size: 0.94rem;
	margin: 0;
}

.site-footer__tea-button {
	display: inline-block;
	background: var(--color-brand);
	color: #fff;
	text-decoration: none;
	font-weight: 700;
	padding: 0.8rem 1.75rem;
	border-radius: var(--radius);
	transition: background 0.2s ease, transform 0.2s ease;
}

.site-footer__tea-button:hover,
.site-footer__tea-button:focus-visible {
	background: var(--color-brand-dark);
	transform: translateY(-2px);
}

/* ---- Newsletter -------------------------------------------------
   The form itself is a cross-origin beehiiv iframe, so nothing inside it can
   be styled from here. Everything below styles the frame AROUND it — heading,
   copy, dialog, sticky bar — which is why the wrapper carries the spacing and
   the embed is left to size itself. */

.newsletter {
	max-width: 46ch;
	margin-inline: auto;
}

.newsletter__heading {
	font-family: var(--font-display);
	font-size: clamp(1.3rem, 2.6vw, 1.7rem);
	color: var(--color-accent);
	margin: 0 0 0.35rem;
}

.newsletter__copy {
	color: var(--color-text-muted);
	margin: 0 0 1rem;
}

.newsletter-embed {
	min-height: 1px;
}

/* The prompt button in the footer and the popup. Same shape as the tip button
   so the footer reads as one family rather than two competing calls to action. */
.newsletter__button {
	display: inline-block;
	background: var(--color-brand);
	color: #fff;
	text-decoration: none;
	font-weight: 700;
	padding: 0.8rem 1.75rem;
	border-radius: var(--radius);
	transition: background 0.2s ease, transform 0.2s ease;
}

.newsletter__button:hover,
.newsletter__button:focus-visible {
	background: var(--color-brand-dark);
	color: #fff;
	transform: translateY(-2px);
}

/* ---- /about/ page ---- */

/* Header photo.
   Kara asked to see the WHOLE photo, not a cropped band of it — so there is
   deliberately no fixed height and no object-fit here. The image keeps its own
   3:2 shape and simply scales.
   The trade-off she approved: at full page width the photo would be about
   790px tall and push everything else off the first screen, so the width is
   capped instead. Narrower, but nothing is cut off. */
.about-header-photo {
	position: relative;
	display: block;
	width: 100%;
	margin: 0;
}

/* Banner across the full screen width. The photo is 3:2, so at full width the
   whole uncropped image would stand about 850px tall — past banner and into
   "fills the screen". The heights below are the compromise Kara asked for:
   as much depth as possible while still reading as a banner. At a typical
   1280x800 desktop this shows roughly 80% of the frame's height, against the
   ~49% the original crop showed. */
.about-header-photo img {
	display: block;
	width: 100%;
	height: 280px;
	object-fit: cover;
	/* The horizon and her face sit above centre, so bias the crop upward —
	   a centred crop is what cut the top of the frame off before. */
	object-position: center 42%;
}

/* Banner crop: the photo is 3:2 (1920x1280) and the visible band runs from
   y=256 to y=1210 in the original — i.e. cut just under the bottom line of
   lettering on Kara's shirt, which is what she asked for.

   The two numbers below encode exactly that, with no fixed pixel heights so
   it holds at any screen width:
     aspect-ratio 1920/954 -> the visible band is 1920 wide x 954 tall.
     object-position 78.5% -> of the 326px cropped away, 78.5% (256px)
                            comes off the TOP and the rest off the bottom, which
                            lands the cut just below the last line of shirt lettering.
   Recrop by changing the ratio (taller band = smaller second number) and
   re-deriving the percentage as topCut / (1280 - bandHeight). */
@media (min-width: 768px) {
	.about-header-photo img {
		height: auto;
		aspect-ratio: 1920 / 954;
		object-position: center 78.5%;
	}
}

/* Top-edge fade to white, same technique and same numbers as the homepage hero
   (.hero__fade--top) so the two photos dissolve identically. The colour is
   layered twice on purpose: the flat rgba() line is the fallback, and browsers
   that understand color-mix() take the second, which interpolates without the
   grey band you get from fading straight to transparent. */
.about-header-photo::after {
	content: '';
	position: absolute;
	inset-inline: 0;
	top: -1px; /* -1px guarantees no hairline gap from subpixel rounding. */
	/* Shorter than the hero's 34%: at that depth the fade reached Kara's face
	   and washed it out. 15% keeps the whole fade up in the sky above her. */
	height: 15%;
	z-index: 1;
	pointer-events: none;
	background: linear-gradient(180deg, #fff 0%, rgba(255, 255, 255, 0.86) 22%, rgba(255, 255, 255, 0) 100%);
	background: linear-gradient(
		180deg,
		var(--color-bg) 0%,
		color-mix(in srgb, var(--color-bg) 86%, transparent) 22%,
		color-mix(in srgb, var(--color-bg) 0%, transparent) 100%
	);
}

/* Breathing room between the header photo and the "About" title — they were
   sitting almost flush against each other. */
.page-about .entry-header {
	padding-block-start: clamp(2rem, 5vw, 3.5rem);
}

/* Body copy on /about/ is DM Sans, at Kara's request — the rest of the site,
   and the "About" title above this container, stay Bodoni Moda.

   Set on the CONTAINER rather than on `p`, so anything she adds in the editor
   later (lists, quotes, subheadings, gallery captions) inherits it too and the
   page does not end up half sans-serif, half serif.

   Bodoni Moda is not in this fallback chain on purpose: if DM Sans fails to
   load, falling through to a system sans keeps the page sans-serif rather than
   silently reverting to the serif this rule exists to override. */
.about-page {
	font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

.about-page {
	padding-block-end: clamp(3rem, 8vw, 5rem);
}

.about-page__lede {
	font-size: 1.15rem;
	color: var(--color-text-muted);
}

/* The text column is capped HERE, on the container, not on each paragraph.
   Capping the paragraphs was what stopped the text wrapping around the floated
   photo: a paragraph limited to 68ch had only ~100px left beside a 620px
   float, too narrow to fit even one word, so every line got pushed below the
   photo instead of beside it. Capping the container leaves the paragraphs free
   to use whatever room the float gives them. */
.about-page {
	max-width: 1120px;
	margin-inline: auto;
}

.about-page p {
	max-width: none;
}

/* Floated photo the body text wraps around. */
.about-inline-photo,
.about-page .wp-block-image {
	margin: 0.5rem 0 1rem;
}

.about-inline-photo img,
.about-page .wp-block-image img {
	display: block;
	width: 100%;
	height: auto;
	border-radius: var(--radius);
}

/* WordPress does NOT put the alignment class where you would expect. For an
   aligned image it emits:

     <div class="wp-block-image">
       <figure class="alignleft size-large">...

   and core's own stylesheet then styles that INNER figure with
   `float: left; max-width: 50%; display: table`.

   Our float lives on the outer div, so core's 50% cap applied inside it: the
   div was the full 672px, the figure could only fill half of that, and the
   result was 336px of dead space between the photo and the text wrapping
   beside it. Neutralising the inner figure here — one float, on the wrapper,
   and the picture fills it — is what closes that gap.

   Kept OUTSIDE the media queries below because it is equally wrong on a phone,
   where core's 50% would leave a half-width photo stranded in the column. */
.about-page .wp-block-image > figure {
	float: none;
	display: block;
	width: 100%;
	max-width: none;
	margin: 0;
}

/* If Kara drags the image to a custom size in the editor, WordPress records it
   as an inline width on the <img> and tags the figure `.is-resized`. In that
   case the float must SHRINK to the size she picked rather than hold a fixed
   680px — otherwise choosing a smaller photo just reopens the same gap. */
.about-page .wp-block-image > figure.is-resized {
	width: fit-content;
	max-width: 100%;
}

.about-page .wp-block-image > figure.is-resized img {
	width: auto;
	max-width: 100%;
}

@media (min-width: 700px) {
	/* Doubled from 380px at Kara's request. This only works because the About
	   text column was widened to the full .wrap at the same time — at the old
	   760px column a 620px photo would have left a ~100px ribbon of text
	   beside it, which is unreadable. */
	.about-inline-photo--left,
	.about-page .wp-block-image.alignleft,
	.about-page .wp-block-image:has(> figure.alignleft) {
		float: left;
		/* Twice the area again, at Kara's request — 480px -> 680px wide.
		   NOT 960px (twice the width): at that size only ~140px of the column
		   would be left beside the photo, too narrow to fit a word, and every
		   line would drop below the photo instead of wrapping around it. The
		   text column was widened alongside this to keep ~420px for the wrap. */
		width: min(60%, 680px);
		/* Right margin is the gutter between photo and wrapped text. */
		margin: 0.4rem 2rem 1.5rem 0;
	}

	/* A custom size set in the editor wins over the 680px default: the float
	   shrinks to the picture so the text still hugs its edge, but it can never
	   grow past 680px and swallow the column. */
	.about-page .wp-block-image:has(> figure.is-resized) {
		width: auto;
		max-width: min(60%, 680px);
	}

	/* Without this the floated photo can hang below the end of the copy and
	   overlap whatever follows. */
	.about-page::after {
		content: '';
		display: block;
		clear: both;
	}
}

/* Narrow screens: no float. A 46%-wide image beside text is unreadable on a
   phone, so it becomes a normal full-width block in the flow. */
@media (max-width: 699px) {
	.about-inline-photo--left,
	.about-page .wp-block-image.alignleft,
	.about-page .wp-block-image:has(> figure.alignleft) {
		float: none;
		width: 100%;
		margin-block: 1.5rem;
	}
}

/* Mirror of the above for a photo Kara aligns RIGHT in the editor, so both
   alignment buttons behave the same way on this page. */
@media (min-width: 700px) {
	.about-page .wp-block-image.alignright,
	.about-page .wp-block-image:has(> figure.alignright) {
		float: right;
		width: min(60%, 680px);
		margin: 0.4rem 0 1.5rem 2rem;
	}
}

@media (max-width: 699px) {
	.about-page .wp-block-image.alignright,
	.about-page .wp-block-image:has(> figure.alignright) {
		float: none;
		width: 100%;
		margin-block: 1.5rem;
	}
}

/* Closing line ("I'm Kara Barros, and this is Karadventures.") — a touch
   larger and bolder so the page ends on a signature rather than trailing off
   in another body paragraph. Kara asked for it in black, not purple, so it
   uses the normal body text colour (which also keeps it readable if she ever
   turns on a dark theme). */
.about-page__signoff {
	clear: both;
	font-size: 1.1rem;
	font-weight: 600;
	color: var(--color-text);
}

/* ---- Gallery slideshow ----
   One photo at a time, scroll or swipe for the next. Applied when Kara picks
   the "Slideshow" style on a Gallery block (registered in functions.php).

   The mechanism is CSS scroll-snap, not JavaScript: the gallery becomes a
   horizontal scroller and each photo is a snap point, so a swipe on a phone or
   a two-finger scroll on a trackpad lands cleanly on the next picture with no
   script involved. gallery-slideshow.js only adds arrows and dots on top, for
   mouse users who have no sideways scroll gesture — if it fails to load the
   gallery is still a working, swipeable strip.

   Everything here is scoped to .is-style-karadventures-slideshow, so an
   ordinary gallery elsewhere on the site is untouched. */

.gallery-slideshow {
	position: relative;
	margin-block: 2rem;
	/* If the slideshow follows a floated photo that runs longer than the text,
	   it would otherwise tuck in beside that float and the breathing room below
	   would land in the wrong place. */
	clear: both;
}

/* Two inches of white space above the slideshow on desktop (Kara asked for an
   inch, then another).
   96px IS an inch: CSS defines 1in as exactly 96px regardless of the actual
   screen, so this is the literal request rather than an approximation.
   Desktop only — that much empty page is a lot of thumb-scrolling on a phone,
   where the 2rem above already reads as a clear break. */
@media (min-width: 768px) {
	.gallery-slideshow {
		margin-block-start: 192px;
	}
}

.wp-block-gallery.is-style-karadventures-slideshow {
	display: flex;
	flex-wrap: nowrap;
	gap: 0;
	overflow-x: auto;
	overscroll-behavior-x: contain;
	scroll-snap-type: x mandatory;

	/* The scrollbar is redundant once the arrows and dots are there, and a
	   grey trough under every photo looks like a mistake. Hidden in all three
	   engines; the container stays scrollable, only the bar is gone. */
	scrollbar-width: none;
	-ms-overflow-style: none;
}

.wp-block-gallery.is-style-karadventures-slideshow::-webkit-scrollbar {
	display: none;
}

/* Core lays nested gallery images out as flexible percentage-width columns
   (`width: calc(50% - var(--wp--style--unstable-gallery-gap))` and friends).
   Those rules have to be overridden outright, or the "one at a time" is
   really "two at a time, slightly clipped" — hence the !important-free but
   deliberately specific selector below. */
.wp-block-gallery.is-style-karadventures-slideshow.has-nested-images > figure.wp-block-image {
	flex: 0 0 100%;
	width: 100%;
	max-width: 100%;
	margin: 0;
	scroll-snap-align: center;
	scroll-snap-stop: always; /* Never skip past a photo on a fast flick. */
}

/* Frame size and "show the whole photo".

   `max-height` rather than `height`, deliberately. Core ships
     .wp-block-gallery.has-nested-images.is-cropped figure.wp-block-image:not(#individual-image) img { height: 100% }
   which scores identically to anything reasonable we can write here, so a
   competing `height` turns into a cascade-order coin toss that depends on when
   WordPress happens to print its block styles. `max-height` is a property core
   does not set at all, so it simply wins — no fight, no fragility.

   `contain`, not `cover`: Kara wants the WHOLE photo visible. cover fills the
   frame edge to edge but crops whatever does not fit, which sliced the tops
   and bottoms off her portrait shots. contain shrinks the photo until all of
   it is inside the frame instead, letterboxing anything that is a different
   shape. The tinted figure background below is what those letterbox bars are.

   To resize the whole slideshow, change these three numbers: floor on a phone,
   how it scales in between, and the desktop ceiling. */
.wp-block-gallery.is-style-karadventures-slideshow.has-nested-images > figure.wp-block-image:not(#individual-image) img,
.wp-block-gallery.is-style-karadventures-slideshow.has-nested-images > figure.wp-block-image:not(#individual-image) a img {
	display: block;
	width: 100%;
	max-height: clamp(200px, 30vw, 380px);
	object-fit: contain;
	border-radius: var(--radius);
}

/* The figure is a column so the caption can sit under the photo. No
   background of its own — see the note on the image below. */
.wp-block-gallery.is-style-karadventures-slideshow.has-nested-images > figure.wp-block-image:not(#individual-image) {
	display: flex;
	flex-direction: column;
	background: transparent;
	border-radius: 0;
}

/* No letterbox tint. Kara asked for the space beside a photo to be plain page
   background, so the picture reads as sitting on the page rather than inside a
   grey box. Left explicit rather than deleted so it is obvious this is a
   decision, not an omission: put a colour back here if the bars ever need to
   be visible again. */
.wp-block-gallery.is-style-karadventures-slideshow.has-nested-images > figure.wp-block-image:not(#individual-image) img {
	background: transparent;
}

/* Captions BELOW the photo, not printed across it.

   Core's gallery caption is absolutely positioned over the bottom of the image
   with a black gradient behind it, clipped to one line. Kara asked for it under
   the picture instead, and larger. Every declaration here is undoing one piece
   of that: position/inset put it back in the normal flow, the background and
   padding remove the dark band, and the white-space/overflow trio let a long
   caption wrap onto as many lines as it needs instead of being cut off with an
   ellipsis.

   The :not(#individual-image) weight is needed again — core's caption rules use
   the same trick. */
.wp-block-gallery.is-style-karadventures-slideshow.has-nested-images > figure.wp-block-image:not(#individual-image) figcaption {
	position: static;
	inset: auto;
	max-width: none;
	margin: 0;
	padding: 0.75rem 0.5rem 0;
	background: none;
	color: var(--color-text-muted);
	font-size: 1rem;
	line-height: 1.5;
	text-align: center;
	white-space: normal;
	overflow: visible;
	text-overflow: clip;
	flex-grow: 0;
}

.wp-block-gallery.is-style-karadventures-slideshow :focus-visible {
	outline: 3px solid var(--color-brand);
	outline-offset: 3px;
}

/* --- Arrows --- */

.gallery-slideshow__arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	z-index: 2;
	display: grid;
	place-items: center;
	width: 44px; /* 44px is the minimum comfortable touch target. */
	height: 44px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	cursor: pointer;
	color: var(--color-brand);
	/* No pill, no shadow — Kara asked for the arrows to sit on plain white.
	   With contain framing there is almost always page background either side
	   of the photo, so the bare glyph has clean ground to sit on.

	   The drop-shadow is the exception that makes that safe: it is a white halo,
	   invisible against the white page, and only shows up on the rare very wide
	   photo that reaches all the way out to the arrow. Without it such a photo
	   could swallow the arrow entirely. */
	background: transparent;
	filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.9));
	transition: opacity 0.2s ease, transform 0.2s ease;
}

.gallery-slideshow__arrow:hover {
	transform: translateY(-50%) scale(1.15);
}

.gallery-slideshow__arrow--prev {
	inset-inline-start: 0.75rem;
}

.gallery-slideshow__arrow--next {
	inset-inline-end: 0.75rem;
}

/* Kara asked for the arrows to sit farther from the photos. On a wide screen
   they move outward, past the edge of the photo and onto the page background,
   rather than hovering over the picture itself.

   Kept to the wide breakpoint on purpose: on a phone there is no page gutter
   to move into, and an arrow pushed outside the box would fall off-screen. */
@media (min-width: 782px) {
	.gallery-slideshow__arrow--prev {
		inset-inline-start: -1.75rem;
	}

	.gallery-slideshow__arrow--next {
		inset-inline-end: -1.75rem;
	}
}

/* At the first or last photo the arrow has nowhere to go. Faded and
   click-through rather than removed, so the other arrow does not jump
   position as you move along the gallery. */
.gallery-slideshow__arrow[disabled] {
	opacity: 0;
	pointer-events: none;
}

/* --- Dots --- */

.gallery-slideshow__dots {
	display: flex;
	justify-content: center;
	gap: 0.5rem;
	margin-block-start: 0.9rem;
}

.gallery-slideshow__dot {
	width: 8px;
	height: 8px;
	border-radius: 50%;
	background: var(--color-border);
	transition: background-color 0.2s ease, transform 0.2s ease;
}

.gallery-slideshow__dot.is-current {
	background: var(--color-brand);
	transform: scale(1.35);
}

@media (prefers-reduced-motion: reduce) {
	.gallery-slideshow__dot,
	.gallery-slideshow__arrow {
		transition: none;
	}
}

/* ---- About page: slideshow + video side by side ---- */

/* Kara asked for the gallery on the left and her YouTube video on the right,
   the same size. Both live in a two-column block she can edit in wp-admin —
   the CSS below only makes the two boxes match; the layout itself is
   WordPress's own Columns block.

   The two inches of white space that used to sit on .gallery-slideshow now
   sit on the ROW, so the video lines up with the gallery instead of riding
   two inches higher than it. */
.about-media-row {
	clear: both;
}

@media (min-width: 782px) {
	/* Doubled-up class: WordPress prints its own layout rule for the columns
	   block (a single class, same specificity) after this stylesheet, so a
	   plain .about-media-row loses the gap declaration to it. */
	.wp-block-columns.about-media-row {
		margin-block-start: 192px;
		/* Wide enough that the slideshow's right-hand arrow, which now sits
		   outside the photo, has clear white space rather than crowding the
		   video next to it. */
		gap: 4rem;
	}

	.about-media-row .gallery-slideshow {
		margin-block: 0;
	}
}

/* Same size, literally: the video embed is a 16:9 box filling its column, so
   the gallery's photos are given the identical box. object-fit stays
   `contain`, so a portrait photo is still shown whole — it just sits inside
   the same frame as the video rather than setting its own height.

   `height: auto` and the :not(#individual-image) hack are both needed: core
   sets `height: 100%` on these images using that same fake-ID selector, and
   a fixed height would override aspect-ratio. Do not "simplify" either away. */
.about-media-row .wp-block-gallery.is-style-karadventures-slideshow.has-nested-images > figure.wp-block-image:not(#individual-image) img,
.about-media-row .wp-block-gallery.is-style-karadventures-slideshow.has-nested-images > figure.wp-block-image:not(#individual-image) a img {
	height: auto;
	max-height: none;
	aspect-ratio: 16 / 9;
}

/* Vertical centring is set on the columns in the editor; this keeps the video
   from stretching if Kara ever switches that off. */
.about-media-row .wp-block-embed__wrapper {
	margin: 0;
}

/* ---- /subscribe/ page ---- */

.subscribe-page {
	max-width: 34rem;
	margin-inline: auto;
	padding-block-end: clamp(3rem, 8vw, 5rem);
	text-align: center;
}

/* The page title sits in .entry-header, outside .subscribe-page, so it does not
   inherit the centring below — it needs its own rule to line up over the text
   beneath it. */
.page-subscribe .entry-title {
	text-align: center;
}

.subscribe-page__lede {
	color: var(--color-text-muted);
	margin-bottom: 2rem;
}

/* beehiiv's fields are left-aligned inside the iframe and there is no
   alignment control in their designer, so the form is centred as a block
   instead — the closest thing available from outside a cross-origin frame. */
.newsletter-embed--page {
	/* Kara asked for the box a third wider: 24rem -> 32rem. That is the desktop
	   width. On a phone the viewport is the real limit, not this value, so the
	   rule below gives back most of the page gutter to get as close to it as
	   the screen allows. */
	max-width: 32rem;
	margin-inline: auto;
}

/* The gutter that actually limits the box on a phone belongs to the .wrap
   ABOVE .subscribe-page, not to .subscribe-page itself — padding added here
   would stack on top of it and make the box narrower, not wider. */
@media (max-width: 34rem) {
	.wrap--subscribe {
		padding-inline: 0.75rem;
	}
}

/* Consent small print under the signup form. Deliberately quiet: muted colour,
   0.75rem (about 14px at the site's 120% root), and a matching link so it does
   not shout at anyone mid-signup. */
.newsletter-embed__consent {
	margin: 0.75rem 0 0;
	font-size: 0.75rem;
	line-height: 1.5;
	color: var(--color-text-muted);
	text-align: center;
}

.newsletter-embed__consent a {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

.newsletter-embed__consent a:hover,
.newsletter-embed__consent a:focus-visible {
	color: var(--color-brand);
}

.newsletter-embed__fallback {
	display: inline-block;
	background: var(--color-brand);
	color: #fff;
	text-decoration: none;
	font-weight: 700;
	padding: 0.8rem 1.75rem;
	border-radius: var(--radius);
}

/* ---- Dialog ---- */

.newsletter-modal[hidden] {
	display: none;
}

.newsletter-modal {
	position: fixed;
	inset: 0;
	z-index: 1000;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 1.25rem;
}

.newsletter-modal__backdrop {
	position: absolute;
	inset: 0;
	background: rgba(32, 16, 54, 0.55);
}

.newsletter-modal__panel {
	position: relative;
	background: var(--color-bg);
	border-radius: var(--radius);
	padding: clamp(1.75rem, 4vw, 2.5rem);
	max-width: 32rem;
	width: 100%;
	/* The beehiiv iframe can be tall; on a short laptop window the panel must
	   scroll rather than run off the top and bottom of the screen. */
	max-height: calc(100vh - 2.5rem);
	overflow-y: auto;
	text-align: center;
	box-shadow: 0 18px 50px rgba(32, 16, 54, 0.3);
}

.newsletter-modal__close {
	position: absolute;
	top: 0.5rem;
	right: 0.5rem;
	width: 44px;
	height: 44px;
	display: flex;
	align-items: center;
	justify-content: center;
	background: none;
	border: 0;
	border-radius: 50%;
	font-size: 1.6rem;
	line-height: 1;
	color: var(--color-text-muted);
	cursor: pointer;
}

.newsletter-modal__close:hover,
.newsletter-modal__close:focus-visible {
	color: var(--color-brand);
	background: var(--color-bg-alt);
}

/* Stop the page behind the dialog from scrolling under it. */
body.has-newsletter-modal {
	overflow: hidden;
}

/* ---- Mobile sticky bar ---- */

.newsletter-bar[hidden] {
	display: none;
}

.newsletter-bar {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 900;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.6rem;
	padding: 0.9rem 2.75rem 0.9rem 1rem;
	/* Clear the iPhone home indicator, which otherwise sits over the button. */
	padding-bottom: calc(0.9rem + env(safe-area-inset-bottom, 0px));
	background: var(--color-brand);
	color: #fff;
	text-align: center;
	box-shadow: 0 -6px 24px rgba(32, 16, 54, 0.25);
}

.newsletter-bar__text {
	margin: 0;
	font-size: 0.95rem;
	line-height: 1.35;
}

.newsletter-bar__button {
	background: #fff;
	color: var(--color-brand);
	border: 0;
	border-radius: var(--radius);
	font-weight: 700;
	font-size: 0.95rem;
	padding: 0.7rem 1.5rem;
	min-height: 44px;
	cursor: pointer;
}

.newsletter-bar__dismiss {
	position: absolute;
	top: 0.15rem;
	right: 0.15rem;
	width: 44px;
	height: 44px;
	background: none;
	border: 0;
	color: #fff;
	font-size: 1.5rem;
	line-height: 1;
	cursor: pointer;
	opacity: 0.85;
}

/* The bar is fixed, so it would otherwise sit on top of the last few lines of
   the footer. Only applied while the bar is actually showing. */
body.has-newsletter-bar .site-footer {
	padding-block-end: 9rem;
}

/* Social icons.
   Circles are 44px so they clear the 44x44 minimum comfortable touch target,
   even though the glyph inside is only 20px. Shrinking the circle to fit the
   glyph would make these genuinely hard to tap on a phone. */
.social-links {
	display: flex;
	justify-content: center;
	gap: 0.75rem;
}

.social-links__link {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	border-radius: 50%;
	color: var(--color-brand);
	background: transparent;
	border: 1px solid var(--color-border);
	text-decoration: none;
	transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.social-links__link:hover,
.social-links__link:focus-visible {
	background: var(--color-brand);
	border-color: var(--color-brand);
	color: #ffffff;
	transform: translateY(-2px);
}

/* The glyph inherits the link's colour, which is what makes the hover state a
   single rule rather than a second set of icon assets. */
.social-links__icon {
	display: block;
}

.site-footer__meta {
	color: var(--color-text-muted);
	font-size: 0.82rem;
	margin: 0;
}

/* Privacy Policy link, sitting quietly under the copyright line. */
.site-footer__privacy {
	margin: 0.4rem 0 0;
	font-size: 0.82rem;
}

.site-footer__privacy a {
	color: var(--color-text-muted);
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

.site-footer__privacy a:hover,
.site-footer__privacy a:focus-visible {
	color: var(--color-brand);
}


/* --- Latest blog post --- */

/* Sits inside the footer, above the wordmark. It fills itself in from
   whatever was published most recently; see footer.php. The footer supplies
   its own vertical rhythm, so this carries none of its own. */
.latest-post {
	width: 100%;
	padding-block: 0;
	margin-bottom: 0.75rem;
}

.latest-post__title {
	margin: 0 0 clamp(1rem, 2vw, 1.5rem);
	font-family: var(--font-display);
	/* Scaled for the footer: reads as a section label there, not as a page
	   heading competing with the wordmark below it. */
	font-size: clamp(1.35rem, 2.4vw, 1.8rem);
	line-height: 1.15;
	text-align: center;
}

.latest-post__card {
	display: grid;
	gap: clamp(1rem, 2.5vw, 1.75rem);
	align-items: center;
	text-decoration: none;
	color: inherit;
	/* The footer centres everything; the card is a block of its own, so it is
	   centred as a whole and its words read from the left inside it. */
	max-width: 620px;
	margin-inline: auto;
	text-align: left;
}

@media (min-width: 700px) {
	.latest-post__card {
		/* Picture at roughly half the width it had as a full-width section. */
		grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
	}
}

/* Fixed shape for the picture so the panel keeps the same height whether the
   newest post has a wide photo, a tall one, or none at all. */
.latest-post__media {
	position: relative;
	max-width: 260px;
	margin-inline: auto;
	width: 100%;
	overflow: hidden;
	border-radius: var(--radius);
	aspect-ratio: 4 / 3;
	background: var(--color-bg-alt);
}

.latest-post__image {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* Stand-in when there is no picture to show — a soft brand-coloured block
   rather than an empty hole or a broken image. */
.latest-post__media-blank {
	position: absolute;
	inset: 0;
	/* Dark, with a gentle ombre across it rather than a flat fill. */
	background: linear-gradient(145deg, var(--color-brand) 0%, var(--color-brand-dark) 100%);
}

.latest-post__date {
	margin: 0 0 0.5rem;
	color: var(--color-text-muted);
	font-size: 0.85rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.latest-post__heading {
	margin: 0 0 0.75rem;
	font-family: var(--font-display);
	font-size: clamp(1.35rem, 2.6vw, 2rem);
	line-height: 1.2;
}

.latest-post__excerpt {
	margin: 0 0 1.25rem;
	color: var(--color-text-muted);
	line-height: 1.6;
}

.latest-post__cta {
	display: inline-block;
	font-weight: 700;
	color: var(--color-brand);
	text-decoration: none;
	border-bottom: 2px solid currentColor;
	padding-bottom: 0.1rem;
}

/* The whole card is one link, so the hover state belongs to the card, not to
   the words inside it. */
.latest-post__card:hover .latest-post__image,
.latest-post__card:focus-visible .latest-post__image {
	transform: scale(1.03);
}

.latest-post__image {
	transition: transform 0.4s ease;
}

.latest-post__card:focus-visible {
	outline: 3px solid var(--color-brand);
	outline-offset: 6px;
	border-radius: var(--radius);
}

/* The placeholder is not a link, so it must not pick up the pointer or the
   hover lift. Its Subscribe button is the only clickable thing in it. */
.latest-post__card--placeholder {
	cursor: default;
}

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

	.latest-post__card:hover .latest-post__image {
		transform: none;
	}
}
/* ============================================================
   11. 404 / SEARCH / NO RESULTS
   ============================================================ */

.notice-block {
	padding-block: clamp(3.5rem, 9vw, 7rem);
	max-width: 60ch;
	margin-inline: auto;
	text-align: center;
}

.notice-block .search-form {
	margin-inline: auto;
	justify-content: center;
}

/* Empty category archives. Sized up so the page reads as a deliberate
   "not written yet" rather than an error, and coloured with --color-accent
   (not --color-brand, which is only 2.4:1 as text on the page background). */
.notice-block__coming-soon {
	font-size: clamp(2.2rem, 5vw, 3.2rem);
	color: var(--color-accent);
	margin-bottom: 0.75rem;
}

.notice-block__home-link {
	font-weight: 700;
}

/* ============================================================
   12. WORDPRESS ADMIN BAR
   With the admin bar showing, the fixed header would tuck underneath it.

   On the FRONT END the admin bar is position:absolute, so it scrolls away —
   a hard `top: 32px` would leave a 32px strip of page showing above the
   header once it had gone. navigation.js therefore keeps
   --wp-admin-bar-offset equal to however much of the bar is actually
   overlapping the top of the viewport right now (32 -> 0 as it scrolls off).
   The static values below are the no-JS / pre-JS fallback.
   ============================================================ */

.admin-bar {
	--wp-admin-bar-offset: 32px;
}

@media screen and (max-width: 782px) {
	.admin-bar {
		--wp-admin-bar-offset: 46px;
	}
}

/* `js` is set on <html> and `admin-bar` on <body>, so the js class has to
   come FIRST in the selector — `.admin-bar .js` would never match. */
.js .admin-bar .nav-drawer,
.js body.admin-bar .nav-drawer {
	padding-top: 7rem;
}

/* ============================================================
   LINK CARDS

   A preview card for an outbound link: picture on the left, title,
   blurb and a "go" line on the right, the whole thing clickable.

   Built out of ordinary core blocks (Group > Image + Heading +
   Paragraphs) so every word stays editable in wp-admin — only the
   look lives here. Apply it from the editor with the Group block's
   "Link card" style, or by typing `link-card` into Advanced > CSS
   class. Stacks to a single column on phones.
   ============================================================ */

/* WordPress wraps every Group block's children in an .__inner-container
   div. Left alone it becomes the grid's only child and the two-column
   layout never happens, so it is made transparent to layout here. The rules
   below then apply whether or not WordPress emits it. */
.entry-content .link-card .wp-block-group__inner-container {
	display: contents;
}

.entry-content .link-card {
	position: relative;
	display: grid;
	margin-block: 0 2.25rem;
	border: 1px solid var(--color-border);
	border-radius: 10px;
	/* --color-surface, not a hard-coded white: it flips to the dark panel
	   colour in dark mode, which keeps the text on it readable. */
	background: var(--color-surface);
	overflow: hidden;
	transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}

@media (min-width: 600px) {
	.entry-content .link-card {
		grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
		align-items: stretch;
	}

	/* Until a picture is chosen the media block saves as nothing at all, so
	   the card would otherwise keep an empty 200px column. */
	.entry-content .link-card:not(:has(.link-card__media)) {
		grid-template-columns: minmax(0, 1fr);
	}
}

.entry-content .link-card:hover,
.entry-content .link-card:focus-within {
	border-color: var(--color-accent);
	box-shadow: 0 8px 24px rgba(34, 26, 43, 0.12);
	transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
	.entry-content .link-card {
		transition: none;
	}

	.entry-content .link-card:hover,
	.entry-content .link-card:focus-within {
		transform: none;
	}
}

/* Media side. Works with a real Image block or the gradient placeholder
   below, and keeps a constant shape either way so cards line up. */
.entry-content .link-card__media {
	margin: 0;
	aspect-ratio: 4 / 3;
	background: linear-gradient(150deg, var(--color-brand) 0%, var(--color-brand-dark) 100%);
	display: grid;
	place-items: center;
	overflow: hidden;
}

@media (min-width: 600px) {
	.entry-content .link-card__media {
		aspect-ratio: auto;
		min-height: 100%;
	}
}

.entry-content .link-card__media img {
	width: 100%;
	height: 100%;
	margin: 0;
	object-fit: cover;
	display: block;
}

/* Product shots and artwork sit on a plain background, so cropping them to
   fill the panel lops the edges off. This variant fits the whole image
   inside instead and sits them on plain white so no colour frames the edges. */
.entry-content .link-card__media--contain {
	padding: 0.85rem;
	/* White, not the brand gradient: these shots already sit on white, so
	   anything else frames them with a visible border of colour. */
	background: #fff;
}

.entry-content .link-card__media--contain img {
	object-fit: contain;
}

/* The stand-in shown until Kara drops a real photo in: a big glyph on the
   brand gradient. Swap the whole media group for an Image block once a
   real photo exists. */
.entry-content .link-card__glyph {
	margin: 0;
	padding: 1.75rem 1rem;
	font-size: 3.25rem;
	line-height: 1;
	text-align: center;
}

.entry-content .link-card__body {
	padding: 1.5rem 1.6rem;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: 0.55rem;
}

/* Depth-agnostic: catches the blocks whether WordPress nests them in an
   inner container or not. The card supplies its own rhythm via flex gap. */
.entry-content .link-card__body :is(p, h2, h3, h4, ul, ol) {
	margin: 0;
}

.entry-content .link-card__eyebrow {
	font-size: 0.82rem;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--color-text-muted);
}

.entry-content .link-card__title {
	margin: 0;
	font-family: var(--font-display);
	font-size: clamp(1.25rem, 2.2vw, 1.5rem);
	line-height: 1.25;
}

/* The title link is stretched over the whole card, so clicking anywhere
   follows it. Anything that needs its own click sits above it via z-index. */
.entry-content .link-card__title a {
	color: var(--color-text);
	text-decoration: none;
}

.entry-content .link-card__title a::after {
	content: '';
	position: absolute;
	inset: 0;
}

.entry-content .link-card:hover .link-card__title a,
.entry-content .link-card__title a:focus-visible {
	color: var(--color-accent);
	text-decoration: underline;
	text-underline-offset: 3px;
}

.entry-content .link-card__desc {
	font-size: 0.97rem;
	line-height: 1.6;
	color: var(--color-text-muted);
}

.entry-content .link-card__cta {
	font-size: 0.95rem;
	font-weight: 700;
	color: var(--color-accent);
}

/* ============================================================
   WIDE READING COLUMN ON LARGE SCREENS

   Standing rule, at Kara's request: on a desktop-sized window the
   reading column runs about 40% wider than the classic measure —
   68ch becomes 95ch — 869px to 1214px, a touch under 40% wider.

   Two things have to move together, which is why this is one block:

   1. --measure itself, which sets the column. It also drives the
      archive page description and the comments list, so those stay
      in step with the article automatically.
   2. The .wrap around the article, which is normally capped at
      --container-max (1180px) and would otherwise clip the wider
      column back to about 1130px. Only the wraps that actually hold
      article content are widened — the header, footer and homepage
      panels keep their own widths.

   Gated at 1360px: the wide column plus its gutters comes to about
   1272px, and the window needs to be wider than that for the page to
   still look centred rather than edge-to-edge. Below 1360px the
   existing 68ch column suits the space available.
   ============================================================ */

@media (min-width: 1360px) {
	:root {
		--measure: 95ch;
	}

	.entry__header.wrap,
	.entry__featured.wrap,
	.wrap:has(> .entry-content),
	.wrap:has(> .comments-area) {
		/* 5rem, not the 3rem of side padding: ch resolves against each
		   element's own font-size, and .wrap is set slightly smaller than
		   .entry-content, so an exact sum would clip the column by a few
		   pixels. The slack costs nothing and guarantees the measure wins. */
		max-width: calc(var(--measure) + 5rem);
	}
}
