/*
Theme Name: Sophie Lenoir
Theme URI: https://example.com/sophie-lenoir
Author: Sophie Lenoir Studio
Author URI: https://example.com
Description: Single-page editorial WordPress theme for Sophie Lenoir, perfumer and olfactive designer. Built around two long-form sections — Design Olfactif and Culture Sensorielle — with expandable practical-info accordions.
Version: 1.7.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: sophie-lenoir
*/

/* =============================================================
   Table of contents
   1. Reset & base
   2. Design tokens
   3. Typography
   4. Layout helpers
   5. Intro header / nav
   6. Hero
   7. Content sections (shared)
   8. Accordion
   9. Placeholder media
   10. Mobile menu
   11. Responsive
   ============================================================= */

/* 1. Reset & base ------------------------------------------------ */
*,
*::before,
*::after {
	box-sizing: border-box;
}

html {
	scroll-behavior: smooth;
	/* No explicit `background` here, deliberately: the HTML5 canvas-
	   background rule already propagates body's own background-color
	   (below) up to the canvas whenever html doesn't set one of its
	   own — which is exactly what's needed during an iOS rubber-band
	   overscroll (see #bg-layer's negative z-index comment below).
	   Setting one directly on html would take over the canvas from
	   body instead of merely matching it, demoting body's background
	   to an ordinary painted box — one that would then sit in normal
	   paint order *above* #bg-layer's negative z-index, permanently
	   hiding every colour #bg-layer is recoloured to by js/intro.js. */
	overscroll-behavior-y: none;
}

body,
h1,
h2,
h3,
h4,
p,
figure,
ul {
	margin: 0;
}

ul {
	padding: 0;
}

img {
	max-width: 100%;
	display: block;
}

button {
	font: inherit;
	color: inherit;
	background: none;
	border: 0;
	cursor: pointer;
}

a {
	color: inherit;
}

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

/* 2. Design tokens ------------------------------------------------ */
@font-face {
	font-family: 'Die Grotesk A';
	src: url('assets/fonts/die-grotesk-a-bold.woff2') format('woff2');
	font-weight: 700;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: 'TRJN DaVinci';
	src: url('assets/fonts/TRJN-DaVinci-Regular.woff') format('woff');
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}

:root {
	--color-cream: #f3e2c3;
	--color-cream-soft: #efe0c8;
	--color-dark: #3b1f1b;
	--color-rosewood: #93675d;
	--color-espresso: #3b1f1b;
	--color-clay: #a97a52;
	--color-accent: #d17d3b;
	--color-placeholder-light: #d9d9d9;
	--color-placeholder-muted: /*#c0b0ac*/transparent;
	--color-hairline: rgba(239, 224, 200, 0.35);

	--font-display: 'TRJN DaVinci', Georgia, 'Times New Roman', serif;
	--font-body: 'TRJN DaVinci', Georgia, serif;
	--font-sans: 'Die Grotesk A', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;

	--container-max: 1400px;
	--gutter: clamp(24px, 5vw, 80px);
}

body {
	font-family: var(--font-body);
	background: var(--color-cream);
	color: var(--color-dark);
	line-height: 1.3;
	-webkit-font-smoothing: antialiased;
}

/* The page's content column caps at 1100px (1300px above 1800px —
   see the min-width: 1800px block in section 10) and centres — but
   each .section (I–IV) breaks back out to the full 100vw its
   bleed-heading needs to glide across (see js/intro.js and .section
   below), rather than being capped along with everything else. The
   breakout math is self-adjusting: on narrower viewports, where
   #main-content is already narrower than its own cap and thus
   effectively full-width anyway, calc(50% - 50vw) resolves to ~0, so
   this is a no-op exactly where it isn't needed — no extra breakpoint
   required for that half of it. */
#main-content {
	max-width: 1100px;
	margin-inline: auto;
}

#main-content > .section {
	width: 100vw;
	margin-left: calc(50% - 50vw);
}

/* The actual page background: a single fixed layer behind
   everything, recoloured by js/intro.js the moment each section's
   title (marked with [data-bg-var] in index.php) scrolls into view.
   Sections themselves paint no background of their own (see
   section 7) — `body`'s own background above is just the no-JS /
   pre-script fallback, matching this layer's initial colour.
   Depends on `html` staying without a `background` of its own (see
   that rule) — the moment html sets one, body's background stops
   being the canvas and starts painting as an ordinary box instead,
   sitting above this element's negative z-index and hiding every
   colour it's ever recoloured to. */
.bg-layer {
	position: fixed;
	inset: 0;
	z-index: -1;
	background-color: var(--color-cream);
	transition: background-color 0.6s ease;
}

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

/* 3. Typography ---------------------------------------------------- */
.eyebrow {
	font-family: var(--font-sans);
	font-weight: 700;
	font-size: 0.95rem;
	letter-spacing: 0.02em;
}

.section-kicker {
	font-family: var(--font-sans);
	font-weight: 700;
	font-size: clamp(1.1rem, 1.6vw, 1.4rem);
	text-transform: uppercase;
	letter-spacing: 0.02em;
	color: var(--color-cream-soft);
	margin-bottom: 1.2em;
}

/* Scoped to sections in effect, not the intro — the more specific
   .hero__text .lede:first-child / :last-child rules below still
   govern the hero's own paragraphs regardless of this. .lede itself
   is a div, not a p — it wraps free-form ACF text-field output, which
   already emits its own <p> tags; wrapping that in another <p> would
   be invalid (nested paragraphs) and get silently split apart by the
   browser's own parser. */
.lede {
	font-size: clamp(1rem, 1.1vw, 1.15rem);
	max-width: 700px;
}

/* p's own margin is already zeroed by the reset (section 1) — this is
   only for when an ACF field's rich text spans multiple paragraphs. */
.lede p + p {
	margin-top: 1em;
}

.lede strong,
.body-copy strong {
	font-family: var(--font-sans);
	font-weight: 700;
}

/* A div, not a p, when it carries .on-color (see index.php) — those
   instances wrap ACF text-field output, which already emits its own
   <p> tags (same reasoning as .lede above). The plain (à propos)
   instances are still <p> themselves, since that content isn't
   ACF-driven; the class doesn't care either way. */
.body-copy {
	font-size: clamp(0.98rem, 1vw, 1.08rem);
	max-width: 700px;
}

.body-copy + .body-copy {
	margin-top: 1.6em;
}

/* Only relevant to the .on-color / ACF instances (see above) — for
   when a field's rich text spans multiple paragraphs internally. */
.body-copy p + p {
	margin-top: 1.6em;
}

.on-color {
	color: var(--color-cream-soft);
}

/* 4. Layout helpers ------------------------------------------------- */
/* .section (below) stays full-bleed 100vw — that's what gives the
   section title room to glide edge-to-edge — while .container is
   what's actually framed between "Sophie" and "Lenoir". This
   centered max-width + gutter padding is only the no-JS / pre-measure
   fallback: js/intro.js measures the header's wordmark slots and
   replaces both with explicit left/right margins instead, so a
   container's content sits flush against the inside edge of both
   words rather than merely centered between them (the two words
   aren't the same width) with extra padding on top of that. */
.container {
	max-width: var(--container-max);
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.overflow-clip {
	overflow: hidden;
}

.bleed-heading {
	position: relative;
	z-index: 2;
	font-family: var(--font-display);
	font-weight: 400;
	color: var(--color-accent);
	font-size: clamp(4rem, 12vw, 10.5rem);
	line-height: 1;
	white-space: nowrap;
	margin: 0 0 clamp(1.5rem, 4vw, 3rem);
	will-change: transform;
}

.bleed-heading--right {
	text-align: right;
	margin-right: -6vw;
}

.bleed-heading--left {
	text-align: left;
	margin-left: -6vw;
}

/* 5. Intro header / nav -------------------------------------------
   Hidden by default; the intro script reveals it (adds
   `is-scrolled` on <body>) once the hero has scrolled past. */
/* position: fixed, not sticky — it doesn't need scroll-linked
   stickiness at all (body.is-scrolled's opacity/transform toggle
   below is what actually shows and hides it), and sticky's own
   requirement — that it stays part of normal document flow, taking
   up its own space there — was pushing everything after it down by
   its height even while invisible, hero__first-screen's own
   min-height: 100dvh included. Fixed removes it from flow outright. */
.intro-header {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	z-index: 50;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 1.4rem 2vw;
	font-family: var(--font-sans);
	font-weight: 700;
	font-size: 0.9rem;
	letter-spacing: 0.03em;
	color: var(--menu-color, var(--color-dark));
	background: transparent;
	opacity: 0;
	transform: translateY(-100%);
	transition: opacity 0.4s ease, transform 0.4s ease, color 0.3s ease;
	pointer-events: none;
}

body.is-scrolled .intro-header {
	opacity: 1;
	transform: translateY(0);
	pointer-events: auto;
}

/* Invisible placeholders: reserve the exact spot the animated title
   docks into (so the nav stays centred) and give intro.js something
   to measure as the animation's end point. Also the visible
   fallback for no-JS / reduced-motion (see bottom of file). */
.intro-header__mark-slot {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 1.15rem;
	text-transform: uppercase;
	letter-spacing: 0.02em;
	text-decoration: none;
	color: inherit;
	flex: none;
	visibility: hidden;
}

/* The animated title clone: fixed to the viewport, its two words
   move and scale independently (driven by js/intro.js) from the
   hero's full-size wordmark down to these header slots. */
.scroll-title {
	display: contents;
	text-decoration: none;
	color: var(--menu-color, var(--color-dark));
}

.scroll-title__word {
	position: fixed;
	top: 0;
	margin: 0;
	font-family: var(--font-display);
	font-weight: 400;
	text-transform: uppercase;
	letter-spacing: 0.02em;
	line-height: 1;
	white-space: nowrap;
	pointer-events: none;
	z-index: 60;
	opacity: 0;
	will-change: transform;
	transition: color 0.3s ease;
}

/* Scaling pivots from the edge each word is anchored to (left for
   "Sophie", right for "Lenoir") so that edge stays put as the word
   shrinks toward it, matching how each is positioned (see
   js/intro.js, which sets `left` on one and `right` on the other). */
.scroll-title__word--start {
	transform-origin: top left;
}

.scroll-title__word--end {
	transform-origin: top right;
}

/* Only revealed once js/intro.js has measured the hero title and
   positioned the clone on top of it — otherwise it would flash
   unpositioned at top:0 for a frame before script runs. */
.scroll-title.is-ready .scroll-title__word {
	opacity: 1;
}

.scroll-title.is-docked .scroll-title__word {
	pointer-events: auto;
}

.intro-nav ul {
	display: flex;
	gap: clamp(1rem, 2.5vw, 2.5rem);
	list-style: none;
}

.intro-nav a {
	display: inline-flex;
	gap: 0.4em;
	text-decoration: none;
	color: inherit;
	opacity: 0.7;
	transition: opacity 0.2s ease, color 0.3s ease;
}

.intro-nav a:hover,
.intro-nav a:focus-visible {
	opacity: 1;
}

.intro-nav__index {
	color: var(--color-accent);
}

/* Persistent contact corner. Transparent, like .intro-header — its
   text colour follows the same --menu-color custom property js/intro.js
   keeps in sync with whichever section is currently in view. */
.intro-contact {
	position: fixed;
	left: 2vw;
	bottom: clamp(1.25rem, 4vh, 2.25rem);
	z-index: 40;
	max-width: 200px;
	padding: 0.9rem 1.1rem;
	font-family: var(--font-sans);
	font-size: 0.78rem;
	line-height: 1.3;
	color: var(--menu-color, var(--color-dark));
	opacity: 0;
	transform: translateY(12px);
	transition: opacity 0.4s ease, transform 0.4s ease, color 0.3s ease;
	pointer-events: none;
}

body.is-scrolled .intro-contact {
	opacity: 1;
	transform: translateY(0);
	pointer-events: auto;
}

/* No fill of its own (it's linework) — stroke: currentColor instead,
   so it always matches .intro-contact's own colour (the --menu-color
   custom property above), including as that colour changes section
   to section. */
.intro-contact__mark {
	display: block;
	width: 22px;
	height: auto;
	margin-bottom: 0.75em;
	fill: none;
	stroke: currentColor;
	stroke-miterlimit: 10;
	stroke-width: 1.4px;
}

.intro-contact__name {
	font-weight: 700;
	margin-bottom: 0.5em;
}

.intro-contact ul {
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: 0.2em;
}

.intro-contact a {
	text-decoration: underline;
	text-underline-offset: 2px;
}

/* Page-load reveal for the hero. Elements carry a --fade-delay
   custom property inline to stagger the sequence. */
@keyframes fade-up {
	from {
		opacity: 0;
		transform: translateY(16px);
	}

	to {
		opacity: 1;
		transform: translateY(0);
	}
}

.js-fade-in {
	animation: fade-up 0.9s cubic-bezier(0.22, 1, 0.36, 1) both;
	animation-delay: var(--fade-delay, 0s);
}

@media (prefers-reduced-motion: reduce) {
	.js-fade-in {
		animation: none;
	}

	.intro-header,
	.intro-contact {
		transition: opacity 0.4s ease;
		transform: none;
	}

	/* Skip the scroll-scrubbed title takeover: show the plain hero
	   title in place, and the header's own small wordmark links,
	   with nothing continuously resizing as the page scrolls. */
	.scroll-title {
		display: none;
	}

	.intro-header__mark-slot {
		visibility: visible;
	}

	.hero__title {
		opacity: 1;
	}
}

/* 6. Hero ----------------------------------------------------------- */
.hero {
	padding-bottom: clamp(5rem, 12vh, 9rem);
}

/* Everything the very first screen shows — hero__intro, the tagline,
   hero__title — lives in here; hero__gallery-wrap is a sibling after
   it (see index.php), deliberately outside, so the gallery can never
   be part of this box no matter how tall it grows: it only ever
   starts once this one ends, i.e. only after scrolling. min-height,
   not height, so real (long-wrapping) content can still push it
   taller rather than clipping — but hero__print (below) is sized to
   keep that from actually happening across ordinary screen shapes.
   dvh over vh for the usual mobile-Safari-toolbar reason. */
.hero__first-screen {
	box-sizing: border-box;
	min-height: 100dvh;
	display: flex;
	flex-direction: column;
	padding-top: clamp(3rem, 8vh, 6rem);
	/* hero__title's own bottom edge lands exactly here regardless of
	   screen shape — see its margin-top: auto below, which is what
	   actually pins it against this padding rather than merely
	   coming to rest after everything above it. */
	padding-bottom: 2vw;
}

.hero__intro {
	display: grid;
	grid-template-columns: minmax(0, 1fr) max-content;
	gap: clamp(2rem, 5vw, 5rem);
	align-items: start;
}

.hero__text .lede:first-child {
	max-width: 46ch;
}

.hero__text .lede:last-child {
	max-width: 44ch;
	margin-top: 1.6em;
	margin-left: clamp(0px, 8vw, 140px);
}

/* Sized by height, not width — the one dial that keeps hero__intro
   (normally the tallest thing above hero__title) from ever pushing
   .hero__first-screen taller than the viewport, on any screen shape,
   without touching type sizes or spacing elsewhere in the hero. */
.hero__print {
	justify-self: end;
	background: var(--color-cream);
	aspect-ratio: 6 / 9;
	height: clamp(180px, 60vh, 500px);
	width: auto;
	max-width: 100%;
	overflow: hidden;
}

.hero__print img,
.hero__print .placeholder-img {
	height: 100%;
	object-fit: cover;
}

.hero__tagline {
	font-family: var(--font-sans);
	font-weight: 700;
	font-size: clamp(1.1rem, 1.7vw, 1.5rem);
	/* Negative at ordinary viewport heights (-5vh lands inside the
	   clamp's range up to ~1760px tall), so — unlike the transform
	   this replaces — it actually pulls hero__title etc. up with it
	   through normal layout, not just a visual shift leaving a gap
	   behind. */
	margin-top: clamp(-5.5rem, -5vh, 4.5rem);
}

/* Kept in normal flow so it reserves the hero's layout space and
   stays announced to screen readers, but made invisible: the
   visible wordmark is the .scroll-title clone that intro.js
   animates from here up into the header (see js/intro.js). */
.hero__title {
	display: flex;
	justify-content: space-between;
	font-family: var(--font-display);
	font-weight: 400;
	font-size: clamp(3.5rem, 11vw, 9.5rem);
	line-height: 1;
	text-transform: uppercase;
	letter-spacing: 0.02em;
	/* Pinned to the bottom of .hero__first-screen's flex box (its
	   padding-bottom is what actually sets the 2vw gap) — absorbs
	   whatever space hero__intro + the tagline don't use, rather than
	   just following them in normal flow. */
	margin-top: auto;
	color: var(--color-dark);
	opacity: 0;
	pointer-events: none;
}

/* Wraps .hero__gallery (rather than putting margin-top and position
   directly on it) so .hero__gallery-caption's inset:0 has a box to
   centre against that exactly matches the grid's own visible bounds,
   with nothing else contending for that same box. */
.hero__gallery-wrap {
	position: relative;
	margin-top: clamp(4rem, 10vh, 8rem);
}

.hero__gallery {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: clamp(6px, 1vw, 14px);
}

.hero__gallery .placeholder-img {
	aspect-ratio: 3 / 4.1;
	/* Above .scroll-title__word (z-index: 60, see section 5) on
	   purpose — the gallery is meant to scroll over the title as they
	   cross paths, not disappear behind it. Scoped to just the hero
	   gallery: every other .placeholder-img in the page keeps the
	   default negative z-index (see section 9), staying under its own
	   section's text. */
	z-index: 61;
}

.hero__gallery-caption {
	position: absolute;
	inset: 0;
	z-index: 62;
	display: flex;
	align-items: center;
	justify-content: center;
	margin: 0;
	font-family: var(--font-sans);
	font-weight: 700;
	font-size: clamp(1.1rem, 1.7vw, 1.5rem);
	/* Same custom property driving .intro-header / .scroll-title's own
	   colour (see js/intro.js's updateBackground) — recolours in step
	   with them as the section behind it changes, not a fixed shade. */
	color: var(--menu-color, var(--color-dark));
	transition: color 0.3s ease;
	text-align: center;
	pointer-events: none;
}

.hero-sentinel {
	height: 1px;
}

/* 7. Content sections ------------------------------------------------
   Sections carry no background colour of their own: #bg-layer (a
   fixed full-viewport layer, see section 5b) is what's actually
   painted, and js/intro.js recolours it the moment each section's
   title (the [data-bg-var] elements below) scrolls into view. A
   section only sets the text `color` it needs for contrast against
   whatever that background will be. */
.section {
	position: relative;
	padding-top: clamp(5rem, 12vh, 9rem);
	/* Matches .subsection's own 20vh padding-top, so the gap after the
	   last article and before the section ends reads the same as the
	   gaps between articles. */
	padding-bottom: 20vh;
	overflow-x: hidden;
}

/* Extra breathing room above just the first section — on top of
   .section's own padding-top, not replacing it. */
.section--design {
	margin-top: 10vh;
}

.section--propos {
	color: var(--color-dark);
}

.section--propos .section-kicker {
	color: var(--color-dark);
}

/* min-height mirrors the placeholder image's own clamp()'d height
   (width clamp(160px, 22vw, 340px) at its 3:4 aspect-ratio, i.e.
   *4/3) so this row's box always fully contains the absolutely
   positioned image below — otherwise, whenever the bleed-heading text
   is shorter than the image (the usual case), the image would
   overflow past margin-bottom and sit on top of the following
   article's text instead of stopping here. */
.section-heading-row {
	position: relative;
	margin-bottom: clamp(2rem, 6vh, 4rem);
	min-height: clamp(214px, 29.4vw, 454px);
}

.section-heading-row .placeholder-img {
	position: absolute;
	top: 0;
	right: var(--gutter);
	width: clamp(160px, 22vw, 340px);
	aspect-ratio: 3 / 4;
}

.section-heading-row--left .placeholder-img {
	right: var(--gutter);
	left: auto;
}

.subsection {
	/* Governs the gap both above the first article (against the
	   section heading row) and between consecutive articles. */
	padding-top: 20vh;
	max-width: 900px;
	margin: 0 auto;
}

/* Stacked, not side-by-side — a .thumb-row (4 images across, needs
   real width to read at all) would only get a sliver of a 700px-
   capped grid column (see .subsection__grid below) if placed beside
   the body text. .subsection--thumbs marks the subsections built
   around a thumb-row (see index.php); it stays within the normal
   900px .subsection cap (unlike every other article here, it never
   bleeds past it) so the row can only ever wrap further, never
   overflow past the section's own centred, gutter-respecting bounds.
   grid-template-columns: 1fr makes both children stack instead of
   sitting in a two-column grid, and order is what actually keeps the
   text on top with the images below, since .thumb-row comes first in
   the markup. */
.subsection--thumbs .subsection__grid {
	grid-template-columns: 1fr;
}

.subsection--thumbs .thumb-row {
	order: 2;
}

.subsection__grid {
	display: grid;
	/* The body column can grow up to 700px, same cap as .lede (see
	   section 3), so the two read as the same width instead of the
	   body column being capped at whatever an even 1fr/1fr split with
	   the image happens to leave it. */
	grid-template-columns: minmax(0, 1fr) minmax(0, 700px);
	gap: clamp(1.5rem, 4vw, 3.5rem);
	align-items: start;
	margin-top: clamp(2rem, 5vh, 3rem);
}

.subsection__grid--reverse .placeholder-img {
	order: 2;
}

.media-stack {
	display: grid;
	gap: clamp(1.5rem, 4vh, 3rem);
}

.media-stack .placeholder-img {
	aspect-ratio: 3 / 4;
}

/* flex, not a 4-column grid: the images are sized by height below, so
   there's no shared column width for a grid to divide up — flex just
   lays them out left-to-right at their own natural (height-driven)
   width, left-aligned by default rather than stretched to fill the
   row if their combined width falls short of it. */
.thumb-row {
	display: flex;
	flex-wrap: wrap;
	gap: clamp(6px, 1vw, 12px);
}

.thumb-row .placeholder-img {
	aspect-ratio: 3 / 4.2;
	height: 25vh;
	width: auto;
	flex: none;
}

.subsection__body {
	max-width: 700px;
}

.subsection__closing {
	margin-top: clamp(2rem, 5vh, 3rem);
}

.full-media {
	margin-top: clamp(2rem, 5vh, 3rem);
	aspect-ratio: 16 / 10;
	max-width: 640px;
}

/* 8. Accordion --------------------------------------------------- */
.accordion {
	margin-top: clamp(2.5rem, 6vh, 4rem);
	max-width: 640px;
}

.accordion-item {
	border-top: 1px solid var(--color-hairline);
}

.accordion-item:last-child {
	border-bottom: 1px solid var(--color-hairline);
}

.accordion-trigger {
	width: 100%;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: 0.9rem 0;
	font-family: var(--font-sans);
	font-weight: 700;
	font-size: 0.95rem;
	color: var(--color-cream-soft);
	text-align: left;
}

.accordion-icon {
	flex: none;
	width: 1.1rem;
	height: 1.1rem;
	border: 1.5px solid currentColor;
	border-radius: 50%;
	position: relative;
}

.accordion-icon::before,
.accordion-icon::after {
	content: '';
	position: absolute;
	background: currentColor;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
}

.accordion-icon::before {
	width: 55%;
	height: 1.5px;
}

.accordion-icon::after {
	width: 1.5px;
	height: 55%;
	transition: opacity 0.2s ease;
}

.accordion-item.is-open .accordion-icon::after {
	opacity: 0;
}

.accordion-panel {
	overflow: hidden;
	max-height: 0;
	transition: max-height 0.35s ease;
}

.accordion-item.is-open .accordion-panel {
	max-height: var(--panel-height, 600px);
}

.accordion-panel__inner {
	padding-bottom: 1.4rem;
	font-size: 0.95rem;
	color: var(--color-cream-soft);
}

.accordion-panel ul {
	list-style: none;
}

.accordion-panel li {
	position: relative;
	padding-left: 1.1em;
	margin-bottom: 0.5em;
}

.accordion-panel li::before {
	content: '\2022';
	position: absolute;
	left: 0;
	color: var(--color-accent);
}

.accordion-panel p + p {
	margin-top: 0.9em;
}

.accordion-panel .price-line {
	font-family: var(--font-sans);
	font-weight: 700;
}

.accordion-panel a {
	text-decoration: none;
}

/* 9. Placeholder media -------------------------------------------- */
/* z-index: -1, not 0 — `will-change: transform` here already forces
   .placeholder-img into its own stacking context, and a *positive or
   zero* z-index on a stacking-context-forming element always paints
   above plain in-flow text (.lede, .body-copy, …) in the same parent
   context, regardless of document order — z-index: 0 only *looked*
   safe here by coincidence of past layouts never quite overlapping.
   A negative z-index is what actually paints behind normal flow
   content, so a future sizing mistake (an oversized photo, a text
   reflow) covers nothing instead of silently hiding it. Overridden
   deliberately, and only, on .hero__gallery's own images below. */
.placeholder-img {
	position: relative;
	z-index: -1;
	background: var(--color-placeholder-light);
	width: 100%;
	will-change: transform;
	overflow: hidden;
}

.placeholder-img img {
	object-fit: cover;
	height: 100%;
	width: 100%;
}

.on-color-bg .placeholder-img,
.section--design .placeholder-img,
.section--culture .placeholder-img,
.section--therapie .placeholder-img {
	background: var(--color-placeholder-muted);
}

.placeholder-img--photo {
	background-size: cover;
	background-position: center;
}

/* 10. Mobile menu ---------------------------------------------------
   Hidden entirely above the 640px breakpoint (section 11 below), where
   .intro-nav / #intro-contact already cover the same job. On mobile,
   those two are hidden instead (see section 11) in favour of this fixed
   toggle + full-screen overlay pair. */
.mobile-menu-toggle {
	display: none;
}

.mobile-menu {
	display: none;
}

/* 11. Responsive ------------------------------------------------------ */
@media (min-width: 1800px) {
	#main-content {
		max-width: 1300px;
	}

	.subsection {
		max-width: 1100px;
	}
}

@media (max-width: 1024px) {
	.hero__intro {
		grid-template-columns: 1fr;
	}

	.hero__print {
		justify-self: start;
		/* Reverts to the width-driven sizing this had before the
		   height-based clamp (see the base rule) — single-column here,
		   so hero__intro's total height is text-height + image-height
		   stacked, not max(text, image); the 100dvh/margin-top:auto
		   pairing on .hero__first-screen still holds, just with a
		   taller hero__intro to absorb before hero__title gets pushed
		   past the first screen on very short + narrow devices. */
		height: auto;
		width: min(100%, 380px);
	}

	.hero__text .lede:last-child {
		margin-left: 0;
	}

	.subsection__grid {
		grid-template-columns: 1fr;
	}

	.subsection__grid--reverse .placeholder-img {
		order: 0;
	}

	.section-heading-row {
		min-height: 0;
	}

	.section-heading-row .placeholder-img {
		position: static;
		width: min(100%, 340px);
		margin-bottom: 2rem;
	}

	.bleed-heading--right,
	.bleed-heading--left {
		margin-inline: 0;
		text-align: left;
	}
}

@media (max-width: 640px) {
	.hero__first-screen {
		/* dvh, not vh: vh is pinned to the "largest" viewport (toolbar
		   hidden), so on a page load with the address bar showing, this
		   padding — and every section's own further down — was computed
		   a few percent taller than the space actually available,
		   throwing off where each section's boundary actually falls
		   on screen. dvh tracks the real, current viewport instead. */
		padding-top: clamp(1.5rem, 4dvh, 3rem);
	}

	.hero__intro {
		gap: 1.5rem;
	}

	.hero__print {
		height: auto;
		width: min(52vw, 220px);
	}

	.hero__tagline {
		margin-top: clamp(1.5rem, 4vh, 2.5rem);
	}

	.hero__gallery {
		grid-template-columns: repeat(2, 1fr);
	}

	.hero__title {
		flex-direction: column;
		gap: 0.1em;
		margin-top: clamp(0.75rem, 2.5vh, 1.5rem);
		font-size: clamp(3rem, 16vw, 6rem);
	}

	.section {
		/* Same dvh reasoning as .hero above. */
		padding-block: clamp(5rem, 12dvh, 9rem);
	}

	.intro-header {
		/* Clears the notch / Dynamic Island: this bar is sticky at
		   top:0, so without this its "Sophie" / "Lenoir" links sit
		   right under it once scrolled. */
		padding: calc(1rem + env(safe-area-inset-top, 0px)) 2vw 1rem;
	}

	/* Both replaced on mobile by the fixed toggle + full-screen overlay
	   below — .intro-nav's own links and #intro-contact's own details
	   are duplicated inside #mobile-menu (see header.php). */
	.intro-nav,
	.intro-contact {
		display: none;
	}

	/* --- Mobile menu toggle --------------------------------------
	   Stays hidden until the "Sophie" / "Lenoir" title has docked at
	   the top (body.is-scrolled — the same trigger .intro-header and
	   #intro-contact already use on desktop), rather than sitting over
	   the hero from the first frame. */
	.mobile-menu-toggle {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		position: fixed;
		left: 50%;
		bottom: max(1.25rem, calc(env(safe-area-inset-bottom, 0px) + 0.75rem));
		transform: translateX(-50%) translateY(12px);
		z-index: 70;
		padding: 0.85em 2.1em;
		border-radius: 999px;
		/* Translucent + blurred, not solid: this button is permanently
		   on screen once scrolled, sitting over whatever content
		   happens to land underneath it (a price line, a paragraph's
		   last words…) — a solid fill would blank that out completely.
		   Frosting it instead keeps the button just as legible while
		   letting the page still read as "there", not erased. */
		background: rgba(59, 31, 27, 0.82);
		backdrop-filter: blur(10px);
		-webkit-backdrop-filter: blur(10px);
		color: var(--color-cream-soft);
		font-family: var(--font-sans);
		font-weight: 700;
		font-size: 0.9rem;
		letter-spacing: 0.02em;
		box-shadow: 0 10px 26px -12px rgba(59, 31, 27, 0.5);
		opacity: 0;
		pointer-events: none;
		transition: opacity 0.4s ease, transform 0.4s ease, background-color 0.3s ease, color 0.3s ease;
	}

	body.is-scrolled .mobile-menu-toggle {
		opacity: 1;
		transform: translateX(-50%) translateY(0);
		pointer-events: auto;
	}

	.mobile-menu-toggle:focus-visible {
		outline: 2px solid var(--color-cream-soft);
		outline-offset: 3px;
	}

	body.mobile-menu-open .mobile-menu-toggle {
		background: rgba(239, 224, 200, 0.9);
		color: var(--color-dark);
	}

	/* --- Mobile menu overlay --------------------------------------
	   Always rendered (`display: flex`, not toggled) so opacity /
	   visibility can transition; `visibility` is what actually keeps it
	   out of the way (non-interactive, skipped by AT) while closed, with
	   its own transition delayed so it only flips to hidden once the
	   fade-out has finished. */
	.mobile-menu {
		display: flex;
		flex-direction: column;
		position: fixed;
		inset: 0;
		z-index: 65;
		background: var(--color-espresso);
		color: var(--color-cream-soft);
		opacity: 0;
		visibility: hidden;
		pointer-events: none;
		overflow-y: auto;
		-webkit-overflow-scrolling: touch;
		padding: calc(env(safe-area-inset-top, 0px) + 5.5rem) var(--gutter) calc(env(safe-area-inset-bottom, 0px) + 6.5rem);
		transition: opacity 0.4s ease, visibility 0s linear 0.4s;
	}

	body.mobile-menu-open .mobile-menu {
		opacity: 1;
		visibility: visible;
		pointer-events: auto;
		transition: opacity 0.4s ease, visibility 0s linear 0s;
	}

	.mobile-menu__nav {
		flex: 1;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.mobile-menu__nav ul {
		list-style: none;
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: clamp(1.1rem, 4dvh, 1.75rem);
		text-align: center;
	}

	.mobile-menu__link {
		display: inline-flex;
		align-items: baseline;
		gap: 0.5em;
		text-decoration: none;
		color: inherit;
		font-family: var(--font-sans);
		font-weight: 700;
		/* Matches .body-copy's own size (see section 3) rather than the
		   much larger display-style scale this used before. */
		font-size: clamp(0.98rem, 1vw, 1.08rem);
		text-transform: uppercase;
		letter-spacing: 0.01em;
	}

	.mobile-menu__index {
		color: var(--color-accent);
	}

	.mobile-menu__footer {
		text-align: center;
		font-family: var(--font-sans);
		font-size: 0.85rem;
	}

	.mobile-menu__footer-name {
		font-weight: 700;
		margin-bottom: 0.75em;
	}

	.mobile-menu__footer ul {
		list-style: none;
		display: flex;
		flex-direction: column;
		gap: 0.35em;
	}

	.mobile-menu__footer a {
		text-decoration: underline;
		text-underline-offset: 2px;
	}

	/* --- Site title stays visible over the open overlay --------------
	   .intro-header is already the (otherwise invisible) sticky bar
	   carrying the real "Sophie" / "Lenoir" links (see .intro-header__
	   mark-slot in section 5) — reused here instead of fighting with
	   .scroll-title's scroll-scrubbed clone, which has no fixed "top of
	   screen" position to snap to if the menu is opened before the user
	   has scrolled past the hero. --menu-color is overridden on <body>
	   so both this bar and (if still visible underneath) the scroll
	   clone switch to the beige used everywhere else on dark backgrounds. */
	body.mobile-menu-open {
		--menu-color: var(--color-cream-soft);
		/* js/mobile-menu.js sets `top` to the negative of the scroll
		   position it was opened at — only meaningful once `position`
		   itself is fixed, which is what actually stops the page
		   scrolling behind the overlay on iOS (see lockScroll there). */
		position: fixed;
		left: 0;
		right: 0;
		width: 100%;
	}

	body.mobile-menu-open .intro-header {
		/* position/top/left/right: fixed already covers this (see
		   section 5) — only opacity/transform/z-index need overriding
		   here. */
		opacity: 1;
		transform: none;
		pointer-events: none;
		z-index: 68;
	}

	body.mobile-menu-open .intro-header__mark-slot {
		visibility: visible;
		pointer-events: auto;
	}

	/* #scroll-title itself is `display: contents` (see section 5) — it
	   has no box of its own to hide, so this targets the two spans
	   that actually render and are positioned. */
	body.mobile-menu-open .scroll-title__word {
		opacity: 0;
		pointer-events: none;
	}
}

@media (max-width: 640px) and (prefers-reduced-motion: reduce) {
	.mobile-menu-toggle,
	.mobile-menu {
		transition: none;
	}
}
