/*
Theme Name: FVM
Theme URI: https://fvm.house
Author: Francisco Vera
Author URI: https://fvm.house
Description: Personal site theme for Francisco Vera — a homepage plus Notes, Sketchbook and Photos as custom collections, with Colophon and Me pages. A clean, blocks-first WordPress block theme (Full Site Editing) set in Manrope.
Requires at least: 6.7
Tested up to: 6.9
Requires PHP: 7.4
Version: 0.6.1
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: fvm
Tags: portfolio, blog, photography, full-site-editing, block-patterns, custom-colors, custom-menu, editor-style
*/

/*
 * Almost all styling lives in theme.json and in block attributes on the
 * templates and patterns. Keep this file only for the few things neither can
 * express: pseudo-elements, :hover / current-page state, media queries, viewport
 * units and JS-driven effects. It is loaded on the front end and inside the
 * editor (via add_editor_style in functions.php).
 *
 * Every rule below carries the reason it cannot be a block attribute.
 */

/* Grayscale antialiasing (macOS only) — renders Manrope lighter and closer to
 * the Figma source. No-op on other platforms. */
body {
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

/* ---- Navigation pills (internal header + home footer) ------------------- */

/*
 * The pill's box — padding, radius, size, weight and resting colour — is set as
 * block attributes on the paragraph in patterns/internal-header.php and
 * patterns/home-nav.php. What is left here is everything blocks cannot express:
 * the link colour inheriting from the pill, the :hover and current-section
 * states, and the 4px indicator dot (a pseudo-element).
 */
.fvm-nav-link {
	position: relative; /* Positioning context for the dot. */
	display: inline-flex;
	align-items: center;
	justify-content: center;
	white-space: nowrap;
}

.fvm-nav-link a {
	color: inherit; /* Let the paragraph's textColor attribute drive the pill. */
	text-decoration: none;
}

/*
 * Hover and current-section take the section colour. The resting colour is a
 * block attribute (dark grey in the header, the section colour on the home
 * page), which WordPress serialises as `.has-*-color { color: … !important }` —
 * so these have to be !important too, or the resting colour wins on hover.
 */
.fvm-nav-link.is-sketch:hover,
.fvm-nav-link.is-sketch:focus-within,
.post-type-archive-sketch .fvm-nav-link.is-sketch,
.single-sketch .fvm-nav-link.is-sketch {
	color: var(--wp--preset--color--dark-green) !important;
}

.fvm-nav-link.is-note:hover,
.fvm-nav-link.is-note:focus-within,
.post-type-archive-note .fvm-nav-link.is-note,
.single-note .fvm-nav-link.is-note {
	color: var(--wp--preset--color--dark-blue) !important;
}

.fvm-nav-link.is-photo:hover,
.fvm-nav-link.is-photo:focus-within,
.post-type-archive-photo .fvm-nav-link.is-photo,
.single-photo .fvm-nav-link.is-photo {
	color: var(--wp--preset--color--dark-red) !important;
}

/*
 * Language switcher (English / Español). A separate class from .fvm-nav-link on
 * purpose — it must not pick up the section colours or the indicator dot.
 */
.fvm-lang-link {
	display: inline-flex;
	align-items: center;
	white-space: nowrap;
	border-radius: 200px;
	line-height: 1.3;
}

.fvm-lang-link a {
	color: inherit; /* Let the paragraph's textColor attribute drive the pill. */
	text-decoration: none;
}

/*
 * Only the language a visitor can switch *to* should ever show — showing both
 * reads as "pick a language" when there is really just one live link. Which
 * pill is current is decided here, off the aria-current the binding emits — not
 * by a per-pill block attribute, because the two pills are identical static
 * markup so that a Site Editor save cannot freeze the wrong one as current.
 */
.fvm-lang-link:has(a[aria-current]) {
	display: none;
}

.fvm-lang-link:hover,
.fvm-lang-link:focus-within {
	/* !important because the resting grey ships as `.has-grey-1-color { color: … !important }`. */
	color: var(--wp--preset--color--dark-grey) !important;
}

/*
 * Theme toggle (Light / Dark / System). Sits next to the language switcher on
 * purpose, styled the same way — grey at rest, dark grey on hover — but kept
 * as its own class so it never matches the language switcher's :has() rule
 * above. The label text and behaviour are entirely JS-driven; see
 * assets/js/theme-toggle.js.
 */
.fvm-theme-toggle a {
	color: inherit;
	text-decoration: none;
}

.fvm-theme-toggle:hover,
.fvm-theme-toggle:focus-within {
	color: var(--wp--preset--color--dark-grey) !important;
}

/*
 * Secondary nav (Colophon / About me) in the internal footer, styled to match
 * the language switcher and theme toggle it sits between: grey at rest (no
 * class of its own — inherited from the wp:columns wrapper), dark grey with no
 * underline on hover, same as the two classes above.
 */
.fvm-secondary-link a {
	color: inherit;
	text-decoration: none;
}

.fvm-secondary-link:hover,
.fvm-secondary-link:focus-within {
	color: var(--wp--preset--color--dark-grey) !important;
}

/*
 * Mobile menu overlay's Close control (core/navigation-overlay-close, in
 * patterns/mobile-menu.php). Same treatment as the language switcher and
 * theme toggle above — grey at rest, dark grey on hover/focus — kept as its
 * own class for the same reason those two are separate from each other.
 */
.fvm-overlay-close:hover,
.fvm-overlay-close:focus-visible {
	color: var(--wp--preset--color--dark-grey) !important;
}

/* Indicator dot — 4px, centered 2px below the label, in the label's own colour. */
.fvm-nav-link::after {
	content: "";
	position: absolute;
	left: 50%;
	bottom: -2px;
	transform: translateX(-50%);
	width: 4px;
	height: 4px;
	border-radius: 50%;
	background: currentColor;
	opacity: 0;
	transition: opacity 0.15s ease;
}

.fvm-nav-link:hover::after,
.fvm-nav-link:focus-within::after,
.post-type-archive-sketch .fvm-nav-link.is-sketch::after,
.single-sketch .fvm-nav-link.is-sketch::after,
.post-type-archive-note .fvm-nav-link.is-note::after,
.single-note .fvm-nav-link.is-note::after,
.post-type-archive-photo .fvm-nav-link.is-photo::after,
.single-photo .fvm-nav-link.is-photo::after {
	opacity: 1;
}

/* ---- Home page (front-page.html) --------------------------------------- */

/*
 * Pointer glow — a very soft shadow that appears only while hovering a nav pill
 * (see assets/js/home-pointer-glow.js, which creates this element and drives its
 * transform). Sits behind the page content on the white canvas. The element is a
 * fixed 80px box (SIZE in the JS must match) moved purely via transform. The JS
 * sets --glow-color to the hovered pill's section colour; registering it as
 * <color> lets that swap crossfade.
 *
 * Two layers: ::before is the blurred coloured blob; ::after is a subtle grain
 * confined to the blob's shape. The grain lives on its own (un-blurred) layer
 * so the blur on ::before doesn't smear the noise away. --glow-color inherits
 * so ::before can read (and crossfade) it.
 */
@property --glow-color {
	syntax: "<color>";
	inherits: true;
	initial-value: transparent;
}

.fvm-pointer-glow {
	position: fixed;
	top: 0;
	left: 0;
	width: 80px;
	height: 80px;
	z-index: -1;
	pointer-events: none;
	opacity: 0;
	transition: opacity 0.8s ease, --glow-color 0.9s ease;
	will-change: transform;
	--glow-color: transparent;
}

.fvm-pointer-glow.is-visible {
	opacity: 0.15;
}

/* Soft coloured blob. */
.fvm-pointer-glow::before {
	content: "";
	position: absolute;
	inset: 0;
	border-radius: 50%;
	background: radial-gradient(circle at 50% 50%, var(--glow-color), transparent 65%);
	filter: blur(10px);
}

/* Subtle film grain, masked to the blob's circular falloff. */
.fvm-pointer-glow::after {
	content: "";
	position: absolute;
	inset: 0;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='fvmg'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23fvmg)'/%3E%3C/svg%3E");
	background-size: 20px 20px;
	mix-blend-mode: soft-light;
	opacity: 0.5;
	-webkit-mask: radial-gradient(circle at 50% 50%, #fff, transparent 60%);
	mask: radial-gradient(circle at 50% 50%, #fff, transparent 60%);
}

/* No scrollbar; zero the root padding so the column is exactly the viewport. */
body.home {
	overflow: hidden;
	--wp--style--root--padding-top: 0px;
	--wp--style--root--padding-right: 0px;
	--wp--style--root--padding-bottom: 0px;
	--wp--style--root--padding-left: 0px;
}

/* Full-height column: wordmark top, body fills the middle, nav pinned to the
 * bottom. Viewport units and the admin-bar offset have no block equivalent. */
.fvm-home {
	height: 100dvh;
	display: flex;
	flex-direction: column;
	overflow: hidden;
}

.admin-bar .fvm-home {
	height: calc(100dvh - 32px);
}

@media screen and (max-width: 782px) {
	.admin-bar .fvm-home {
		height: calc(100dvh - 46px);
	}
}

/* Header wordmark — "fvm" crossfades into the full name on hover. Both labels
 * are stacked in one grid cell so the box stays the width of the full name (no
 * layout jump, stable hover target). */
.fvm-wordmark a {
	color: inherit;
	text-decoration: none;
}

.fvm-wordmark__link {
	display: inline-grid;
	white-space: nowrap;
}

.fvm-wordmark__short,
.fvm-wordmark__full {
	grid-area: 1 / 1; /* stack both labels in the same cell */
	text-align: center;
	transition: opacity 0.15s ease-in;
}

.fvm-wordmark__full {
	opacity: 0;
}

.fvm-wordmark__link:hover .fvm-wordmark__short,
.fvm-wordmark__link:focus-visible .fvm-wordmark__short {
	opacity: 0;
}

.fvm-wordmark__link:hover .fvm-wordmark__full,
.fvm-wordmark__link:focus-visible .fvm-wordmark__full {
	opacity: 1;
}

/*
 * Body fills the space between wordmark and nav and centers its content. The
 * 480px column, the 40px side padding and the 20px gap are block attributes on
 * the group (patterns/home-body.php); only the viewport-filling flexbox and the
 * image clamp that keeps the page from scrolling live here.
 */
.fvm-home-body {
	flex: 1 1 auto;
	min-height: 0;
	display: flex;
	flex-direction: column;
	justify-content: center;
}

.fvm-home-body > figure {
	min-height: 0;
}

.fvm-home-body > figure > img {
	display: block;
	width: 100%;
	height: auto;
	max-height: 100%;
	object-fit: contain;
}

/* ---- Inner pages -------------------------------------------------------- */

/*
 * Archive page wrappers (notes, photos): min-height fills the viewport
 * (minus the admin bar, which has no block equivalent) so short result sets
 * still push the footer to the bottom of the screen; longer content simply
 * grows past it. The flex column plus the direct-child main flex-grow (below)
 * is what pins the footer — block attributes have no "grow to fill remaining
 * space" concept. Selectors cover both the theme-file markup (className) and
 * the Site Editor's saved customization (HTML anchor, which renders as an id).
 */
.notes-container,
#notes-container,
.photos-container,
#photos-container,
.about-container,
#about-container {
	display: flex;
	flex-direction: column;
	min-height: 100vh;
}

.admin-bar .notes-container,
.admin-bar #notes-container,
.admin-bar .photos-container,
.admin-bar #photos-container,
.admin-bar .about-container,
.admin-bar #about-container {
	min-height: calc(100vh - 32px);
}

@media screen and (max-width: 782px) {
	.admin-bar .notes-container,
	.admin-bar #notes-container,
	.admin-bar .photos-container,
	.admin-bar #photos-container,
	.admin-bar .about-container,
	.admin-bar #about-container {
		min-height: calc(100vh - 46px);
	}
}

.notes-main,
#notes-container > main,
.photos-main,
#photos-container > main,
.about-main,
#about-container > main {
	flex: 1 1 auto;
}

/*
 * Each main-navigation section has a colour (Sketchbook green, Notes blue,
 * Photos red), exposed as --fvm-section via the body class for the mobile
 * header dot. The nav's own active state is handled by the .fvm-nav-link rules
 * above.
 */
.post-type-archive-sketch,
.single-sketch {
	--fvm-section: var(--wp--preset--color--dark-green);
}

.post-type-archive-note,
.single-note {
	--fvm-section: var(--wp--preset--color--dark-blue);
}

.post-type-archive-photo,
.single-photo {
	--fvm-section: var(--wp--preset--color--dark-red);
}

/*
 * Internal header. Height, padding and gaps are block attributes on the group
 * (patterns/internal-header.php). What stays here: the positioning context for
 * the mobile dropdown, and the whole mobile/desktop switch — a media query,
 * which block attributes cannot express.
 */
.fvm-header {
	position: relative;
}

/*
 * The house is an inline SVG (patterns/internal-header.php), sized here since
 * a raw <svg> carries no block attributes for width/height.
 */
.fvm-header__home {
	display: block;
}

.fvm-header__home svg {
	display: block;
	width: 36px;
	height: auto;
}

/*
 * Rest state: roof, walls and ground read as one dark-grey silhouette. Hover
 * and focus split them into roof blue, walls red, ground green. The
 * <g id="walls"> group carries no fill of its own, so its two child paths
 * inherit whatever fill is set on the group — one rule colors both walls.
 * All three colors are the dark-blue/dark-red/dark-green palette presets,
 * which the site's light/dark toggle already redefines to their light-*
 * values (see the [data-theme="dark"] and prefers-color-scheme rules further
 * down), so the hover colors follow the site's theme automatically.
 */
.fvm-header__home svg #roof,
.fvm-header__home svg #walls,
.fvm-header__home svg #ground {
	fill: var(--wp--preset--color--dark-grey);
	transition: fill 0.15s ease;
}

.fvm-header__home:hover svg #roof,
.fvm-header__home:focus-visible svg #roof {
	fill: var(--wp--preset--color--dark-blue);
}

.fvm-header__home:hover svg #walls,
.fvm-header__home:focus-visible svg #walls {
	fill: var(--wp--preset--color--dark-red);
}

.fvm-header__home:hover svg #ground,
.fvm-header__home:focus-visible svg #ground {
	fill: var(--wp--preset--color--dark-green);
}

/*
 * Dark mode drops the three-way blue/red/green split above in favor of one
 * shared hover color for all three parts — same [data-theme="dark"] /
 * prefers-color-scheme pairing as the rest of the toggle. Listed after (and
 * so overriding) the per-part rules above; equal specificity, later in the
 * file wins.
 */
html[data-theme="dark"] .fvm-header__home:hover svg #roof,
html[data-theme="dark"] .fvm-header__home:focus-visible svg #roof,
html[data-theme="dark"] .fvm-header__home:hover svg #walls,
html[data-theme="dark"] .fvm-header__home:focus-visible svg #walls,
html[data-theme="dark"] .fvm-header__home:hover svg #ground,
html[data-theme="dark"] .fvm-header__home:focus-visible svg #ground {
	fill: #e0d16b;
}

@media (prefers-color-scheme: dark) {
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-header__home:hover svg #roof,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-header__home:focus-visible svg #roof,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-header__home:hover svg #walls,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-header__home:focus-visible svg #walls,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-header__home:hover svg #ground,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-header__home:focus-visible svg #ground {
		fill: #e0d16b;
	}
}

@media (prefers-reduced-motion: reduce) {
	.fvm-header__home svg #roof,
	.fvm-header__home svg #walls,
	.fvm-header__home svg #ground {
		transition: none;
	}
}

/* The hamburger: inline SVG too. */
.fvm-header__toggle svg {
	display: block;
	width: 24px;
	height: 24px;
	color: var(--wp--preset--color--dark-grey); /* fills with currentColor */
}

.fvm-header__dot {
	display: none; /* mobile only */
	width: 12px;
	height: 12px;
	border-radius: 50%;
	background: var(--fvm-section, var(--wp--preset--color--dark-grey));
}

.fvm-header__toggle {
	display: none; /* mobile only */
	cursor: pointer;
	color: var(--wp--preset--color--dark-grey);
}

/*
 * .fvm-mobile-menu (the "Menu" template part) is the full-screen mobile nav
 * overlay. It is always in the DOM — inserted into the Header, right after
 * the same checkbox that drives the old desktop nav's mobile visibility — but
 * hidden outside the mobile breakpoint below, same as the dot and hamburger.
 * !important because WordPress's global-styles-inline-css ships
 * `body .is-layout-flex{display:flex}` — a (0,1,1) selector that otherwise
 * beats this (0,1,0) one regardless of source order.
 */
.fvm-mobile-menu {
	display: none !important;
}

/*
 * The block editor has no checkbox to check, so the frontend's hidden-by-
 * default state above made this pattern impossible to select or edit — it
 * never appeared in the canvas, whether editing the Header (nested) or the
 * Menu template part on its own. WordPress auto-scopes a theme's editor
 * styles under `.editor-styles-wrapper`, so this shows it there in its final,
 * visible, in-flow state — the same progressive-enhancement rule scroll
 * animations follow — while the frontend rule above still gates it behind
 * the checkbox.
 */
.editor-styles-wrapper .fvm-mobile-menu {
	display: flex !important;
	position: static !important;
	inset: auto !important;
}

/* Mobile: show the dot + hamburger + the full-screen menu overlay; the old
 * desktop pill row (.fvm-header__nav) is desktop-only from here down. */
@media (max-width: 599px) {
	.fvm-header__dot { display: block; }
	.fvm-header__toggle { display: inline-flex; }
	.fvm-header__nav { display: none !important; }

	.fvm-mobile-menu {
		position: fixed;
		/*
		 * top/left/right pin the edges; height is set explicitly rather than via
		 * `inset: 0` (which would also pin `bottom: 0`). A fixed element's height
		 * from top+bottom is resolved against the initial containing block, which
		 * iOS Safari sizes for the toolbar-hidden state — so when the address bar
		 * is showing, that bottom edge sits behind it and cuts off the last links
		 * (e.g. "About me").
		 *
		 * `100dvh` (dynamic viewport height, tracking the toolbar live) looks
		 * like the fix, but WebKit has a standing bug where a `position: fixed`
		 * element sized with `dvh` does not re-layout when the toolbar
		 * shows/hides after first paint — confirmed on an iPhone 12 Pro, where the
		 * overlay kept the address-bar-hidden height and Safari's own chrome then
		 * overlapped the bottom links. `100svh` (small viewport height, the
		 * guaranteed-smallest size with the toolbar fully expanded) is a static
		 * value computed once, so it never needs that reflow and always fits —
		 * the only cost is unused space below the overlay once the toolbar
		 * auto-hides, which `overflow-y: auto` below already handles safely.
		 * `100vh` first is a fallback for browsers without `svh` support (they
		 * get the classic unit, better than no explicit height at all).
		 */
		top: 0;
		left: 0;
		right: 0;
		height: 100vh;
		height: 100svh;
		z-index: 30;
		overflow-y: auto;
		/*
		 * The block's vertical flex layout defaults to flex-wrap:wrap. With the
		 * 40px item padding, content can exceed the fixed 100svh box, and
		 * wrapping a column layout starts a second *column* instead of just
		 * overflowing — e.g. the language switcher jumping to the top-right.
		 * nowrap + the overflow-y above turns that into a normal scroll instead.
		 */
		flex-wrap: nowrap !important;
	}

	/*
	 * The menu overlay is no longer nested next to the checkbox in the DOM —
	 * it's rendered separately via wp_body_open() (see fvm_render_mobile_menu
	 * in functions.php), specifically so it stays out of every other
	 * template's editable content. :has() reaches across that DOM distance
	 * without needing them to share a parent.
	 */
	body:has(#fvm-menu-toggle:checked) .fvm-mobile-menu { display: flex !important; }

	/* Locked by mobile-menu.js while the overlay is open, so the page behind
	 * it cannot scroll. */
	body.fvm-menu-open { overflow: hidden; }

	/*
	 * The nav is a column here, so the switcher's inline left margin (a block
	 * attribute, correct on desktop) would indent it. Only a media query can
	 * undo it per-viewport, which is why this is CSS and not an attribute.
	 */
	.fvm-lang-switch {
		margin-left: 0 !important;
		margin-top: var(--wp--preset--spacing--xs);
		justify-content: center;
	}

	/*
	 * The four backgrounds: base (Secondary Nav pages — the block's own default
	 * attribute, no override needed) and the three section colors, keyed off
	 * the same body classes style.css already uses for --fvm-section. Text
	 * flips to base for contrast against a colored fill — including the
	 * "current" link, which on every other surface reads in its section color;
	 * here that would mean e.g. dark-green text on a dark-green background, so
	 * this overrides it too. The dot no longer rides currentColor (see the
	 * "Main nav" dot rules further down) so it needs its own background swap
	 * here to stay white against these same colored fills.
	 */
	.post-type-archive-sketch .fvm-mobile-menu,
	.single-sketch .fvm-mobile-menu {
		background-color: var(--wp--preset--color--dark-green) !important;
	}

	.post-type-archive-note .fvm-mobile-menu,
	.single-note .fvm-mobile-menu {
		background-color: var(--wp--preset--color--dark-blue) !important;
	}

	.post-type-archive-photo .fvm-mobile-menu,
	.single-photo .fvm-mobile-menu {
		background-color: var(--wp--preset--color--dark-red) !important;
	}

	/*
	 * No :where() here on purpose — this has to beat the per-section "current
	 * page" rule near the top of this file (.post-type-archive-sketch
	 * .fvm-nav-link.is-sketch, specificity 0,3,0), which would otherwise still
	 * win and paint e.g. "Sketchbook" in dark-green on this same dark-green
	 * background. Matching its specificity and coming later in the file wins
	 * the tie.
	 */
	.post-type-archive-sketch .fvm-mobile-menu .fvm-lang-link,
	.post-type-archive-sketch .fvm-mobile-menu .fvm-theme-toggle,
	.post-type-archive-sketch .fvm-mobile-menu .fvm-overlay-close,
	.post-type-archive-sketch .fvm-mobile-menu .wp-block-button__link,
	.single-sketch .fvm-mobile-menu .fvm-lang-link,
	.single-sketch .fvm-mobile-menu .fvm-theme-toggle,
	.single-sketch .fvm-mobile-menu .fvm-overlay-close,
	.single-sketch .fvm-mobile-menu .wp-block-button__link,
	.post-type-archive-note .fvm-mobile-menu .fvm-lang-link,
	.post-type-archive-note .fvm-mobile-menu .fvm-theme-toggle,
	.post-type-archive-note .fvm-mobile-menu .fvm-overlay-close,
	.post-type-archive-note .fvm-mobile-menu .wp-block-button__link,
	.single-note .fvm-mobile-menu .fvm-lang-link,
	.single-note .fvm-mobile-menu .fvm-theme-toggle,
	.single-note .fvm-mobile-menu .fvm-overlay-close,
	.single-note .fvm-mobile-menu .wp-block-button__link,
	.post-type-archive-photo .fvm-mobile-menu .fvm-lang-link,
	.post-type-archive-photo .fvm-mobile-menu .fvm-theme-toggle,
	.post-type-archive-photo .fvm-mobile-menu .fvm-overlay-close,
	.post-type-archive-photo .fvm-mobile-menu .wp-block-button__link,
	.single-photo .fvm-mobile-menu .fvm-lang-link,
	.single-photo .fvm-mobile-menu .fvm-theme-toggle,
	.single-photo .fvm-mobile-menu .fvm-overlay-close,
	.single-photo .fvm-mobile-menu .wp-block-button__link {
		color: var(--wp--preset--color--white) !important;
	}

	/*
	 * The house icon (.fvm-header__home, shared with the desktop header) reads
	 * its rest/hover coloring from the rules near the top of this file, which
	 * already give it the right look on this overlay's default white
	 * background. Against the three colored fills above, per-part hover colors
	 * would fight the fill instead of reading as a link, so here every part
	 * stays a flat white — at rest and on hover alike, which is why this has
	 * no :hover variant of its own; the !important below already outranks the
	 * non-!important hover rule regardless of state.
	 */
	.post-type-archive-sketch .fvm-mobile-menu .fvm-header__home svg #roof,
	.post-type-archive-sketch .fvm-mobile-menu .fvm-header__home svg #walls,
	.post-type-archive-sketch .fvm-mobile-menu .fvm-header__home svg #ground,
	.single-sketch .fvm-mobile-menu .fvm-header__home svg #roof,
	.single-sketch .fvm-mobile-menu .fvm-header__home svg #walls,
	.single-sketch .fvm-mobile-menu .fvm-header__home svg #ground,
	.post-type-archive-note .fvm-mobile-menu .fvm-header__home svg #roof,
	.post-type-archive-note .fvm-mobile-menu .fvm-header__home svg #walls,
	.post-type-archive-note .fvm-mobile-menu .fvm-header__home svg #ground,
	.single-note .fvm-mobile-menu .fvm-header__home svg #roof,
	.single-note .fvm-mobile-menu .fvm-header__home svg #walls,
	.single-note .fvm-mobile-menu .fvm-header__home svg #ground,
	.post-type-archive-photo .fvm-mobile-menu .fvm-header__home svg #roof,
	.post-type-archive-photo .fvm-mobile-menu .fvm-header__home svg #walls,
	.post-type-archive-photo .fvm-mobile-menu .fvm-header__home svg #ground,
	.single-photo .fvm-mobile-menu .fvm-header__home svg #roof,
	.single-photo .fvm-mobile-menu .fvm-header__home svg #walls,
	.single-photo .fvm-mobile-menu .fvm-header__home svg #ground {
		fill: var(--wp--preset--color--white) !important;
	}

	/*
	 * Annotation: the Main nav (Sketchbook/Notes/Photos) and Secondary nav
	 * (Colophon/Me) labels sit on the section's own accent-colored background
	 * (see the background-color rules above), which is itself one of the
	 * dark-green/dark-blue/dark-red presets — and those presets swap to their
	 * light-* pastel values under dark mode (see the [data-theme="dark"] custom-
	 * property block further down), so the fill behind these labels turns light
	 * in dark mode. The label color must swap opposite it, so it uses the same
	 * "white" preset the indicator dot already does (var(--wp--preset--color--
	 * white), not a literal #ffffff): that preset itself flips dark/light
	 * (white ↔ dark-grey) under the toggle, so the label reads dark-on-light in
	 * dark mode and white-on-saturated in light mode, same as the dot.
	 */
	.post-type-archive-sketch .fvm-mobile-menu .fvm-nav-link,
	.post-type-archive-sketch .fvm-mobile-menu .fvm-secondary-link,
	.single-sketch .fvm-mobile-menu .fvm-nav-link,
	.single-sketch .fvm-mobile-menu .fvm-secondary-link,
	.post-type-archive-note .fvm-mobile-menu .fvm-nav-link,
	.post-type-archive-note .fvm-mobile-menu .fvm-secondary-link,
	.single-note .fvm-mobile-menu .fvm-nav-link,
	.single-note .fvm-mobile-menu .fvm-secondary-link,
	.post-type-archive-photo .fvm-mobile-menu .fvm-nav-link,
	.post-type-archive-photo .fvm-mobile-menu .fvm-secondary-link,
	.single-photo .fvm-mobile-menu .fvm-nav-link,
	.single-photo .fvm-mobile-menu .fvm-secondary-link {
		color: var(--wp--preset--color--white) !important;
	}

	/* Same colored-background swap, for the "Main nav" / "Secondary nav" dots'
	 * own background (not currentColor, so it needs listing separately from
	 * the color rule above). */
	.post-type-archive-sketch .fvm-mobile-menu .fvm-nav-link::after,
	.post-type-archive-sketch .fvm-mobile-menu .fvm-secondary-link::after,
	.single-sketch .fvm-mobile-menu .fvm-nav-link::after,
	.single-sketch .fvm-mobile-menu .fvm-secondary-link::after,
	.post-type-archive-note .fvm-mobile-menu .fvm-nav-link::after,
	.post-type-archive-note .fvm-mobile-menu .fvm-secondary-link::after,
	.single-note .fvm-mobile-menu .fvm-nav-link::after,
	.single-note .fvm-mobile-menu .fvm-secondary-link::after,
	.post-type-archive-photo .fvm-mobile-menu .fvm-nav-link::after,
	.post-type-archive-photo .fvm-mobile-menu .fvm-secondary-link::after,
	.single-photo .fvm-mobile-menu .fvm-nav-link::after,
	.single-photo .fvm-mobile-menu .fvm-secondary-link::after {
		background: var(--wp--preset--color--white) !important;
	}

	/*
	 * Annotation: "when background is colored, make these two elements white"
	 * — the Home / Close buttons. `color` alone (above) would normally carry
	 * the border too since is-style-outline's border-color defaults to
	 * currentColor, but WP's button-block CSS sets it explicitly, so it needs
	 * its own override.
	 */
	.post-type-archive-sketch .fvm-mobile-menu .wp-block-button__link,
	.single-sketch .fvm-mobile-menu .wp-block-button__link,
	.post-type-archive-note .fvm-mobile-menu .wp-block-button__link,
	.single-note .fvm-mobile-menu .wp-block-button__link,
	.post-type-archive-photo .fvm-mobile-menu .wp-block-button__link,
	.single-photo .fvm-mobile-menu .wp-block-button__link {
		border-color: var(--wp--preset--color--white) !important;
	}

	/*
	 * Per the Menu template part's own layout (edited in the block editor):
	 * each nav-link paragraph and nav item carries zero padding/margin of its
	 * own — the group's padding-left is what indents the whole stack. The dot
	 * has to stay absolutely positioned (not a flex item taking up in-flow
	 * space) so it never shifts that indent: it hangs in the gutter to the
	 * left, 12px like the header's mobile dot, 24px away from the label (the
	 * "lg" spacing preset — the closest match to the requested 26px). This way
	 * the resting layout matches the editor canvas exactly whether or not the
	 * dot is visible. Same treatment for the Secondary nav's Colophon/Me
	 * paragraphs (.fvm-secondary-link) as for the Main nav paragraphs, so both
	 * respond the same way.
	 */
	.fvm-mobile-menu .fvm-nav-link,
	.fvm-mobile-menu .fvm-secondary-link {
		position: relative;
	}

	.fvm-mobile-menu .fvm-nav-link::after,
	.fvm-mobile-menu .fvm-secondary-link::after {
		content: "";
		position: absolute;
		top: 50%;
		left: -50px;
		bottom: auto;
		transform: translateY(-50%);
		width: 12px;
		height: 12px;
		border-radius: 50%;
		background: var(--wp--preset--color--dark-grey);
		opacity: 0;
		/* No transition here on purpose — the menu overlay's dot should snap on
		 * with the hover, not fade in. */
	}

	/*
	 * "The dot shows up only when the page is active" — the site-wide rule
	 * (.fvm-nav-link:hover::after near the top of this file) also reveals it on
	 * hover, which is the right behavior for the header pills and the home
	 * footer nav. Inside the menu overlay it is wanted too, for both the Main
	 * nav paragraphs and the Secondary nav's Colophon/Me paragraphs
	 * (.fvm-secondary-link) — hovering (or, via :focus-within, tabbing to)
	 * either previews its dot the same way. Colophon/Me have no per-page class
	 * like .is-sketch and no "currently on this page" state of their own here
	 * (they always link away from wherever this overlay is opened), so hover/
	 * focus is the only trigger they need.
	 */
	.fvm-mobile-menu .fvm-nav-link:hover::after,
	.fvm-mobile-menu .fvm-nav-link:focus-within::after,
	.fvm-mobile-menu .fvm-secondary-link:hover::after,
	.fvm-mobile-menu .fvm-secondary-link:focus-within::after,
	.post-type-archive-sketch .fvm-mobile-menu .fvm-nav-link.is-sketch::after,
	.single-sketch .fvm-mobile-menu .fvm-nav-link.is-sketch::after,
	.post-type-archive-note .fvm-mobile-menu .fvm-nav-link.is-note::after,
	.single-note .fvm-mobile-menu .fvm-nav-link.is-note::after,
	.post-type-archive-photo .fvm-mobile-menu .fvm-nav-link.is-photo::after,
	.single-photo .fvm-mobile-menu .fvm-nav-link.is-photo::after {
		opacity: 1 !important;
	}
}

/*
 * Notes archive rows — a 64px crop box with a paragraph-symbol glyph behind
 * it, so posts with no featured image still show something. core/post-
 * featured-image has no fallback of its own; the size and radius are block
 * attributes. The block renders nothing at all when a post has no thumbnail
 * (no wrapper, no empty figure), so :empty scopes the glyph to that case —
 * an actual thumbnail image fills the box and hides it anyway.
 *
 * The glyph is a CSS mask (paragraph-symbol-mask.svg, the paragraph shape
 * only) painted with the body-text color, so it recolors with the
 * dark-grey preset automatically. The box background is the paragraph
 * vector's other shape, grey-5 at rest; the dark-mode override below swaps
 * it to grey-1 (see the theme switcher block further down).
 */
.fvm-note-thumb {
	position: relative;
	flex: 0 0 64px;
	width: 64px;
	height: 64px;
	border-radius: 4px;
	overflow: hidden;
	background-color: var(--wp--preset--color--grey-5);
}

.fvm-note-thumb:empty::before {
	content: "";
	position: absolute;
	inset: 0;
	background-color: var(--wp--preset--color--dark-grey);
	-webkit-mask: url("assets/img/paragraph-symbol-mask.svg") no-repeat center / 100% 100%;
	mask: url("assets/img/paragraph-symbol-mask.svg") no-repeat center / 100% 100%;
}

.fvm-note-thumb .wp-block-post-featured-image,
.fvm-note-thumb figure {
	margin: 0;
	width: 100%;
	height: 100%;
}

.fvm-note-thumb img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * Photos — the taken-date behaviour that can't be a block attribute. Keep it on
 * one line, and hide it entirely when the post has none (an empty binding still
 * renders an empty paragraph, which would otherwise add a gap).
 */
.fvm-photo-date {
	white-space: nowrap;
}

.fvm-photo-date:empty {
	display: none;
}

/*
 * Entry / page body links (Notes, Sketchbook, Photos entries and prose
 * pages like Colophon and Me) all render their post-content through
 * .fvm-prose (see templates/single-note.html, single-sketch.html,
 * single-photo.html, page.html, secondary-page.html). Scoped to that class so
 * template parts — header, internal-footer, menu — keep their own nav/link
 * treatment untouched.
 */
.fvm-prose a {
	text-underline-offset: 0.17rem;
	text-decoration-thickness: 1.25px;
}

/*
 * Inline mentions of a collection's own name in prose (Colophon, About me) —
 * "sketchbook", "notes", "photos" — link to that archive and pick up its
 * section colour, the same dark-green/dark-blue/dark-red used by the header
 * nav pills (see .fvm-nav-link.is-* above), so the reference reads as the same
 * section rather than a generic link.
 */
.fvm-prose .fvm-section-link.is-sketch {
	color: var(--wp--preset--color--dark-green);
}

.fvm-prose .fvm-section-link.is-note {
	color: var(--wp--preset--color--dark-blue);
}

.fvm-prose .fvm-section-link.is-photo {
	color: var(--wp--preset--color--dark-red);
}

/*
 * About ("me") page — the me-photo-interaction portrait. img[src] is swapped
 * by assets/js/photo-cycle.js on hover; the cursor hints that it's
 * interactive. All frames are the same 700×700 crop, so the swap never
 * shifts layout.
 */
.fvm-photo-cycle img {
	cursor: pointer;
}

/* ---- Colophon ----------------------------------------------------------- */

/* Registration crosshairs pinned to the viewport corners + the CMYK bar. */
.fvm-marks {
	pointer-events: none;
}

.fvm-mark {
	position: fixed;
	z-index: 5;
}

.fvm-mark--tl { top: 88px; left: 24px; }
.fvm-mark--tr { top: 88px; right: 24px; }
.fvm-mark--bl { bottom: 28px; left: 24px; }
.fvm-mark--br { bottom: 28px; right: 24px; }

.fvm-mark--colors {
	left: 50%;
	bottom: 30px;
	transform: translateX(-50%);
}

/* Clear the admin bar for the top marks when logged in. */
.admin-bar .fvm-mark--tl,
.admin-bar .fvm-mark--tr { top: 120px; }

@media (max-width: 599px) {
	.fvm-mark--tl, .fvm-mark--bl { left: 10px; }
	.fvm-mark--tr, .fvm-mark--br { right: 10px; }
	.admin-bar .fvm-mark--tl,
	.admin-bar .fvm-mark--tr { top: 134px; }
}

/* ---- Internal footer ----------------------------------------------------- */

/*
 * The Secondary Nav column (Colophon / Me) is hidden below the mobile
 * breakpoint — the footer's three-column row (isStackedOnMobile:false) stays
 * on one line at every width, so on narrow screens this middle column would
 * otherwise squeeze the language switcher and theme toggle. The three
 * columns carry HTML anchors (site-language, second-nav, site-theme) set in
 * the editor as CSS hooks. With the middle one hidden, the remaining two
 * are widened to 50% each so the language switcher and theme toggle sit
 * flush against the left and right edges instead of bunching together.
 */
@media (max-width: 599px) {
	#second-nav { display: none; }
	/* !important beats the block's own inline flex-basis (33.34%/33.33%). */
	#site-language,
	#site-theme { flex-basis: 50% !important; }
}

/* ---- Theme switcher (light / dark / system) ----------------------------- */

/*
 * Every block on the site reaches its color through a theme.json preset slug
 * (dark-grey, grey-1, dark-green…), never a hardcoded hex — so dark mode is a
 * pure custom-property swap here, not a rewrite of block attributes. Fran can
 * keep picking "dark-grey" or "dark-green" in the editor; only what those
 * names resolve to changes under [data-theme="dark"].
 *
 * Pairing: white ↔ dark-grey (background and ink swap, so text painted with
 * either preset stays readable — this also flips the dark-grey/white button
 * to a light button with dark text; white's dark-mode value is the black
 * preset's own hex, literal rather than var(--wp--preset--color--black), same
 * reasoning as below). dark-grey → grey-4 (the default text/heading ink).
 * grey-1 → grey-2 (the resting secondary color: dates, captions, the
 * language switcher, default links) — one step darker than the body text so
 * links still read as links; hovering moves them to grey-4 — the same value
 * the default text/heading ink resolves to — instead of darkening further (see
 * the hover block below). The three section colors move to their light-*
 * counterparts so they still read against a dark background. Literal hex
 * throughout, not var(--other-var) — chaining these presets to each other
 * would be a circular custom-property reference.
 *
 * data-theme is set by assets/js/theme-toggle.js. "system" leaves the
 * attribute off entirely, so the prefers-color-scheme block below (same
 * values, media-gated) takes over instead.
 *
 * `:not(:has(.editor-styles-wrapper))` keeps the OS-driven "system" branch
 * out of wp-admin: the block editor (post editor canvas and Site Editor,
 * including Template Parts) loads this stylesheet via add_editor_style()
 * into an iframe whose document never gets a data-theme attribute — nothing
 * in wp-admin runs theme-toggle.js or the inline pre-paint script below, both
 * frontend-only — so `html:not([data-theme])` alone matched there too,
 * flipping the editor to dark whenever the OS was in dark mode even though
 * wp-admin's own chrome stayed light. The editor iframe's document is the
 * only place `.editor-styles-wrapper` exists, so excluding it scopes the
 * whole system-dark branch back to the front end, where it belongs; the
 * front end's <html> never contains that class, so this changes nothing
 * there. `html[data-theme="dark"]` above needs no such guard — it only ever
 * gets set by frontend-only JS.
 */
html[data-theme="dark"] {
	color-scheme: dark;
	--wp--preset--color--white: #181818;
	--wp--preset--color--dark-grey: #cfcfcf;
	--wp--preset--color--grey-1: #8f8f8f;
	--wp--preset--color--dark-green: #7ec471;
	--wp--preset--color--dark-blue: #8baff6;
	--wp--preset--color--dark-red: #f78e7f;
}

/* Notes archive empty-thumb box: grey-5 at rest reads too light against a
 * dark page, so dark mode swaps it to the "Dark grey" swatch. Literal hex,
 * not var(--wp--preset--color--dark-grey) — that variable is repurposed
 * above as the dark-mode text-ink color (#cfcfcf), not this box's fill. */
html[data-theme="dark"] .fvm-note-thumb {
	background-color: #1f1f1f;
}

@media (prefers-color-scheme: dark) {
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) {
		color-scheme: dark;
		--wp--preset--color--white: #181818;
		--wp--preset--color--dark-grey: #cfcfcf;
		--wp--preset--color--grey-1: #8f8f8f;
		--wp--preset--color--dark-green: #7ec471;
		--wp--preset--color--dark-blue: #8baff6;
		--wp--preset--color--dark-red: #f78e7f;
	}

	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-note-thumb {
		background-color: #1f1f1f;
	}
}

/*
 * The default link (resting state reads grey-1, which above now means
 * "grey-2") hover, plus the language-switcher / theme-toggle hover, all move
 * to grey-4 — the same value the default text/heading ink resolves to, so a
 * hovered link settles to "body text color" instead of moving further away
 * from it. Zero-specificity :where() in the generated elements.link CSS
 * means a plain compound selector is enough to win; !important matches the
 * resting `.has-grey-1-color` convention above.
 *
 * `.fvm-secondary-link:not(.fvm-mobile-menu *)` carves out Colophon/Me when
 * they're inside the menu overlay: there they behave like the Main nav pills
 * (per-section rules + dot further down) — only the indicator dot reacts to
 * hover/focus, not the text colour. Without the exclusion this rule's
 * !important, plus the extra html[data-theme="dark"] specificity, would beat
 * the overlay's own "white on this background" rule and flash grey-4 over it
 * on hover. The footer's Colophon/Me (outside `.fvm-mobile-menu`) keep the
 * grey-1 → grey-4 hover exactly as before.
 */
html[data-theme="dark"] a:not(.wp-element-button):hover,
html[data-theme="dark"] .fvm-lang-link:hover,
html[data-theme="dark"] .fvm-lang-link:focus-within,
html[data-theme="dark"] .fvm-theme-toggle:hover,
html[data-theme="dark"] .fvm-theme-toggle:focus-within,
html[data-theme="dark"] .fvm-secondary-link:hover:not(.fvm-mobile-menu *),
html[data-theme="dark"] .fvm-secondary-link:focus-within:not(.fvm-mobile-menu *),
html[data-theme="dark"] .fvm-overlay-close:hover,
html[data-theme="dark"] .fvm-overlay-close:focus-visible {
	color: var(--wp--preset--color--grey-4) !important;
}

@media (prefers-color-scheme: dark) {
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) a:not(.wp-element-button):hover,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-lang-link:hover,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-lang-link:focus-within,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-theme-toggle:hover,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-theme-toggle:focus-within,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-secondary-link:hover:not(.fvm-mobile-menu *),
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-secondary-link:focus-within:not(.fvm-mobile-menu *),
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-overlay-close:hover,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-overlay-close:focus-visible {
		color: var(--wp--preset--color--grey-4) !important;
	}
}

/*
 * Nav pills (.fvm-nav-link — Sketchbook/Notes/Photos, header and home alike)
 * already own their :hover colour via the per-section rules near the top of
 * this file: grey-1/dark-grey resting turns into the section colour on
 * internal pages, while on the home page resting already *is* the section
 * colour, so hover re-asserts the same value. The generic link-hover rule
 * just above matches their anchor too (same !important, and on this specific
 * selector pair the same specificity), so without this override it would win
 * on source order and flatten every pill to plain grey on hover — visible on
 * the home page as the green/blue/red Sketchbook/Notes/Photos links dulling
 * to grey instead of keeping their resting colour (only the indicator dot
 * should react to hover there). `color: inherit` hands hover control back to
 * the parent .fvm-nav-link, whose own colour (from the rules above) is
 * correct in both contexts; matching specificity plus coming later in the
 * file is enough to win the tie.
 */
html[data-theme="dark"] .fvm-nav-link a:hover,
html[data-theme="dark"] .fvm-nav-link a:focus-within {
	color: inherit !important;
}

@media (prefers-color-scheme: dark) {
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-nav-link a:hover,
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-nav-link a:focus-within {
		color: inherit !important;
	}
}

/*
 * Soft cross-fade when the toggle actually changes data-theme (system
 * following the OS is instant, since no JS runs on it — there is nothing to
 * transition against). Scoped to the properties dark mode touches, not `all`.
 */
body,
.fvm-nav-link a,
.fvm-lang-link a,
.fvm-theme-toggle a,
.fvm-secondary-link a,
.fvm-overlay-close {
	transition: background-color 0.2s ease, color 0.2s ease;
}

@media (prefers-reduced-motion: reduce) {
	body,
	.fvm-nav-link a,
	.fvm-lang-link a,
	.fvm-theme-toggle a,
	.fvm-secondary-link a,
	.fvm-overlay-close {
		transition: none;
	}
}

/*
 * Home-page house illustration: light/dark are two separate <img>s (see
 * fvm/patterns/home-body.php) rather than one image with a filter, so this
 * just shows one and hides the other — same [data-theme="dark"] /
 * prefers-color-scheme pairing as the color-preset swap above. Resting state
 * (no data-theme, OS light) is the light illustration, set once here instead
 * of repeated in each branch below.
 */
.fvm-house--dark {
	display: none;
}

html[data-theme="dark"] .fvm-house--light {
	display: none;
}

html[data-theme="dark"] .fvm-house--dark {
	display: block;
}

@media (prefers-color-scheme: dark) {
	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-house--light {
		display: none;
	}

	html:not([data-theme]):not(:has(.editor-styles-wrapper)) .fvm-house--dark {
		display: block;
	}
}
