/**
 * Homepage-only styles — enqueued only on is_front_page() (see
 * inc/enqueue.php), so no other page pays for any of this CSS.
 *
 * 0.2.0-dev.1: .cristaline-section / .cristaline-section--alt /
 * .cristaline-section__heading / .cristaline-hero* / .cristaline-categories__* /
 * .cristaline-products-grid / .cristaline-brands__* moved here from
 * layout.css unchanged (still used by nothing outside front-page.php's own
 * template parts) — everything else in this file is new this round
 * (editorial sections, shops, services, the shared "voir tout" link style,
 * focus-visible states, prefers-reduced-motion).
 */

/* Section rhythm helpers, used by every homepage template part.
   0.2.0-dev.5: the default (var(--space-16), 64px) already sits inside the
   client's own "56 à 80px sur les écrans intermédiaires" range — kept
   as-is for that tier. A new >=1280px tier below bumps to the client's
   separate "80 à 110px sur grand écran" range; the existing <=480px tier
   (var(--space-8), 32px) is unchanged, mobile was never part of this
   ask. */
/* 0.2.0-dev.8: real structural cause of "l'alternance des fonds est trop
   discrète", found live before writing anything — ".cristaline-section" is
   a direct child of Storefront's own real site-wide "#content .col-full"
   (the ancestor already documented in this file's own 0.2.0-dev.7 comment
   below), which caps EVERY section — not just the hero — to exactly
   var(--container-max) (1280px), centered. That's correct for the hero
   (deliberately framed) but means every OTHER section's own background
   colour (".cristaline-section--alt", #EDF1F2) never actually reaches the
   viewport edges either — confirmed live: at 1920px, a plain #F8F7F4 body-
   coloured gutter of 312.5px sat on EACH side of the alt background box,
   making the "alternation" read as a small floating rectangle rather than
   a true full-width band. The standard "break out of a centered ancestor"
   technique (negative margins sized to the viewport, independent of the
   ancestor's own width) makes the SECTION's own box span the full
   viewport while leaving every section's own inner ".col-full"/content
   wrapper exactly as centered/boxed as before (~1200-1280px, unchanged —
   confirmed live no inner content shifted). Hero is unaffected: it uses
   its own ".cristaline-hero" class, never ".cristaline-section". */
.cristaline-section {
	margin-left: calc(50% - 50vw);
	margin-right: calc(50% - 50vw);
	padding: var(--space-16) 0;
}

/* 0.2.0-dev.8: 96px -> 84px (12.5% reduction, inside the client's own
   "10 à 15%" ask) — this shared rule governs the gap after the Femme/
   Homme/Enfants cards (.cristaline-univers's own bottom padding), both
   sides of "Nos boutiques" (no dedicated override there), and the top of
   "Nos services" — reducing it once here covers 3 of this round's 4 named
   gaps without touching each section's own template part. */
@media (min-width: 1280px) {
	.cristaline-section {
		padding: 84px 0;
	}
}

/* 0.2.0-dev.5: real bug fixed — "Votre sélection" (process-steps.php) and
   "Nos boutiques" (shops.php) were BOTH --alt and directly adjacent (no
   other section renders between them today: Nouveautés has 0 published
   products, Marques has 0 brand terms, so both sit invisible between "Nos
   univers" and "Votre sélection"), which merged the two into one visual
   block — the client's own "ne pas enfermer Votre sélection et Nos
   boutiques dans un seul grand rectangle commun" complaint. Deliberate
   alternation, chosen to hold even once Nouveautés/Marques eventually DO
   render (never assumed permanently invisible): Nos univers (plain,
   #F8F7F4) -> Nouveautés (--alt) -> Marques (plain) -> Votre sélection
   (--alt) -> Nos boutiques (plain) -> Nos services (--alt) -> footer
   (dark) — a clean A/B alternation either with or without the 2 currently-
   invisible sections in between. Do not add --alt to two sections that can
   ever sit directly adjacent in front-page.php's own render order. */
/* 0.2.0-dev.6: a hairline top border added — the client's own "renforcer
   légèrement l'alternance des fonds... transitions sobres, sans bordures
   épaisses" ask. #F8F7F4 and #EDF1F2 are close enough that the seam
   between them can read as barely-there on some monitors; a 1px line at
   low opacity makes the transition legible without touching either
   background value (both are fixed brand tokens, not to be adjusted).
   0.2.0-dev.8: rgba(23,23,23,.06) -> rgba(168,176,179,.20) — this round's
   own explicit literal (--color-neutral, #A8B0B3, at 20%), a cooler and
   slightly more visible hairline than the previous warm-ink one, still
   well short of a "gros cadre"/heavy border. */
.cristaline-section--alt {
	background-color: var(--color-bg-alt);
	border-top: 1px solid rgba(168, 176, 179, .20);
}

.cristaline-section__heading {
	text-align: center;
	margin-bottom: var(--space-8);
}

/* Shared "view all" link — Nouveautés, Boutiques Cristaline. Same weight/
   underline pattern as the mega-menu's own "Voir tout X" (navigation.css's
   .cristaline-mega-menu__view-all), reused here for visual consistency
   across the whole site rather than inventing a 2nd link style.
   0.2.0-dev.3: this round explicitly calls out "Voir toutes nos boutiques"
   as needing the same elegant treatment as the collection links — this
   class is the one it actually uses (editorial-section.php's own
   .cristaline-editorial__cta is a different class), so the same mobile
   size/weight/underline fixes applied there are mirrored here: .9rem
   (14.4px) -> 1.2rem (19.2px) on mobile, weight 600 -> 500, an explicit
   thin text-decoration-thickness, :visited pinned to --color-ink. */
/* 0.2.0-dev.31: "Voir toutes les nouveautés" converted from a plain
   underlined text link (.cristaline-section__all, now unused — removed
   rather than left as dead CSS) to a real .button.secondary, matching
   "Découvrir toutes les marques" exactly (.cristaline-brands__cta below) —
   the client's own explicit "bouton secondaire centré, cohérent avec le
   bouton Découvrir toutes les marques" ask. Same minimal centering rule,
   nothing else needed since .button.secondary already supplies the real
   visual styling. */
.cristaline-section__all-btn {
	display: table;
	margin: var(--space-8) auto 0;
}

@media (max-width: 480px) {
	.cristaline-section {
		padding: var(--space-8) 0;
	}
}

/* =========================================================
   Hero — 0.2.0-dev.4: full editorial rewrite. The old centered card with an
   opaque rgba(248,247,244,.88) background is GONE — the client's own
   explicit "pas de grand encart blanc opaque qui cache les personnes" ask.
   Text now sits directly on the photo, left-aligned, behind a thin gradient
   scrim (never a solid box) confined to the zone the real bundled photo
   actually leaves clear — see hero.php's own docblock for why left/desktop
   vs bottom/mobile were chosen (checked the real images first, not
   assumed).
   ========================================================= */
.cristaline-hero {
	background-color: var(--color-ink);
	background-position: center 25%;
	background-size: cover;
	display: flex;
	align-items: center;
	min-height: 420px;
	position: relative;
}

/* 0.2.0-dev.7: verified LIVE (getBoundingClientRect, not assumed from
   reading the CSS alone) before writing anything here — Storefront's own
   real "#content .col-full" (its site-wide content wrapper, from
   header.php's storefront_content_top(), NOT anything in this theme) was
   ALREADY the ancestor of every ".cristaline-section" including the hero,
   already capping ALL of them — hero's photo included — to exactly
   var(--container-max) (1280px), centered, on every viewport tested
   (1280/1920px): confirmed the hero's own rendered width was byte-for-byte
   identical to every sibling section's (nos-univers/process/shops/
   services) content box, not the "full-bleed, no width constraint" state
   assumed before re-测ing live. A first draft of this fix added a SECOND,
   redundant max-width+width:calc() constraint directly on ".cristaline-hero"
   — since it would have nested INSIDE that already-1280px box, it would
   have silently SHRUNK the hero below its siblings' width instead of
   widening it, the opposite of this section's own ask. No width rule is
   needed at all: the client's own numeric ceiling ("~1200-1280px") and
   "harmoniser sa largeur avec les autres sections" are both already true.
   What was genuinely missing was the visual "encadré" quality itself — the
   photo fills its 1280px box edge-to-edge with no framing treatment of its
   own, unlike a card; a subtle rounded corner + shadow (the same
   --radius-md other homepage cards already use) gives it a real framed
   read without touching the (already-correct) width. */
.cristaline-hero {
	border-radius: var(--radius-md);
	box-shadow: 0 8px 28px rgba(23, 23, 23, .16);
	overflow: hidden;
}

/* 0.2.0-dev.5: client's own explicit "réduire l'espace entre la fin du
   header et le hero à environ 28 à 40 px" — the sticky header itself never
   overlaps the hero (it's sticky, not fixed, so it simply occupies its own
   space at scroll position 0), but there was no deliberate gap between the
   two, either. 32px sits mid-range. Desktop only — mobile's own spacing
   is unchanged. */
@media (min-width: 768px) {
	.cristaline-hero {
		margin-top: var(--space-8);
	}
}

/* 0.2.0-dev.1: real, client-authorized bundled photos (see
   02_Ancien-site-Hostinger/Images-exportees/README.md, decision D1) used as
   the default background only while no Customizer image has been uploaded —
   hero.php only adds this class in that case, and a Customizer image is set
   via an inline `style` attribute on the same element, which always wins
   over this class regardless of source order. Two distinct crops (never one
   image reused): a portrait mobile crop below 768px, the wide desktop crop
   from 768px up — matches how the reference site itself served two
   different files, not a single recrop. */
.cristaline-hero--default-bg {
	background-image: url('../images/hero-accueil-mobile.jpg');
}

@media (min-width: 768px) {
	.cristaline-hero--default-bg {
		background-image: url('../images/hero-accueil-desktop.jpg');
	}
}

/* Mobile crop: the family fills the upper ~2/3 of this particular photo
   (it's a pre-composed rounded card + blurred backdrop, checked live) — a
   bottom-anchored scrim sits in the plain blurred zone below them, never
   over a face. Desktop crop: the family stands right-of-center, leaving the
   left third genuinely clear (lake/mountains) — a left-anchored scrim runs
   there instead. Both are gradients fading to fully transparent, never a
   flat tint over the whole photo. */
.cristaline-hero__scrim {
	background: linear-gradient(to top, rgba(23, 23, 23, .78) 0%, rgba(23, 23, 23, .45) 35%, transparent 65%);
	bottom: 0;
	left: 0;
	position: absolute;
	right: 0;
	top: 0;
}

@media (min-width: 768px) {
	.cristaline-hero__scrim {
		background: linear-gradient(to right, rgba(23, 23, 23, .62) 0%, rgba(23, 23, 23, .3) 42%, transparent 62%);
	}
}

/* 0.2.0-dev.5: max-width 560px -> 460px — client's own explicit "zone
   d'environ 430 à 480 px de largeur" (was sized more generously last
   round; this narrows it back to a tighter, more editorial column).

   0.2.0-dev.7: real bug, confirmed live and root-causing this round's
   "NOUVELLE SAISON presque invisible" report — this element also carries
   Storefront's real ".col-full" class (for the OUTER content wrapper's own
   max-width, same as every other section), whose real compiled CSS sets
   "margin: 0 auto". On every OTHER section that's harmless (plain block
   centering, exactly what's wanted there); here it collided with this
   element being a FLEX ITEM (".cristaline-hero" is "display:flex"), where
   "margin:auto" becomes a flexbox auto-margin and silently CENTERS the
   item on the main axis instead of leaving it at the flex-start (left)
   position this component was actually designed for — confirmed live via
   getComputedStyle: margin-left/-right were both 378px, landing the whole
   text column (kicker/heading/paragraph) dead center on the photo, exactly
   where the LEFT-anchored scrim (see .cristaline-hero__scrim above) has
   already faded to near-transparent. Measured contrast for the kicker
   against the real photo pixels there: ~1.05:1 (WCAG needs ~4.5:1) — this,
   not the kicker's own color/size, was the real cause. margin:0 restores
   the intended flex-start (left) position. */
.cristaline-hero__inner {
	margin: 0;
	max-width: 460px;
	padding: var(--space-16) var(--space-6) var(--space-8);
	position: relative;
	text-align: left;
	z-index: 1;
}

/* 0.2.0-dev.7: real contrast problem, confirmed by sampling the actual
   photo pixels behind this text (canvas + WCAG relative-luminance math,
   not assumed): even after the .cristaline-hero__inner position bug above
   was fixed, the desktop crop's own left zone is genuinely bright
   (~238,238,233 — sky/mountains) and champagne (#C5AD8A) is a MID-tone
   colour — against a bright photo, contrast is fundamentally bad (~1.6:1
   measured) and, counter-intuitively, only fixable by scrim opacity alone
   at very heavy (~80%+) darkening, well past this project's own "never
   heavily darken / never go grey and heavy" constraint. A page-wide scrim
   gradient (however dark) can't reliably solve this since it has to work
   across the WHOLE range of tones the real photo actually contains, not a
   single sampled point — a dedicated text-shadow is the standard, robust
   technique for exactly this ("legible text over an arbitrary photo")
   because it creates real local contrast right at the glyph edges,
   independent of what's directly behind them. Two layers: a tight, fairly
   opaque shadow for edge definition + a softer, wider glow — same
   principle as .cristaline-hero__heading/__subheading's own shadows below,
   just stronger, since champagne needs more help than near-white text
   does against the same photo. */
.cristaline-hero__kicker {
	color: var(--color-chrome-accent);
	font-family: var(--font-nav);
	font-size: .95rem;
	font-weight: 600;
	letter-spacing: .1em;
	margin: 0 0 var(--space-3);
	text-shadow: 0 1px 3px rgba(0, 0, 0, .55), 0 2px 12px rgba(0, 0, 0, .35);
	text-transform: uppercase;
}

/* Subtle text-shadow — the client's own "améliorer légèrement le contraste
   du texte sur l'image" ask, kept deliberately faint (never a heavy drop
   shadow) since the gradient scrim above already does most of the work. */
.cristaline-hero__heading {
	color: var(--color-bg);
	text-shadow: 0 1px 12px rgba(0, 0, 0, .25);
}

.cristaline-hero__subheading {
	color: rgba(248, 247, 244, .92);
	font-size: 1.05rem;
	max-width: 46ch;
	text-shadow: 0 1px 8px rgba(0, 0, 0, .2);
}

.cristaline-hero__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-4);
	margin-top: var(--space-6);
}

/* 0.2.0-dev.13: real bug, confirmed live — the 2 CTAs already stack one
   per row below 768px (flex-wrap doing its job), but each one still sized
   to its OWN intrinsic text width ("Découvrir les nouveautés" vs
   "Explorer les collections", 225px vs 210px measured live) rather than
   matching each other — flex-wrap only affects the MAIN axis (here,
   horizontal); nothing was making the two rows the same width. Switching
   to flex-direction:column turns width into the CROSS axis, where
   align-items:stretch (the flex default, made explicit here) naturally
   gives both buttons the same full-width box with no extra sizing rule
   needed — the client's own "boutons de même largeur" ask for this
   round. */
@media (max-width: 767px) {
	.cristaline-hero__actions {
		align-items: stretch;
		flex-direction: column;
	}
}

/* 0.2.0-dev.3: styled here, scoped to THIS one class only — never touching
   components.css's shared ".button" rule, which also styles every WooCommerce
   button (add-to-cart, checkout, quantity, …). Default ink-fill/pearl-text/
   border/dimensions all already come from ".button" (components.css) and
   are left alone; this only adds a light shadow + the hover/active
   treatment this round asks for, specifically for the hero. */
/* white-space:nowrap keeps each button's own label as one unbroken line —
   same fix as the footer legal links (layout.css) for the same reason: a
   flex row with wrap should move a WHOLE button to the next line if it
   doesn't fit, never wrap its own text internally (confirmed live in the
   local harness — without this, both buttons were shrinking to a
   narrower-than-natural width and wrapping their own label onto 2 lines,
   which also stretched the OTHER button's height to match via the flex
   row's default align-items:stretch). */
.cristaline-hero__cta {
	align-items: center;
	box-shadow: 0 2px 8px rgba(23, 23, 23, .18);
	display: inline-flex;
	justify-content: center;
	min-height: 48px;
	transition: background-color 220ms ease, color 220ms ease, box-shadow 220ms ease, transform 220ms ease;
	white-space: nowrap;
}

/* components.css's shared ".button:hover" (background/border/color) is
   UNCONDITIONAL — it would still apply here on a touch tap regardless of
   the (hover:hover) media query below, since this element also carries the
   plain ".button" class. Neutralized back to the same tied specificity
   (0,2,0) but later in source order (this file loads after components.css)
   so it wins on every device by default; the media query below then
   re-applies champagne ONLY where a real hover exists. */
.cristaline-hero__cta--primary:hover {
	background-color: var(--color-ink);
	border-color: var(--color-ink);
	color: var(--color-bg);
}

/* Desktop-with-a-real-pointer only — champagne stays an accent used
   sparingly, never the default state of every button site-wide (that's
   still components.css's own ink fill, untouched). */
@media (hover: hover) and (pointer: fine) {
	.cristaline-hero__cta--primary:hover {
		background-color: var(--color-gold-hover);
		border-color: var(--color-gold-hover);
		box-shadow: 0 4px 14px rgba(23, 23, 23, .25);
		color: var(--color-ink);
		transform: translateY(-1px);
	}
}

/* Secondary hero CTA — client's own "fond transparent ou clair, bordure
   fine, texte charbon" spec. components.css's shared ".button.secondary"
   (transparent bg, gold border) is correct everywhere else on the site, but
   fully transparent would sit directly on a photo here with no guaranteed
   contrast — given a light/opaque fill instead, scoped to the hero only.
   Selector matches ".button.secondary"'s own specificity (0,2,0) plus this
   class (0,3,0 total) so it reliably wins regardless of source order — the
   same match-or-exceed pattern used throughout this project for overriding
   a shared global rule (see CLAUDE.md's own Storefront-collision history). */
.button.secondary.cristaline-hero__cta--secondary {
	background-color: rgba(248, 247, 244, .94);
	border-color: rgba(248, 247, 244, .94);
	color: var(--color-ink);
}

.button.secondary.cristaline-hero__cta--secondary:hover {
	background-color: var(--color-bg);
	border-color: var(--color-bg);
	color: var(--color-ink);
}

@media (hover: hover) and (pointer: fine) {
	.button.secondary.cristaline-hero__cta--secondary:hover {
		box-shadow: 0 4px 14px rgba(23, 23, 23, .18);
		transform: translateY(-1px);
	}
}

/* Transient touch feedback — no lingering hover-coloured state after a tap
   (mobile never matches :hover here at all, per the media query above). */
.cristaline-hero__cta:active {
	transform: scale(.98);
}

@media (min-width: 768px) {
	.cristaline-hero {
		min-height: 620px;
	}

	.cristaline-hero__inner {
		padding: var(--space-16) var(--space-8);
	}
}

/* Small phones: tighter padding, shorter minimum height — avoids the
   "grand vide" the client flagged, and keeps the header-to-hero gap small
   since the hero itself starts right where the sticky header ends. */
@media (max-width: 380px) {
	.cristaline-hero {
		min-height: 380px;
	}

	.cristaline-hero__inner {
		padding: var(--space-8) var(--space-4) var(--space-6);
	}
}

/* =========================================================
   Section kicker — shared "surtitre" style, used above the H2 in every
   section that has one (Nos univers, Nouveautés, Votre sélection à votre
   façon, Nos boutiques). One rule, reused, rather than one per section —
   avoids the drift risk documented elsewhere in this project when the
   "same" visual concept gets copy-pasted per call site instead of shared.
   ========================================================= */
.cristaline-section__kicker {
	color: var(--color-gold-hover);
	font-family: var(--font-nav);
	font-size: .85rem;
	font-weight: 600;
	letter-spacing: .08em;
	margin: 0 0 var(--space-2);
	text-align: center;
	text-transform: uppercase;
}

/* =========================================================
   Nos univers — 0.2.0-dev.4: replaces BOTH the old "Découvrir par
   catégorie" tiles AND the old 3× full-width editorial sections (both
   deleted, see template-parts/homepage/nos-univers.php's own docblock) —
   the client's own explicit "un seul encart clair" instruction. Desktop: 3
   equal cards, one row. Mobile: stacked, image height capped shorter than
   the old editorial blocks were, so the page reads noticeably shorter.
   ========================================================= */
.cristaline-univers__intro {
	color: var(--color-ink);
	margin: 0 auto var(--space-8);
	max-width: 56ch;
	text-align: center;
}

/* align-items:start — client's own "réduire la hauteur vide sous les
   liens" ask: without it, CSS Grid's default row-stretch made each card
   fill the tallest sibling's height, leaving empty space below the
   shortest card's own link. Image ratio + title/link sizing are shared by
   all 3 cards already, so they still read as visually uniform. */
.cristaline-univers__grid {
	align-items: start;
	display: grid;
	gap: var(--space-6);
	grid-template-columns: 1fr;
}

@media (min-width: 768px) {
	.cristaline-univers__grid {
		grid-template-columns: repeat(3, 1fr);
	}
}

/* 0.2.0-dev.10: real cause of "tout le texte devient champagne au survol",
   found live — base.css's own global "a:hover, a:focus { color:
   var(--color-gold-hover) }" matches THIS card too (it's a real <a>,
   ".cristaline-univers__card"), and ".cristaline-univers__title" never
   declared its own color at all, only inheriting the card's — so on
   hover, the card's own (now gold) computed color propagated straight
   into the title. ".cristaline-univers__link" happened to look correct
   already only because it already had its own explicit resting colour
   AND its own dedicated hover override (below); nothing protected the
   title. Fixed at the root: the card's own colour is now explicitly
   pinned across every state (normal/hover/focus-visible/visited), so
   nothing inside it ever has a hover-driven colour to inherit in the
   first place — not just patched on the title alone. */
.cristaline-univers__card,
.cristaline-univers__card:visited,
.cristaline-univers__card:hover,
.cristaline-univers__card:focus-visible {
	color: var(--color-ink);
	display: block;
	transition: transform 180ms ease;
}

.cristaline-univers__card:focus-visible {
	outline: 2px solid var(--color-gold);
	outline-offset: 4px;
}

/* Transient touch feedback only, same reasoning as every other clickable
   card on this page — CSS :active is inherently transient, never sticks
   after the pointer lifts, unlike :hover on a touchscreen. */
.cristaline-univers__card:active {
	transform: scale(.985);
}

.cristaline-univers__media {
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-card);
	display: block;
	margin-bottom: var(--space-4);
	overflow: hidden;
}

/* Shorter than the old editorial blocks' 4/4.5 ratio (~0.889) — the
   client's own explicit "images moins hautes que sur la version actuelle"
   ask for this compact card format. height:auto is required alongside
   aspect-ratio here for the same reason documented in the (now deleted)
   editorial-section rule this replaces: the <img>'s own width/height
   attributes (kept for CLS prevention) map to a literal pixel height that
   silently overrides aspect-ratio unless neutralized back to "auto". */
.cristaline-univers__media img {
	aspect-ratio: 4 / 3.4;
	display: block;
	height: auto;
	object-fit: cover;
	width: 100%;
}

@media (hover: hover) and (pointer: fine) {
	.cristaline-univers__media img {
		transition: transform 500ms ease;
	}

	.cristaline-univers__card:hover .cristaline-univers__media img {
		transform: scale(1.015);
	}
}

/* 0.2.0-dev.5: 1.35rem -> 1.5rem, weight 500 -> 600 — client's own "titres
   Femme, Homme et Enfants légèrement plus visibles" ask, still clearly
   below the section's own H2 size. */
/* color: explicit (--color-ink) rather than left to inherit from the
   card's own colour — belt-and-suspenders alongside the card-level fix
   above, so this specific title never changes colour even if some future
   rule touches the card's own :hover colour again. */
.cristaline-univers__title {
	color: var(--color-ink);
	display: block;
	font-family: var(--font-heading);
	font-size: 1.5rem;
	font-weight: 600;
	line-height: 1.2;
	margin-bottom: var(--space-2);
}

/* 0.2.0-dev.6: mobile font-size 1.1rem (17.6px) -> 1.3rem (20.8px) — the
   client's own explicit "réduire les liens Découvrir la collection… à
   environ 20-22px" ask (a card-content wording tweak, not a change to the
   univers cards' own structure/layout, which stays untouched this round).
   Underline was already thin (1px) and offset (.25em) — already matched
   this round's own spec, no change needed there. Desktop size (.9rem,
   below) untouched — not mentioned this round. */
.cristaline-univers__link {
	color: var(--color-ink);
	display: inline-block;
	font-family: var(--font-nav);
	font-size: 1.3rem;
	font-weight: 500;
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: .25em;
}

@media (min-width: 768px) {
	.cristaline-univers__link {
		font-size: .9rem;
	}
}

@media (hover: hover) and (pointer: fine) {
	.cristaline-univers__card:hover .cristaline-univers__link {
		color: var(--color-gold-hover);
	}
}

.cristaline-univers__card:focus-visible .cristaline-univers__link {
	color: var(--color-gold-hover);
}

/* =========================================================
   Nouveautés — reuses WooCommerce's ul.products, laid out with CSS Grid
   instead of its default float layout (see also woocommerce.css, which
   applies the same grid to shop/category pages)
   ========================================================= */
/* 0.2.0-dev.24: breakpoints changed from this section's own previous
   768px/1024px thresholds to 900px/1280px — matching the SAME "no sidebar"
   grid already used sitewide (assets/css/woocommerce.css, shared by
   single-product related/upsell grids) instead of a second, homepage-only
   set of numbers for the same 2/3/4-column shape.
   0.2.0-dev.28: the intermediate 900px/3-column tier below was REMOVED —
   this section now shows exactly 4 real products (never 8), and this
   round's own brief explicitly specifies only 2 tiers for it ("Tablette :
   2 colonnes" / "PC : exactement 4 cartes sur une seule ligne"), not the
   3-tier shape shop.css's sidebar-having archive grid still needs
   (unchanged, not touched this round — a 4-item preview and a
   many-item paginated archive don't need the same breakpoint count). */
.cristaline-products-grid {
	display: grid;
	gap: var(--space-6);
	grid-template-columns: repeat(2, 1fr);
}

/* 0.2.0-dev.28 — the 0.2.0-dev.26 fix below was specificity-insufficient
   and never actually won on the homepage. Storefront's real compiled rule
   (Storefront theme's own assets/css/woocommerce/woocommerce.css, fetched
   and confirmed live this round) is the UNSCOPED `ul.products::before,
   ul.products::after{content:"";display:table}` — specificity (0,1,2): 1
   type (ul) + 1 class (.products) + 1 pseudo-element. The 0.2.0-dev.26
   fix, `.cristaline-products-grid::before` alone, is (0,1,1): 1 class + 1
   pseudo-element — LOWER on the 3rd (type) component once the class count
   ties at 1, so Storefront's rule kept winning regardless of load order.
   This is exactly why the shop/category archives (fixed correctly in
   assets/css/woocommerce.css via `.woocommerce ul.products::before`,
   (0,2,2) — wins on class-count) were never affected, while the homepage
   — which carries no `.woocommerce`/`.woocommerce-page` body class at all
   — kept the phantom grid item: confirmed live as the exact, sole cause of
   this round's own "3 cards on row 1, 4 on later rows" report (the
   generated, empty `::before` box occupies grid cell 1, pushing every
   real card one position later). Fixed by matching BOTH real classes the
   markup already carries (`<ul class="products cristaline-products-grid">`,
   new-arrivals.php) instead of just one — (0,2,2), now beats Storefront's
   (0,1,2) on class-count alone, regardless of type-count or load order. */
ul.products.cristaline-products-grid::before,
ul.products.cristaline-products-grid::after {
	content: none;
}

@media (min-width: 1280px) {
	.cristaline-products-grid {
		grid-template-columns: repeat(4, 1fr);
	}
}

@media (max-width: 430px) {
	.cristaline-products-grid {
		gap: var(--space-4);
	}
}

.cristaline-new-arrivals__intro {
	color: var(--color-ink);
	margin: 0 auto var(--space-8);
	max-width: 56ch;
	text-align: center;
}

/* Positioned ancestor for the badges below — same shared markup as the
   WooCommerce category/Boutique grid (inc/shop.php's
   cristaline_shop_render_product_badges()/_brand(), 0.2.0-dev.22 — this
   section no longer has its own local badge/brand hooks, see
   new-arrivals.php). Anchoring to the <li> instead keeps this rule simple
   regardless of the product-link <a>'s own exact class name.
   0.2.0-dev.24: `display:flex;flex-direction:column` (matching shop.css's
   own 0.2.0-dev.23 fix, now extended here since Nouveautés is explicitly
   in scope this round) — lets the add-to-cart button use `margin-top:auto`
   below to stay flush at the bottom of every card regardless of how many
   lines the title/brand take, which is the real cause of this round's own
   "grands espaces verticaux et lignes irrégulières" / "gros écarts quand
   un produit est en rupture" complaints: without this, cards with a
   1-line vs 2-line title, or a shorter "Rupture de stock" button label,
   left their buttons (and the visual bottom edge of each card) at
   different heights within the same row. */
.cristaline-products-grid li.product {
	/* 0.2.0-dev.26: `min-width: 0` — same real, confirmed fix as
	   shop.css's own identical rule (a `1fr` grid track never shrinks
	   below its item's own content min-width by default; a long nowrap
	   sale-price string was forcing one card's column wider than its
	   siblings). See the `.cristaline-products-grid .price del/ins` rule
	   below for the matching price-side half of this same fix. */
	display: flex;
	flex-direction: column;
	min-width: 0;
	position: relative;
}

.cristaline-products-grid a.woocommerce-LoopProduct-link {
	display: block;
	position: relative;
}

/* Same badge/brand visual treatment as assets/css/shop.css's
   .cristaline-product-badges / .cristaline-product-badge / .cristaline-product-brand
   rules — duplicated here (not shared via one file) because shop.css only
   enqueues on real shop/category archives (cristaline_is_shop_context()),
   never the homepage, while this markup is now the same shared PHP output
   on both. Keep both blocks in sync if this visual treatment ever changes. */
.cristaline-product-badges {
	display: flex;
	flex-direction: column;
	gap: var(--space-1);
	left: var(--space-3);
	position: absolute;
	top: var(--space-3);
	z-index: 1;
}

.cristaline-product-badge {
	border-radius: var(--radius-sm);
	font-family: var(--font-nav);
	font-size: .68rem;
	font-weight: 700;
	letter-spacing: .04em;
	padding: var(--space-1) var(--space-2);
	text-transform: uppercase;
}

.cristaline-product-badge--new {
	background-color: var(--color-bg);
	color: var(--color-ink);
}

.cristaline-product-badge--sale {
	background-color: var(--color-sale);
	color: var(--color-bg);
}

.cristaline-product-badge--outofstock {
	background-color: var(--color-neutral);
	color: var(--color-bg);
}

/* 0.2.0-dev.39 — kept in sync with shop.css's own identical fix: single
   line + ellipsis (was a 2-line clamp) with an explicit min-height, so this
   line's space is reserved even when empty. inc/shop.php's
   cristaline_shop_render_product_brand() (the SAME shared function this
   grid's markup already reused since 0.2.0-dev.22) now always prints the
   span, aria-hidden when a product has no real brand — previously the span
   was entirely absent from the DOM in that case, which is the real,
   confirmed cause of this grid's own "le prix ne tombe pas au même niveau"
   symptom: a no-brand card's title/price started higher than a same-row
   card that does show a brand line. */
.cristaline-product-brand {
	/* 0.2.0-dev.24: same accessible-contrast fix as shop.css's own
	   0.2.0-dev.23 change (kept in sync per that round's own note) —
	   #A8B0B3 on this card's #F8F7F4 background measures ~2.05:1, well
	   under WCAG AA's 4.5:1 for real text. */
	color: color-mix(in srgb, var(--color-neutral) 40%, var(--color-ink) 60%);
	display: block;
	font-family: var(--font-nav);
	font-size: .72rem;
	letter-spacing: .04em;
	line-height: 1.4;
	min-height: calc(.72rem * 1.4);
	overflow: hidden;
	text-overflow: ellipsis;
	text-transform: uppercase;
	white-space: nowrap;
}

/* 0.2.0-dev.24: unified with shop.css's own image rule (same properties,
   same 4:5/cover treatment for a real photo or WooCommerce's placeholder
   fallback alike) — this rule previously only set 3 of the properties
   needed (aspect-ratio/object-fit/width), missing the background color,
   border radius, object-position and explicit height:auto/display:block
   shop.css already has, which is exactly the kind of inconsistency this
   round's own "structure homogène" brief exists to close. */
.cristaline-products-grid img {
	aspect-ratio: 4 / 5;
	background-color: var(--color-bg-alt);
	border-radius: var(--radius-md);
	display: block;
	height: auto;
	object-fit: cover;
	object-position: center;
	width: 100%;
}

@media (hover: hover) and (pointer: fine) {
	.cristaline-products-grid img {
		transition: transform 400ms ease, box-shadow 220ms ease;
	}

	.cristaline-products-grid a.woocommerce-LoopProduct-link:hover img {
		box-shadow: var(--shadow-card);
		transform: scale(1.02);
	}
}

.cristaline-products-grid a.woocommerce-LoopProduct-link:focus-visible {
	outline: 2px solid var(--color-gold);
	outline-offset: 2px;
}

/* 0.2.0-dev.26: reverted the 0.2.0-dev.24 transform+clip-path zoom (same
   real, confirmed regression as assets/css/shop.css's own identical rule —
   see that file's comment for the full measured explanation: clip-path's
   reference box resolves AFTER the transform, so it never actually clipped
   the enlarged render back down, and the oversized placeholder bled into
   the badge/text areas of the card). `object-fit:contain` is the safe
   replacement — the placeholder can never exceed its own box. */
.cristaline-products-grid img.woocommerce-placeholder {
	object-fit: contain;
}

.cristaline-products-grid li.product.outofstock img {
	opacity: .55;
}

/* WooCommerce's own native "Promo !" ribbon duplicates this grid's own
   .cristaline-product-badge--sale badge — same real bug already fixed in
   shop.css (0.2.0-dev.23), extended here since this grid renders the exact
   same shared PHP markup. */
.cristaline-products-grid .onsale {
	display: none;
}

/* 0.2.0-dev.39 — same min-height fix as shop.css's own identical rule:
   reserves up to 2 real lines (an on-sale price's old+new amounts can wrap
   onto separate lines, see the del/ins nowrap rule below) so the space
   between price and the button below stays visually consistent whether a
   card is on sale or not. */
.cristaline-products-grid .price {
	display: block;
	font-size: .95rem;
	line-height: 1.3;
	min-height: calc(.95rem * 1.3 * 2);
}

/* 0.2.0-dev.26: same real fix as shop.css's own identical rule — nowrap
   moved from the whole `.price` (old+new amount together, which could
   force this ONE card's grid column wider than its siblings) onto each
   price fragment individually, so a single amount still never breaks
   mid-number but the two fragments can wrap onto separate lines from each
   other if a column is ever too narrow for both. */
.cristaline-products-grid .price del,
.cristaline-products-grid .price ins {
	white-space: nowrap;
}

.cristaline-products-grid .price ins {
	color: var(--color-sale);
	font-weight: 600;
	text-decoration: none;
}

.cristaline-products-grid .price del {
	color: var(--color-neutral);
	opacity: 1;
}

/* Clamped to 2 lines — the client's own "titres tronqués pour éviter les
   hauteurs de cartes inégales" ask, since product names vary a lot in
   length and this grid has no other mechanism forcing equal card heights.
   0.2.0-dev.39: added an explicit min-height (2 real lines at this exact
   font-size/line-height) — kept in sync with shop.css's own identical fix.
   -webkit-line-clamp only caps the MAXIMUM height; without a min-height a
   short 1-line title left less space above the price than a long 2-line
   one in the same row. */
.cristaline-products-grid .woocommerce-loop-product__title {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
	hyphens: none;
	line-height: 1.35;
	min-height: calc(1rem * 1.35 * 2);
	overflow-wrap: normal;
	word-break: normal;
}

/* Same button treatment as shop.css's own 0.2.0-dev.23 fix: `.button`
   (components.css) never sets its own `display`, so a real <a> defaults to
   `inline` and silently ignores `width` — `display:block` is required for
   `width:100%` to do anything. `margin-top:auto` (the card is now
   `display:flex;flex-direction:column`, see li.product above) pins the
   button to the bottom of the card regardless of title/brand/price height. */
.cristaline-products-grid .button {
	display: block;
	margin-top: auto;
	text-align: center;
	width: 100%;
}

/* 0.2.0-dev.40 — same real fix as shop.css's own identical rule: this grid
   is 2 columns by default (see .cristaline-products-grid above), so at a
   narrow phone width a card column is only ~150-165px wide — too narrow
   for "Ajouter au panier"/"Choisir une option" to fit on one line at the
   button's normal sitewide padding/font-size. A wrapped button in one card
   next to a 1-line button in its row sibling breaks the "same line" look
   even though margin-top:auto still pins both buttons' BOTTOM edge to the
   same row height — their TOP edges (and visible text) no longer align.
   Scoped to this grid only, never the sitewide `.button` default. */
@media (max-width: 480px) {
	.cristaline-products-grid .button {
		font-size: .82rem;
		padding: var(--space-3) var(--space-2);
	}
}

.cristaline-products-grid li.product.outofstock .button {
	background-color: transparent;
	border-color: var(--color-neutral);
	color: var(--color-neutral);
}

@media (hover: hover) and (pointer: fine) {
	.cristaline-products-grid li.product.outofstock .button:hover {
		background-color: transparent;
		border-color: var(--color-neutral);
		color: var(--color-neutral);
	}
}

/* =========================================================
   Nos marques (brand-strip.php) — 0.2.0-dev.38 "galerie de logos premium"
   lightening pass (was a boxed-card grid since 0.2.0-dev.34). Client's own
   explicit spec: 2 regular ROWS on desktop, 3 columns on tablet, 2 columns
   on mobile, logos only (no name/count — already true, unchanged), borders
   near-invisible or removed entirely, generous breathing room without
   making the section excessively tall.

   12 featured brands (cristaline_get_featured_homepage_brand_slugs(),
   inc/brands.php) ÷ 6 columns = exactly 2 rows — so "2 rows on desktop"
   means 6 columns at that tier, not the previous 4-then-6 two-step ramp;
   collapsed to a single, simpler 3-tier system (2 / 3 / 6) that maps
   directly to the brief's own 3 named breakpoints, rather than 4.
   ========================================================= */
.cristaline-brands__intro {
	color: var(--color-ink);
	margin: 0 auto var(--space-8);
	max-width: 56ch;
	text-align: center;
}

/* 20px — explicit px value, not a --space-* token: tokens.css's scale has
   no --space-5 (it jumps --space-4/1rem to --space-6/1.5rem directly),
   and an invalid token inside a shorthand/var() drops the whole
   declaration — see shop.css's own .cristaline-brands-gallery__grid
   comment for the same fix applied to the Marques page gallery. */
.cristaline-brands__grid {
	display: grid;
	gap: 20px;
	grid-template-columns: repeat(2, 1fr);
}

@media (min-width: 600px) {
	.cristaline-brands__grid {
		grid-template-columns: repeat(3, 1fr);
	}
}

@media (min-width: 1024px) {
	.cristaline-brands__grid {
		gap: var(--space-6);
		grid-template-columns: repeat(6, 1fr);
	}
}

/* No background, no border by default — a logo surrounded by whitespace
   only, per the client's own "contours presque invisibles ou totalement
   supprimés" instruction. Height trimmed from the previous 140px boxed
   card to keep the section from reading as tall now that it's no longer
   visually anchored by a card outline. */
.cristaline-brands__card,
.cristaline-brands__card:visited {
	align-items: center;
	background-color: transparent;
	border: 1px solid transparent;
	border-radius: var(--radius-md);
	box-shadow: none;
	color: inherit;
	display: flex;
	height: 100%;
	justify-content: center;
	min-height: 96px;
	padding: var(--space-4);
	transition: border-color 200ms ease, transform 200ms ease;
}

@media (min-width: 600px) {
	.cristaline-brands__card,
	.cristaline-brands__card:visited {
		min-height: 110px;
		padding: 20px;
	}
}

/* 0.2.0-dev.35 — a card with no real WooCommerce match/products renders as
   a plain <div> (see template-parts/homepage/brand-strip.php), never an
   <a>, so it's already unreachable by Tab and carries no href by
   construction. This modifier additionally strips any lingering
   affordance a shared class alone might imply — no pointer cursor, no
   hover/focus animation — so a purely informative card never LOOKS
   clickable either (the client's own explicit "aucune animation donnant
   l'impression qu'elle est cliquable" instruction). */
.cristaline-brands__card--static {
	cursor: default;
}

.cristaline-brands__logo {
	align-items: center;
	display: flex;
	height: 52px;
	justify-content: center;
	width: 100%;
}

@media (min-width: 600px) {
	.cristaline-brands__logo {
		height: 60px;
	}
}

.cristaline-brands__logo img {
	height: 100%;
	max-width: 100%;
	object-fit: contain;
	object-position: center;
	transition: transform 220ms ease;
	width: auto;
}

/* Discreet, premium hover: a slight lift and a barely-visible border
   appearing from transparent (never a heavy outline), the logo itself
   growing by ~2-3% — gated to real hover devices so nothing sticks after
   a mobile tap (this project's own standing rule since 0.2.0-dev.3). */
@media (hover: hover) and (pointer: fine) {
	.cristaline-brands__card:not(.cristaline-brands__card--static):hover {
		border-color: rgba(23, 23, 23, .12);
		transform: translateY(-2px);
	}

	.cristaline-brands__card:not(.cristaline-brands__card--static):hover .cristaline-brands__logo img {
		transform: scale(1.025);
	}
}

@media (prefers-reduced-motion: reduce) {
	.cristaline-brands__card,
	.cristaline-brands__logo img {
		transition: none;
	}
}

.cristaline-brands__card:focus-visible {
	outline: 2px solid var(--color-gold);
	outline-offset: 2px;
}

.cristaline-brands__card:not(.cristaline-brands__card--static):active {
	transform: scale(.985);
}

.cristaline-brands__cta {
	display: table;
	margin: var(--space-8) auto 0;
}

/* =========================================================
   Nos boutiques — 0.2.0-dev.4 rewrite: ONE photo + text (was a 4-photo
   grid, now reserved for the real "Nos boutiques" page only — see
   shops.php's own docblock).

   0.2.0-dev.6: mobile order corrected to the client's own explicit
   "label, titre, photographie, description, villes, bouton" sequence —
   previously the photo rendered FIRST (before the kicker/title), which
   didn't match. shops.php now renders 3 siblings (.cristaline-shops__head,
   __media, __body, in that literal order) instead of 2, specifically so
   plain DOM order alone gives the right mobile sequence with no CSS
   reordering trick needed. Desktop reassembles them with CSS Grid instead
   of the old 2-item flex row (media 55% / body 45%): __media spans both
   grid rows in column 1, __head sits in row 1 / column 2, __body in row 2
   / column 2 — visually equivalent to the previous single text column
   (kicker+title above, paragraph+cities+button below), just split across
   2 real elements now instead of 1. */
.cristaline-shops__layout {
	display: flex;
	flex-direction: column;
	gap: var(--space-6);
}

.cristaline-shops__head,
.cristaline-shops__body {
	text-align: center;
}

.cristaline-shops__media {
	border-radius: var(--radius-md);
	box-shadow: var(--shadow-card);
	display: block;
	overflow: hidden;
}

.cristaline-shops__media img {
	aspect-ratio: 4 / 3;
	display: block;
	object-fit: cover;
	width: 100%;
}

/* This section's own kicker/heading are centered on mobile (stacked
   layout) but need to switch to left-aligned once they sit in a text
   column next to the photo on desktop — .cristaline-section__kicker/
   __heading's own shared rules default to centered, overridden here only
   for this section, only at the desktop breakpoint below. */
/* 0.2.0-dev.5: max-width 48ch (~480-500px depending on font metrics) ->
   430px — client's own explicit "largeur du texte, environ 400 à 460 px"
   ask. */
.cristaline-shops__text {
	color: var(--color-ink);
	margin: 0 auto var(--space-4);
	max-width: 430px;
}

/* 0.2.0-dev.5: was --color-neutral (#A8B0B3) — client's own "la ligne des
   villes est actuellement trop claire" complaint, same fix pattern as the
   process-steps text above (a color-mix blend toward --color-ink, still
   clearly secondary next to the paragraph above it). */
.cristaline-shops__cities {
	color: color-mix(in srgb, var(--color-ink) 65%, var(--color-neutral) 35%);
	font-family: var(--font-nav);
	font-size: .9rem;
	letter-spacing: .02em;
	margin: 0 0 var(--space-6);
}

@media (min-width: 768px) {
	/* align-content:center + auto rows (rather than letting the 2 text
	   rows stretch to share the photo's own full height) — a first pass
	   using align-self:end/start on the 2 text items alone left a real,
	   ugly ~127px dead gap between the title and the paragraph (confirmed
	   live), since each row track was still sized to roughly HALF the
	   photo's height regardless of how short its own content was. Auto
	   rows keep __head and __body their own natural height + row-gap
	   apart, and align-content:center vertically centers that short
	   2-row block as a group against the taller __media column — the
	   same "vertically centered next to the photo" result as the old
	   2-item flex layout, just achieved correctly this time. */
	.cristaline-shops__layout {
		align-content: center;
		column-gap: var(--space-12);
		display: grid;
		grid-template-areas: "media head" "media body";
		grid-template-columns: 55% 1fr;
		grid-template-rows: auto auto;
		row-gap: var(--space-3);
	}

	.cristaline-shops__head {
		grid-area: head;
	}

	.cristaline-shops__media {
		grid-area: media;
	}

	.cristaline-shops__body {
		grid-area: body;
	}

	.cristaline-shops__head,
	.cristaline-shops__body {
		text-align: left;
	}

	.cristaline-shops__kicker,
	.cristaline-shops__heading {
		text-align: left;
	}

	.cristaline-shops__text {
		margin-left: 0;
		margin-right: 0;
	}
}

.cristaline-shops__cta {
	margin-top: var(--space-2);
}

/* Same "neutralize the shared .button:hover, re-apply champagne only under
   (hover:hover)" pattern as .cristaline-hero__cta — this button is new this
   round and would otherwise inherit components.css's UNCONDITIONAL
   ".button:hover", which can latch after a touch tap. */
.cristaline-shops__cta:hover {
	background-color: var(--color-ink);
	border-color: var(--color-ink);
	color: var(--color-bg);
}

@media (hover: hover) and (pointer: fine) {
	.cristaline-shops__cta:hover {
		background-color: var(--color-gold-hover);
		border-color: var(--color-gold-hover);
		color: var(--color-ink);
	}
}

/* =========================================================
   Votre sélection, à votre façon — 0.2.0-dev.4: new 3-step section, added
   directly before "Nos services" (below) to explain the general flow
   first — never a replacement for that 4-card grid, per the client's own
   mid-brief correction.
   ========================================================= */
/* 0.2.0-dev.5: reduces this ONE section's own vertical padding below the
   shared .cristaline-section large-screen default (96px, see the top of
   this file) — the client's own explicit "réduire la hauteur globale de
   la section d'environ 15 à 20%" ask for this section specifically, while
   every OTHER section still gets the general 80-110px large-screen
   padding. Same specificity as .cristaline-section (one class each), so
   source order (this rule loads later) decides it. */
/* 0.2.0-dev.8: 52px -> 46px (~12% reduction, inside the client's own
   "10 à 15%" ask) — covers this round's remaining 4th named gap ("avant
   UN SERVICE SIMPLE ET PERSONNALISÉ", this section's own top padding) and
   its symmetric bottom padding (part of "entre la fin de Votre sélection
   et la section boutiques", together with the shared .cristaline-section
   reduction above). */
@media (min-width: 1280px) {
	.cristaline-process {
		padding: 46px 0;
	}
}

/* 0.2.0-dev.5: margin-bottom 48px -> 32px, tightened as part of the same
   "reduce section height" ask above. */
.cristaline-process__intro {
	color: var(--color-ink);
	margin: 0 auto var(--space-8);
	max-width: 60ch;
	text-align: center;
}

.cristaline-process__steps {
	display: grid;
	gap: var(--space-6);
	grid-template-columns: 1fr;
	list-style: none;
	margin: 0;
	padding: 0;
}

.cristaline-process__step {
	text-align: center;
}

.cristaline-process__number {
	color: var(--color-gold-hover);
	display: block;
	font-family: var(--font-heading);
	font-size: 2rem;
	line-height: 1;
	margin-bottom: var(--space-3);
}

/* 0.2.0-dev.5: 1.15rem -> 1.25rem — client's own "augmenter légèrement la
   taille des titres" ask (Sélectionnez / Nous confirmons / Essayez ou
   recevez). */
.cristaline-process__title {
	display: block;
	font-family: var(--font-heading);
	font-size: 1.25rem;
	margin-bottom: var(--space-2);
}

/* 0.2.0-dev.5: was --color-neutral (#A8B0B3) — client's own "ne pas
   utiliser un gris trop clair" complaint, confirmed a real low-contrast
   choice against this section's own light backgrounds. A blend weighted
   toward --color-ink reads as a genuine secondary tone rather than a
   near-invisible one, same technique already used for the footer's own
   copyright text on its dark background (color-mix, no new palette
   value). */
.cristaline-process__text {
	color: color-mix(in srgb, var(--color-ink) 65%, var(--color-neutral) 35%);
	display: block;
	font-size: .95rem;
	line-height: 1.5;
	margin: 0 auto;
	max-width: 32ch;
}

/* Mobile: each step is a left-aligned row (number badge + text), connected
   by a thin vertical line — the client's own "ligne verticale légère
   optionnelle" ask. The line is a single ::before on the LIST (not per
   step) so it doesn't need to stop short of the last number by guesswork —
   it's simply inset to start/end at the vertical center of the first/last
   number circle. */
@media (max-width: 767px) {
	.cristaline-process__steps {
		position: relative;
	}

	.cristaline-process__steps::before {
		background-color: var(--color-accent-soft);
		content: "";
		left: 19px;
		position: absolute;
		top: 20px;
		bottom: 20px;
		width: 1px;
	}

	.cristaline-process__step {
		display: grid;
		column-gap: var(--space-4);
		grid-template-columns: 40px 1fr;
		position: relative;
		text-align: left;
	}

	.cristaline-process__number {
		align-items: center;
		background-color: var(--color-bg);
		border: 1px solid var(--color-accent-soft);
		border-radius: 50%;
		display: flex;
		font-size: 1rem;
		height: 40px;
		justify-content: center;
		margin-bottom: 0;
		position: relative;
		width: 40px;
		z-index: 1;
	}

	.cristaline-process__title,
	.cristaline-process__text {
		grid-column: 2;
	}
}

/* Desktop: 3 balanced columns with a thin vertical separator between them
   (never around the whole row) — the client's own "fins séparateurs" ask. */
@media (min-width: 768px) {
	.cristaline-process__steps {
		grid-template-columns: repeat(3, 1fr);
	}

	.cristaline-process__step {
		border-left: 1px solid var(--color-accent-soft);
		padding: 0 var(--space-6);
	}

	.cristaline-process__step:first-child {
		border-left: none;
		padding-left: 0;
	}

	.cristaline-process__step:last-child {
		padding-right: 0;
	}
}

/* =========================================================
   Services et avantages — 4 short links to real existing pages.
   ========================================================= */
.cristaline-services__grid {
	display: grid;
	gap: var(--space-4);
	grid-template-columns: repeat(2, 1fr);
	list-style: none;
	margin: 0;
	padding: 0;
	text-align: center;
}

/* 0.2.0-dev.2: height:100% is the real fix for "les quatre cartes n'ont pas
   visuellement la même hauteur" — .cristaline-services__grid is a 2-row
   (mobile) / 1-row (desktop) CSS Grid, and a grid ROW already auto-sizes to
   its own tallest cell (default align-items:stretch on the grid, unchanged
   here) — but that only stretches the <li> itself; without height:100% the
   <a> inside stayed only as tall as its own text, so a 1-line label
   ("Livraison") and a 2-line one ("Essayage en boutique") rendered
   different visible card heights even though their grid CELLS were already
   equal. min-height is now a mobile-only FLOOR on top of that (the client's
   own "environ 130 à 145px maximum" ask), not the mechanism making them
   equal — that's height:100%, which also already keeps desktop's single
   row uniform with no extra rule needed there.
   0.2.0-dev.3: real cause of "la carte Nous contacter peut rester bleue"
   found — the old combined ".../a:hover, a:focus-visible { background-
   color: --color-accent-soft }" rule used --color-accent-soft (#C9DDE2, a
   pale crystal blue), and mobile browsers can keep matching :hover after a
   tap until a later, unrelated tap elsewhere clears it — exactly the
   "reste bleue" symptom. Fixed the same way as every other component this
   round: :hover only inside (hover:hover) so touch never matches it at
   all; :focus-visible keeps a clear outline but no longer also changes the
   background (a keyboard-only indicator, never a lingering fill); the only
   touch feedback now comes from :active, which is inherently transient. */
/* 0.2.0-dev.5: background swapped from --color-bg-alt to --color-bg — the
   section itself (services.php) now carries --color-bg-alt (EDF1F2) as
   part of this round's section-alternation fix (see .cristaline-section--alt's
   own comment near the top of this file), so the cards need the OPPOSITE
   light tone to still read as distinct cards rather than blending flush
   into their own section background. A thin border replaces the old
   same-color card, per the client's own "bordure fine ou ombre extrêmement
   légère" spec. */
/* 0.2.0-dev.8: border color-color-accent-soft (#C9DDE2, a pale crystal
   BLUE) -> rgba(168,176,179,.4) (--color-neutral, a neutral gray) — the
   client's own "les cartes sont presque invisibles" report plus explicit
   new spec asking for a neutral-toned border, not the blue one previously
   used (blue is reserved for hover/active feedback specifically, per this
   same rule block below, and reads as slightly odd as a permanent resting
   border). A very light shadow added on top — the client's own "ombre
   éventuelle extrêmement légère" option — for a touch more separation from
   the section's own --alt (#EDF1F2) background without a heavier card
   treatment. Background/text/icon colours were already correct
   (--color-bg / --color-ink) — confirmed live before assuming they needed
   a fix too. */
.cristaline-services__item a,
.cristaline-services__item a:visited {
	align-items: center;
	background-color: var(--color-bg);
	border: 1px solid rgba(168, 176, 179, .4);
	border-radius: var(--radius-md);
	box-shadow: 0 1px 4px rgba(23, 23, 23, .05);
	color: var(--color-ink);
	display: flex;
	flex-direction: column;
	font-family: var(--font-nav);
	font-size: .95rem;
	font-weight: 600;
	gap: var(--space-2);
	height: 100%;
	justify-content: center;
	padding: var(--space-4);
	text-align: center;
	transition: background-color 180ms ease, border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease;
}

@media (hover: hover) and (pointer: fine) {
	.cristaline-services__item a:hover {
		background-color: var(--color-accent-soft);
	}
}

.cristaline-services__item a:focus-visible {
	outline: 2px solid var(--color-gold);
	outline-offset: -2px;
}

.cristaline-services__item a:active {
	background-color: var(--color-accent-soft);
	transform: scale(.985);
}

/* Purely decorative (the visible label already names the service) — see
   services.php for the aria-hidden="true" on each inline SVG. */
.cristaline-services__icon {
	align-items: center;
	color: var(--color-ink);
	display: flex;
	justify-content: center;
}

@media (max-width: 767px) {
	.cristaline-services__item a {
		min-height: 136px;
	}
}

@media (min-width: 768px) {
	.cristaline-services__grid {
		grid-template-columns: repeat(4, 1fr);
	}
}

/* Respect reduced-motion preferences for every hover/active transition this
   file adds (brand card, univers card/photo zoom, services card, hero
   button) — none of this page's animation is load-bearing, every
   transition here is a pure hover/touch-feedback nicety, so this list is
   simply every selector above that declared a `transition` property. The
   old shops link/photo zoom is gone with the 0.2.0-dev.4 rewrite (a single
   plain photo now, not a hover card). 0.2.0-dev.30: brand-strip.php's own
   card is now .cristaline-brands__card (was .cristaline-brands__item img,
   the old logo-fade version). */
@media (prefers-reduced-motion: reduce) {
	.cristaline-brands__card,
	.cristaline-univers__card,
	.cristaline-univers__media img,
	.cristaline-services__item a,
	.cristaline-hero__cta {
		transition: none;
	}
}
