/*
 * Shared foundation for every generated preview.
 *
 * Everything a build used to invent — header, nav, footer, buttons, sections, gallery, responsive
 * behaviour — lives here once, so the mistakes we kept shipping cannot come back: a 127px header with a
 * 152px logo tile, links that break under a subfolder, pages that disagree with each other.
 *
 * A site's personality comes from the TOKENS below (set per site by a palette + type pack) and from its
 * variant stylesheet. Nothing here should need editing per business.
 */

/* ── tokens: the only per-site knobs ─────────────────────────────────────────── */
:root {
	--ink: #14110f; /* dark sections + body text */
	--ground: #f7f2e8; /* warm off-white. NEVER #fff — the instant tell of a generated page */
	--accent: #b4763a; /* buttons, small marks, one or two moments */
	--ink-soft: color-mix(in srgb, var(--ink) 66%, var(--ground));
	--line: color-mix(in srgb, var(--ink) 14%, transparent);
	--line-dark: color-mix(in srgb, var(--ground) 18%, transparent);

	--display: 'Forum', Georgia, serif;
	--body: 'Instrument Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
	--display-weight: 400;
	--display-tracking: -0.01em;

	/*
	 * Contrast tokens. Defaults live HERE, in CSS read fresh on every build, and the per-site :root
	 * that render-blocks appends overrides --on-accent with the value picked for that palette.
	 *
	 * --on-accent: text ON the accent (buttons). The default is --ink, not --ground: measured across
	 * the ten palettes, ink beats ground on eight of them and cream-on-accent was as low as 2.12:1.
	 * --accent-ink: the accent darkened toward ink, for small accent-coloured text on the ground —
	 * kickers, eyebrows, prices — where the raw accent ran 2.12–4.94:1 at 11px. Derived from tokens
	 * the site block already sets, so it needs nothing from the build step.
	 */
	--on-accent: var(--ink);
	--accent-ink: color-mix(in srgb, var(--accent) 55%, var(--ink));

	--radius: 14px;
	--pad: clamp(20px, 5vw, 64px); /* page gutter */
	--section: clamp(64px, 9vw, 150px); /* vertical rhythm: whitespace is the luxury signal */
	/*
	 * For bands that are a LINE of content, not a section of it: the facts strip, the plain cta band.
	 * One padding for every section is right while the content is tall and absurd when it is not —
	 * measured on a real page, the facts strip held 101px of content inside 374px of section, so 73%
	 * of it was empty, and the cta band 55%. The tall sections were 14-22%, which is the look working.
	 */
	--section-tight: clamp(32px, 4vw, 72px);
	--bar: 72px; /* header height — see HEADER below */
}

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

html {
	-webkit-text-size-adjust: 100%;
}

body {
	margin: 0;
	background: var(--ground);
	color: var(--ink);
	font-family: var(--body);
	font-size: 17px;
	line-height: 1.6;
	-webkit-font-smoothing: antialiased;
}

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

a {
	color: inherit;
}

h1,
h2,
h3 {
	font-family: var(--display);
	font-weight: var(--display-weight);
	letter-spacing: var(--display-tracking);
	line-height: 1.02;
	margin: 0;
	text-wrap: balance;
}

h1 {
	font-size: clamp(2.6rem, 6.4vw, 5.2rem);
}
h2 {
	font-size: clamp(2rem, 4.2vw, 3.4rem);
}
h3 {
	font-size: clamp(1.15rem, 1.7vw, 1.45rem);
}

/* A heading followed straight by copy had NO gap — h1/h2/h3 carry margin:0 and p only has a bottom
   margin, so "…you arriving?" sat on the line under it in every block that pairs the two. A block that
   wants its own spacing sets it on its own class, which wins on specificity. */
:is(h1, h2, h3) + :is(p, .muted, ul, ol, blockquote) {
	margin-top: 0.7em;
}

p {
	margin: 0 0 1em;
	max-width: 62ch;
}

.wrap {
	width: 100%;
	max-width: 1240px;
	margin-inline: auto;
	padding-inline: var(--pad);
}

.eyebrow {
	font-size: 0.72rem;
	letter-spacing: 0.18em;
	text-transform: uppercase;
	/* Darkened accent: this is 11px of tracked-out uppercase on the ground, where the raw accent was
	   2.21:1 and effectively invisible. */
	color: var(--accent-ink);
	margin: 0 0 14px;
}

.muted {
	color: var(--ink-soft);
}

/* ── header ───────────────────────────────────────────────────────────────────
 * HARD SIZES. Generated sites drifted to a 127px bar carrying a 152x103 logo tile; the mark is a brand
 * mark, not a banner. Bar 72px (60 on phones), logo 40px tall (32), everything on one line.
 */
.site-head {
	position: sticky;
	top: 0;
	z-index: 50;
	background: color-mix(in srgb, var(--ground) 92%, transparent);
	backdrop-filter: blur(10px);
	border-bottom: 1px solid transparent;
	transition: border-color 0.2s ease-out, background-color 0.2s ease-out;
}

.site-head.is-stuck {
	border-bottom-color: var(--line);
}

.site-head__in {
	display: flex;
	align-items: center;
	gap: 18px;
	height: var(--bar);
}

/* white-space: nowrap keeps the mark and the name on one line, which is right until the name is
   "Animal Kingdom Veterinary Hospital" — 410px of it in a 390px viewport, which pushed the whole
   document to 482px wide and gave every page a horizontal scrollbar on a phone. min-width: 0 lets the
   flex item shrink instead of forcing the header open; below the breakpoint the name is allowed to
   wrap rather than be truncated, because a business's own name is the last thing to cut short. */
.brand {
	display: inline-flex;
	align-items: center;
	gap: 10px;
	min-width: 0;
	text-decoration: none;
	font-family: var(--display);
	font-size: 1.15rem;
	letter-spacing: 0.01em;
	white-space: nowrap;
}

@media (max-width: 560px) {
	.brand {
		white-space: normal;
		font-size: 1rem;
		line-height: 1.15;
	}
	.brand span {
		/* Two lines at most; a name longer than that is a sign to shorten it in the content. */
		display: -webkit-box;
		-webkit-line-clamp: 2;
		-webkit-box-orient: vertical;
		overflow: hidden;
	}
}

.brand img {
	height: 40px;
	width: auto;
	max-width: 132px;
	object-fit: contain;
	border-radius: 999px;
	background: #fff;
}

.brand img.is-lockup {
	border-radius: 8px;
	max-width: 168px;
}

.nav {
	display: flex;
	align-items: center;
	gap: clamp(16px, 2.2vw, 30px);
	margin-left: auto;
}

.nav a {
	position: relative;
	font-size: 0.93rem;
	text-decoration: none;
	color: var(--ink-soft);
	padding: 4px 0;
	transition: color 0.18s ease-out;
}

.nav a:hover,
.nav a[aria-current='page'] {
	color: var(--ink);
}

/* The nav's own `a` rule is more specific than .btn, which flattened the CTA's padding to 4px 0 and
   left the pill hugging its text. Restore it for a button that happens to live in the nav. */
.nav a.btn {
	padding: 11px 20px;
	color: var(--ground);
}

.nav a[aria-current='page']::after {
	content: '';
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 1.5px;
	background: var(--accent);
}

.nav a.btn::after {
	content: none;
}

/*
 * A row of buttons. SIX blocks render .b-actions — the five heroes and cta-band — and only three of
 * them ever declared it a flex row. In the other three the buttons fell back to inline-flex and sat
 * with nothing between them but the whitespace in the HTML, which on pill buttons reads as a single
 * blob; cta-band's own `justify-content: center` was a no-op, because the children of a block
 * element cannot be justified. Declared once, here, so a block only states what it ADDS.
 */
.b-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 12px;
}

.btn {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	border: 0;
	border-radius: 999px;
	padding: 12px 22px;
	font: inherit;
	font-size: 0.92rem;
	font-weight: 500;
	text-decoration: none;
	cursor: pointer;
	background: var(--accent);
	/* Not --ground. A button is normal-size text, so it needs 4.5:1, and cream on a mid-tone accent
	   gave 2.21:1 on pearl — nine of the ten palettes failed. --on-accent is picked per palette. */
	color: var(--on-accent);
	transition: transform 0.18s ease-out, filter 0.18s ease-out;
}

.btn:hover {
	transform: translateY(-1px);
	filter: brightness(1.06);
}

.btn--ghost {
	background: transparent;
	color: var(--ink);
	box-shadow: inset 0 0 0 1px var(--line);
}

.burger {
	display: none;
	margin-left: auto;
	width: 40px;
	height: 40px;
	border: 0;
	background: transparent;
	cursor: pointer;
}

.burger span {
	display: block;
	height: 1.5px;
	width: 22px;
	margin: 5px auto;
	background: var(--ink);
	transition: transform 0.2s ease-out, opacity 0.2s ease-out;
}

/* ── sections ─────────────────────────────────────────────────────────────── */
.section {
	padding-block: var(--section);
}

.section--ink {
	background: var(--ink);
	color: var(--ground);
}

/* Any surface that flips to the ink background has to flip its secondary text with it. The footer was
   missing from this list, so .muted there kept --ink-soft — a dark grey meant for a light background —
   and the business description and address sat at 2.68:1 against near-black. AA wants 4.5:1. */
.section--ink .muted,
.section--ink .nav a,
.site-foot .muted {
	color: color-mix(in srgb, var(--ground) 72%, transparent);
}

.section__head {
	display: grid;
	gap: 18px;
	margin-bottom: clamp(28px, 4vw, 56px);
}

@media (min-width: 900px) {
	.section__head--split {
		grid-template-columns: 1.1fr 0.9fr;
		align-items: end;
	}
}

.grid {
	display: grid;
	gap: clamp(16px, 2.4vw, 28px);
}

.grid--2 {
	grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.grid--3 {
	grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.card {
	border-radius: var(--radius);
	overflow: hidden;
	background: color-mix(in srgb, var(--ink) 4%, var(--ground));
}

.card img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	aspect-ratio: 4 / 3;
}

.card__body {
	padding: 18px 20px 22px;
}

.figure {
	border-radius: var(--radius);
	overflow: hidden;
}

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

/* Full-bleed photo band between sections. */
.interstitial img {
	width: 100%;
	height: clamp(260px, 42vw, 520px);
	object-fit: cover;
}

/* ── menu ─────────────────────────────────────────────────────────────────── */
.menu-item {
	display: grid;
	grid-template-columns: 1fr auto;
	gap: 6px 18px;
	padding: 14px 0;
	border-bottom: 1px solid var(--line);
}

/* The last item in a group left a rule hanging under it with nothing beneath — a divider that divides
   nothing. The group's own edge does that job. */
.menu-item:last-child {
	border-bottom: 0;
	padding-bottom: 0;
}

.menu-item__name {
	font-weight: 500;
}

.menu-item__price {
	font-variant-numeric: tabular-nums;
	color: var(--accent-ink);
	white-space: nowrap;
}

.menu-item__desc {
	grid-column: 1 / -1;
	margin: 0;
	font-size: 0.93rem;
	color: var(--ink-soft);
}

/* A SHORT menu — a café's six drinks rather than a restaurant's eleven dishes. The item carries sizes
   instead of a description, so the line stays one row and the prices line up down the column. */
.menu-item--sizes {
	grid-template-columns: 1fr auto;
	align-items: baseline;
}

.menu-sizes {
	display: flex;
	flex-wrap: wrap;
	gap: 6px 14px;
	justify-content: flex-end;
	font-variant-numeric: tabular-nums;
}

.menu-size {
	display: inline-flex;
	align-items: baseline;
	gap: 6px;
	font-size: 0.93rem;
}

.menu-size__label {
	font-size: 0.72rem;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--ink-soft);
}

.menu-size__price {
	color: var(--accent-ink);
}

/* A short menu reads better as one narrow column than as two wide ones. */
.menu-cols--short {
	max-width: 760px;
	grid-template-columns: 1fr;
}

/* ── gallery ──────────────────────────────────────────────────────────────── */
.gallery {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
	gap: 10px;
}

.gallery img {
	width: 100%;
	height: 100%;
	aspect-ratio: 1;
	object-fit: cover;
	border-radius: 10px;
}

/* ── facts / contact ──────────────────────────────────────────────────────── */
.facts {
	display: grid;
	gap: 2px;
}

.fact {
	display: grid;
	grid-template-columns: 7.5rem 1fr;
	gap: 14px;
	padding: 12px 0;
	border-bottom: 1px solid var(--line);
	font-size: 0.95rem;
}

.fact dt {
	color: var(--ink-soft);
	font-size: 0.78rem;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	padding-top: 3px;
}

.fact dd {
	margin: 0;
}

.map {
	width: 100%;
	height: clamp(240px, 34vw, 420px);
	border: 0;
	border-radius: var(--radius);
	background: color-mix(in srgb, var(--ink) 6%, var(--ground));
}

/* ── footer ───────────────────────────────────────────────────────────────── */
.site-foot {
	background: var(--ink);
	color: var(--ground);
	padding-block: clamp(48px, 6vw, 88px) 28px;
}

.site-foot a {
	color: color-mix(in srgb, var(--ground) 80%, transparent);
	text-decoration: none;
}

.site-foot a:hover {
	color: var(--ground);
}

.site-foot__grid {
	display: grid;
	gap: 32px;
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
	padding-bottom: 36px;
	border-bottom: 1px solid var(--line-dark);
}

.site-foot__bottom {
	display: flex;
	flex-wrap: wrap;
	gap: 10px 24px;
	justify-content: space-between;
	padding-top: 22px;
	font-size: 0.82rem;
	color: color-mix(in srgb, var(--ground) 60%, transparent);
}

.stack {
	display: grid;
	gap: 8px;
	align-content: start;
	font-size: 0.93rem;
}

/* ── page header (inner pages) ────────────────────────────────────────────── */
.page-head {
	padding-block: clamp(44px, 6vw, 92px) clamp(28px, 4vw, 56px);
}

/* ── motion: on-load only. No scroll-tied animation anywhere ───────────────── */
@keyframes rise {
	from {
		opacity: 0;
		transform: translateY(14px);
	}
	to {
		opacity: 1;
		transform: none;
	}
}

[data-rise] {
	animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
	animation-delay: var(--d, 0ms);
}

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation: none !important;
		transition: none !important;
	}
}

/* ── phones ───────────────────────────────────────────────────────────────── */
@media (max-width: 820px) {
	:root {
		--bar: 60px;
	}

	.brand img {
		height: 32px;
		max-width: 108px;
	}

	.burger {
		display: block;
	}

	.nav {
		position: absolute;
		top: var(--bar);
		left: 0;
		right: 0;
		display: none;
		flex-direction: column;
		align-items: stretch;
		gap: 0;
		padding: 8px var(--pad) 18px;
		background: var(--ground);
		border-bottom: 1px solid var(--line);
	}

	.site-head.is-open .nav {
		display: flex;
	}

	.nav a {
		padding: 13px 0;
		font-size: 1.05rem;
		border-bottom: 1px solid var(--line);
	}

	.nav .btn {
		margin-top: 12px;
		justify-content: center;
	}

	.fact {
		grid-template-columns: 1fr;
		gap: 2px;
	}
}

/* ── virtual scroll ──────────────────────────────────────────────────────────
   Only live when a look sets --virtual-scroll: 1 AND motion.js decided the device suits it; until
   then none of these classes exist and the page is an ordinary scrolling document. */
.vscroll-on .vscroll {
	position: fixed;
	inset: 0 0 auto 0;
	will-change: transform;
}
/* The spacer is the document's real height — it is what the scrollbar measures and what the browser
   scrolls. Take it out of the flow's normal sizing so it cannot add its own margins. */
.vscroll-on .vscroll-spacer {
	pointer-events: none;
}
/* The header has to sit outside the translated wrapper, so sticky becomes fixed. Same result: it was
   pinned to the top at every scroll position anyway. */
.vscroll-on .site-head {
	position: fixed;
	inset: 0 0 auto 0;
	width: 100%;
}
/* There is no scrolling ancestor left inside the wrapper, so a sticky pin cannot stick. It lays out
   fine as an ordinary column — the photograph scrolls with its text instead of holding. A look that
   wants pinned columns should not ask for virtual scroll. */
.vscroll-on .sticky-split__pin {
	position: static;
}

/* Styling for the interaction components in components.js. Structural, not decorative: a variant
   restyles these, it does not need to re-implement them. */

/* ── carousel ─────────────────────────────────────────────────────────────── */
.rail {
	display: flex;
	gap: 16px;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
	padding-bottom: 4px;
}

.rail::-webkit-scrollbar { display: none; }

.rail.is-dragging { cursor: grabbing; scroll-snap-type: none; }

.rail > * {
	flex: 0 0 min(78vw, 420px);
	scroll-snap-align: start;
}

.rail--wide > * { flex-basis: min(92vw, 720px); }

.carousel__bar {
	display: flex;
	align-items: center;
	gap: 12px;
	margin-top: 18px;
}

.carousel__btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	border: 0;
	border-radius: 999px;
	cursor: pointer;
	background: color-mix(in srgb, var(--ink) 8%, var(--ground));
	color: var(--ink);
	font-size: 1.1rem;
	transition: background-color .2s ease-out, opacity .2s ease-out;
}

.carousel__btn:hover { background: color-mix(in srgb, var(--accent) 30%, var(--ground)); }
.carousel__btn:disabled { opacity: .35; cursor: default; }

.dots { display: flex; gap: 7px; margin-left: auto; }

.dot {
	width: 7px; height: 7px; padding: 0; border: 0; border-radius: 999px; cursor: pointer;
	background: color-mix(in srgb, var(--ink) 22%, transparent);
	transition: width .25s ease-out, background-color .25s ease-out;
}

.dot.is-on { width: 22px; background: var(--accent); }

/* ── slideshow ────────────────────────────────────────────────────────────── */
.slideshow { position: relative; overflow: hidden; }

.slideshow [data-frame] {
	position: absolute;
	inset: 0;
	opacity: 0;
	transition: opacity 1.4s ease-in-out;
}

.slideshow [data-frame].is-on { opacity: 1; }

.slideshow [data-frame] img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* The slow push that makes a still photograph read as footage. */
.slideshow [data-frame].is-on img { animation: kenburns 9s ease-out both; }

@keyframes kenburns {
	from { transform: scale(1.01) translate3d(0, 0, 0); }
	to { transform: scale(1.12) translate3d(-1.5%, -1%, 0); }
}

/* ── before / after ───────────────────────────────────────────────────────── */
.compare { position: relative; overflow: hidden; border-radius: var(--radius); touch-action: none; }
.compare img { display: block; width: 100%; }
.compare__after { position: absolute; inset: 0; width: var(--split, 50%); overflow: hidden; }
.compare__after img { width: auto; height: 100%; max-width: none; }

.compare__handle {
	position: absolute;
	top: 0; bottom: 0;
	left: var(--split, 50%);
	width: 2px;
	background: #fff;
	box-shadow: 0 0 14px rgba(0,0,0,.4);
	pointer-events: none;
}

.compare__handle::after {
	content: '';
	position: absolute;
	top: 50%; left: 50%;
	width: 42px; height: 42px;
	transform: translate(-50%, -50%);
	border-radius: 999px;
	background: #fff;
	box-shadow: 0 4px 16px rgba(0,0,0,.35);
}

.compare input[type=range] { position: absolute; inset: auto 0 10px; width: 100%; opacity: 0; height: 44px; }
.compare__label {
	position: absolute; bottom: 12px;
	padding: 5px 12px; border-radius: 999px;
	background: color-mix(in srgb, #000 55%, transparent);
	color: #fff; font-size: .72rem; letter-spacing: .12em; text-transform: uppercase;
}
.compare__label--a { left: 12px; }
.compare__label--b { right: 12px; }

/* ── lightbox ─────────────────────────────────────────────────────────────── */
.lightbox {
	position: fixed; inset: 0; z-index: 200;
	display: none; place-items: center;
	padding: 4vmin;
	background: color-mix(in srgb, #000 88%, transparent);
	backdrop-filter: blur(6px);
}

.lightbox.is-open { display: grid; animation: fade .25s ease-out both; }
.lightbox img { max-width: 100%; max-height: 92vh; border-radius: 4px; }

.lightbox__close {
	position: absolute; top: 18px; right: 22px;
	width: 44px; height: 44px;
	border: 0; border-radius: 999px; cursor: pointer;
	background: rgba(255,255,255,.14); color: #fff; font-size: 1.6rem; line-height: 1;
}

@keyframes fade { from { opacity: 0 } to { opacity: 1 } }

/* ── stat row (works with data-count) ─────────────────────────────────────── */
.stats { display: grid; gap: clamp(18px,3vw,40px); grid-template-columns: repeat(auto-fit,minmax(150px,1fr)); }
.stat__n { font-family: var(--display); font-size: clamp(2.4rem,5vw,4rem); line-height: 1; }
.stat__n sup { font-size: .4em; vertical-align: super; color: var(--accent); }
.stat__label { margin: 8px 0 0; font-size: .82rem; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-soft); }

/* ── sticky split: one pane holds while the other scrolls ─────────────────── */
@media (min-width: 950px) {
	.sticky-split { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(30px,4vw,70px); align-items: start; }
	.sticky-split__pin { position: sticky; top: calc(var(--bar) + 28px); }
}

@media (prefers-reduced-motion: reduce) {
	.slideshow [data-frame] { transition: none; }
	.slideshow [data-frame].is-on img { animation: none; }
}

/* ── form fields ─────────────────────────────────────────────────────────────
   Used by booking-dates, appointment and reservation. These MUST live here and not in one of those
   blocks: the renderer only includes the stylesheets of blocks a page uses, so a page with the
   appointment block and no booking-dates block was getting unstyled browser controls. */
.b-field { display: grid; gap: 6px; font-size: .78rem; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); }
.b-field--wide { grid-column: 1/-1; }
.b-field input, .b-field select, .b-field textarea { font: inherit; font-size: .98rem; letter-spacing: 0; text-transform: none;
	color: var(--ink); padding: 12px 14px; border: 1px solid var(--line); border-radius: calc(var(--radius) - 4px);
	background: var(--ground); width: 100%; box-sizing: border-box;
	/* The line-height puts every control's text on the same baseline. The HEIGHT — not a min-height —
	   is what keeps them identical: a date input's native calendar button sets a taller content box than
	   a select's, so a minimum let the date fields stand 2px proud of the rest of the form. */
	line-height: 1.4; height: 48px; }
.b-field textarea { height: auto; min-height: 96px; }
.b-field input:focus, .b-field select:focus, .b-field textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.b-book__submit { grid-column: 1/-1; justify-content: center; }
.b-book__hint { grid-column: 1/-1; margin: 0; font-size: .82rem; color: var(--ink-soft); }

/* ── pointer + scroll effects ────────────────────────────────────────────────
   The CSS half of components.js. Everything here is inert until the script adds the element or the
   class, so a page with JavaScript off loses the effect and keeps the content. */

/* Anything a script appends to <body> that must never join the page flow. A look that restyles direct
   children of body has to leave these alone — one of them did not, and the cursor ended up in the
   document, 470px tall, below the footer. */
.is-overlay { position: fixed; }

/* A hairline of progress along the top of the page. --scroll-progress is written by motion.js. */
.scroll-progress { position: fixed; inset: 0 0 auto 0; height: 2px; z-index: 60; transform-origin: 0 50%;
	transform: scaleX(var(--scroll-progress, 0)); background: var(--accent); pointer-events: none; }

/* The cursor. The dot is exact, the ring lags — and both are centred on the pointer, which is why
   they are translated to the raw coordinates and then pulled back by half their own size. */
.cursor-dot, .cursor-ring { position: fixed; top: 0; left: 0; z-index: 90; border-radius: 999px;
	pointer-events: none; will-change: transform; }
.cursor-dot { width: 6px; height: 6px; margin: -3px 0 0 -3px; background: var(--accent); }
.cursor-ring { width: 34px; height: 34px; margin: -17px 0 0 -17px; border: 1px solid color-mix(in srgb,var(--ink) 40%,transparent);
	transition: width .22s ease, height .22s ease, margin .22s ease, border-color .22s ease, background-color .22s ease; }
.cursor-ring.is-over { width: 58px; height: 58px; margin: -29px 0 0 -29px;
	border-color: color-mix(in srgb,var(--accent) 70%,transparent); background: color-mix(in srgb,var(--accent) 12%,transparent); }
@media (hover: none), (pointer: coarse) { .cursor-dot, .cursor-ring { display: none; } }

/* A button that leans toward the pointer needs to spring back, not snap — but only the opted-in ones
   move at all, so only they get a transform transition. Every button keeps the colour transitions. */
.btn { transition: background-color .2s ease, color .2s ease, box-shadow .2s ease; }
.btn[data-magnetic] { transition: transform .45s cubic-bezier(.2,.8,.2,1), background-color .2s ease, color .2s ease, box-shadow .2s ease; }

/* A photograph that wipes into place the first time it is seen.
   The clip goes on the IMAGE, never on the element being observed: clipping the observed element to
   zero width makes its intersection ratio zero, so the observer that would reveal it never fires and
   the photograph stays invisible forever. */
[data-mask] > img,
[data-mask] > picture > img { clip-path: inset(0 100% 0 0); transition: clip-path 1.1s cubic-bezier(.2,.75,.2,1); }
[data-mask].is-revealed > img,
[data-mask].is-revealed > picture > img { clip-path: inset(0 0 0 0); }
.motion-off [data-mask] > img { clip-path: none; transition: none; }


/* ── reveal on scroll ────────────────────────────────────────────────────────
   The CSS half of motion.js's reveals, which the block system never had.
   motion.js has been adding `is-in` to elements that nothing styled, so nothing faded, nothing rose
   and nothing staggered — the whole motion layer was inert. Worse, `.line` was left unstyled too, so
   the spans a headline is split into ran together inline: "Gel, extensions" + "and nail art" rendered
   as "extensionsand". Both rules lived only in the old per-family stylesheets, which the block system
   does not load.

   Everything here is guarded by `.motion-on`, which motion.js sets only when the visitor has not asked
   for reduced motion — so with the class absent, or with JavaScript off, content renders in its final
   state and nothing is ever stuck invisible. */
.motion-on [data-reveal] {
	opacity: 0;
	transform: translateY(16px);
	transition:
		opacity 0.9s cubic-bezier(0.2, 0.7, 0.2, 1) var(--d, 0ms),
		transform 0.9s cubic-bezier(0.2, 0.7, 0.2, 1) var(--d, 0ms);
}
.motion-on [data-reveal].is-in {
	opacity: 1;
	transform: none;
}

/* A headline split for the line-by-line rise. The block display is what puts each line on its own
   line; the overflow is what the line rises out from behind. */
.line {
	display: block;
	overflow: hidden;
}
.line > span {
	display: block;
}
.motion-on [data-reveal-lines] .line > span {
	transform: translateY(106%);
	transition: transform 1s cubic-bezier(0.16, 0.84, 0.28, 1) var(--line-d, 0ms);
}
.motion-on [data-reveal-lines].is-in .line > span {
	transform: none;
}


/* ── block: hero-slideshow ── */
.b-heroshow { position: relative; min-height: 100svh; display: flex; color: #fff; overflow: hidden; }
.b-heroshow__bg { position: absolute; inset: 0; }
/* The middle stop was 10% black, which is nothing: over a bright photograph the kicker and the lead
   were unreadable. A floor of ~34% across the frame, heavier at both ends, plus a left wash for the
   column the text actually occupies. */
.b-heroshow__scrim { position: absolute; inset: 0; background:
	linear-gradient(180deg, rgba(8,9,12,.62), rgba(8,9,12,.34) 38%, rgba(8,9,12,.38) 58%, rgba(8,9,12,.82)),
	linear-gradient(90deg, rgba(8,9,12,.5), rgba(8,9,12,.12) 46%, transparent 68%); }
.b-heroshow__in { position: relative; display: flex; flex-direction: column; justify-content: space-between; width: 100%; padding-block: calc(var(--bar) + clamp(30px,6vw,80px)) clamp(34px,5vw,70px); }
/* The chip must HUG its words: block + a translucent background drew a 438px smear across the
   hero for 135px of text. inline-block, own padding, no inherited underline. */
/* `section.` is load-bearing, not decoration. The look stylesheet is appended AFTER the blocks,
   and .look-editorial .b-kicker sets display:block at the same 0,2,0 specificity — so the plain
   .b-cover .b-kicker lost on source order and the chip stayed a 438px bar. The element selector
   takes it to 0,2,1, which wins wherever it sits in the file. */
section.b-heroshow .b-kicker { color: rgba(255,255,255,.86); background: rgba(255,255,255,.14); backdrop-filter: blur(3px); display: inline-block; width: auto; padding: 7px 12px;
	border-bottom: 0; border-radius: 3px; }
.b-heroshow h1 { color: #fff; font-size: clamp(3rem,8vw,7rem); line-height: .95; max-width: 14ch; }
.b-heroshow__foot { display: grid; gap: 18px; margin-top: auto; }
@media (min-width: 900px) { .b-heroshow__foot { grid-template-columns: 1fr auto; align-items: end; } }
.b-heroshow__lead { max-width: 44ch; margin: 0; color: color-mix(in srgb,#fff 84%,transparent); }
.b-status { grid-column: 1/-1; display: flex; align-items: center; gap: 10px; margin: 4px 0 0; font-size: .86rem; color: color-mix(in srgb,#fff 80%,transparent); }
.pulse { width: 8px; height: 8px; border-radius: 999px; background: #6ee7a8; box-shadow: 0 0 0 0 rgba(110,231,168,.7); animation: pulse 2.4s ease-out infinite; }
@keyframes pulse { 70% { box-shadow: 0 0 0 10px rgba(110,231,168,0) } 100% { box-shadow: 0 0 0 0 rgba(110,231,168,0) } }


/* ── block: list-priced ── */
.b-priced__cols { display: grid; gap: clamp(24px,3.4vw,48px); grid-template-columns: repeat(auto-fit,minmax(300px,1fr)); }
.b-priced__cols--short { max-width: 760px; grid-template-columns: 1fr; }
.b-priced__group h3 { margin-bottom: 8px; }
.b-priced__note { margin-top: -2px; font-size: .92rem; }
.b-priced__foot { margin-top: clamp(24px,3.4vw,44px); font-size: .93rem; }
.b-dur { display: inline-flex; align-items: center; gap: 6px; }
.b-dur::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }


/* ── block: appointment ── */
.b-appt__in { display: grid; gap: clamp(24px,3.4vw,50px); align-items: start; }
@media (min-width: 950px) { .b-appt__in { grid-template-columns: 1fr 1fr; } }
.b-appt__form { display: grid; gap: 14px 16px; grid-template-columns: 1fr 1fr; padding: clamp(20px,2.6vw,32px);
	border-radius: var(--radius); background: color-mix(in srgb,var(--accent) 10%,var(--ground)); }
@media (max-width: 520px) { .b-appt__form { grid-template-columns: 1fr; } }
.b-appt__points { margin: 20px 0 0; padding: 0; display: grid; gap: 10px; }
.b-appt__points li { list-style: none; padding-left: 22px; position: relative; color: var(--ink-soft); font-size: .96rem; }
.b-appt__points li::before { content: ''; position: absolute; left: 0; top: .55em; width: 8px; height: 8px; border-radius: 999px; background: var(--accent); }


/* ── block: story-split ── */
.b-story__in { display: grid; gap: clamp(30px,4.5vw,72px); align-items: center; }
@media (min-width: 900px) { .b-story__in:not(.sticky-split) { grid-template-columns: 1fr 1fr; } .b-story__in.sticky-split { align-items: start; } }
.b-story__figure { margin: 0; overflow: hidden; border-radius: var(--radius); }
.b-story__figure img { width: 100%; aspect-ratio: 4/5; object-fit: cover; }
.b-story__figure figcaption { margin-top: 10px; font-size: .82rem; color: var(--ink-soft); }
.b-pull { margin: 28px 0 0; padding: 18px 0 18px 26px; border-left: 3px solid color-mix(in srgb,var(--accent) 55%,transparent); font-family: var(--display); font-size: clamp(1.1rem,1.7vw,1.5rem); line-height: 1.3; }
.b-pull cite { display: block; margin-top: 10px; font-family: var(--body); font-size: .8rem; font-style: normal; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft); }
.b-link { display: inline-block; margin-top: 20px; font-weight: 500; text-decoration: none; border-bottom: 2px solid var(--accent); padding-bottom: 2px; }
.b-link:hover { opacity: .72; }
/* No photograph for this block: the media column is not rendered at all (see block.html), so the copy
   is the only child and must span both columns — otherwise it sits in the left 1fr with an empty half
   beside it, which is worse than the empty <img> this replaced. */
@media (min-width: 900px) {
  .b-story__in > .b-story__copy:only-child { grid-column: 1 / -1; }
}


/* ── block: stats ── */
/* A row of figures, not a section of content — the same shape as the facts strip, and it had the
   same fault: measured with four stats it held 149px inside a 423px section, 65% of it empty. This
   file was empty, so it took the full --section with nothing offsetting it. See --section-tight. */
.b-stats { padding-block: var(--section-tight); }


/* ── block: cta-band ── */
.b-cta { text-align: center; padding-block: var(--section-tight); }
.b-cta__in p { margin-inline: auto; }
.b-cta .b-actions { justify-content: center; margin-top: 26px; }
.b-cta--photo { position: relative; overflow: hidden; color: #fff; }
/* The scrim is a pseudo-element on the section, but the photo is a real child that comes after it in
   the DOM — and it carries a parallax transform, so it was painting straight over the scrim and the
   headline was white-on-gold. The three layers now say explicitly which is on top. */
.b-cta--photo::before { content: ''; position: absolute; inset: 0; z-index: 1; background:
	linear-gradient(to bottom, rgba(8,9,12,.74), rgba(8,9,12,.58) 45%, rgba(8,9,12,.78)); }
.b-cta__bg { position: absolute; inset: -12% 0; z-index: 0; }
.b-cta__bg img { width: 100%; height: 100%; object-fit: cover; }
.b-cta--photo .b-cta__in { position: relative; z-index: 2; }
.b-cta--photo { padding-block: clamp(56px,7vw,104px); }
.b-cta--photo h2 { color: #fff; }
.b-cta--photo p { color: color-mix(in srgb,#fff 82%,transparent); }
.b-cta--photo .btn--ghost { color: #fff; background: rgba(255,255,255,.1); backdrop-filter: blur(3px);
	box-shadow: inset 0 0 0 1px color-mix(in srgb,#fff 55%,transparent); }
.b-cta__in p { max-width: 54ch; }


/* ── block: gallery-mosaic ── */
.b-mosaic__grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(8px,1.2vw,16px); }
@media (min-width: 760px) { .b-mosaic__grid { grid-template-columns: repeat(4, 1fr); } }
.b-mosaic__cell { margin: 0; overflow: hidden; border-radius: calc(var(--radius) * .5); }
.b-mosaic__cell img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* A four-cell rhythm that TILES: [2x2][1][1][2x1] fills a 4-column, 2-row block exactly, so every
   group of four closes itself and the mosaic never ends on a hole. (The first version used a six-cell
   rhythm with `dense`, which left gaps at most item counts.) */
.b-mosaic__cell { grid-column: span 1; grid-row: span 1; aspect-ratio: 1; }
.b-mosaic__cell:nth-child(4n + 1) { grid-column: span 2; grid-row: span 2; aspect-ratio: 1; }
.b-mosaic__cell:nth-child(4n + 4) { grid-column: span 2; aspect-ratio: 2; }

/* The last group is rarely a full four. Two leftovers become a pair of half-width rows; three become
   a big plate beside two tall ones. Either way the final row is full. */
@media (min-width: 760px) {
	.b-mosaic__cell:nth-child(4n + 1):nth-last-child(2) { grid-row: span 1; aspect-ratio: 2; }
	.b-mosaic__cell:nth-child(4n + 1):nth-last-child(2) ~ .b-mosaic__cell:last-child { grid-column: span 2; aspect-ratio: 2; }
	.b-mosaic__cell:nth-child(4n + 2):nth-last-child(2),
	.b-mosaic__cell:nth-child(4n + 3):last-child { grid-row: span 2; aspect-ratio: 1 / 2; }
}

/* The handcrafted look turns every .b-shot into a rotated polaroid. Right for a loose grid, wrong in a
   tiled mosaic — rotated frames collide and leave white wedges between cells. It uses the `rotate`
   property rather than `transform`, so both have to be cleared. */
.b-mosaic__grid .b-shot { rotate: none !important; transform: none !important; padding: 0 !important;
	background: none !important; box-shadow: none !important; border: 0 !important; }


/* ── block: map-visit ── */
.b-visit__in { display: grid; gap: clamp(28px,4vw,60px); align-items: start; }
@media (min-width: 900px) { .b-visit__in { grid-template-columns: 1.05fr .95fr; } }
.b-visit__hours-head { margin-bottom: 14px; }
.b-visit__map { margin-top: clamp(24px,3.4vw,44px); }
.b-visit .btn { margin-top: 20px; }


/* ── look: minimal ── */
/* MINIMAL — near-monochrome, enormous space, slow. The restraint is the product. */
.look-minimal { --radius: 2px; --section: clamp(80px,11vw,190px); }
.look-minimal .b-kicker { display: block; margin: 0 0 24px; font-size: .68rem; letter-spacing: .28em; text-transform: uppercase; color: var(--accent-ink); }
.look-minimal h1 { font-size: clamp(2.6rem,5.4vw,4.6rem); line-height: 1.06; }
.look-minimal h1 em, .look-minimal h2 em { font-style: italic; color: inherit; }
.look-minimal .btn { border-radius: 0; font-size: .74rem; letter-spacing: .18em; text-transform: uppercase; padding: 16px 30px; }
.look-minimal .brand { font-size: .95rem; letter-spacing: .2em; text-transform: uppercase; }
.look-minimal .nav a { font-size: .8rem; letter-spacing: .14em; text-transform: uppercase; }
/* A look that draws a box around something owes it padding. This rule turned five things into bordered
   boxes and gave none of them any, so on the services list the group heading sat on the border. The
   forms bring their own padding from their blocks; the rest did not, because in the other looks they
   are not boxes at all. */
.look-minimal .b-card, .look-minimal .b-fact, .look-minimal .b-priced__group, .look-minimal .b-book__form, .look-minimal .b-appt__form, .look-minimal .b-resv__form { background: transparent; box-shadow: inset 0 0 0 1px var(--line); border-radius: 0; }
.look-minimal .b-priced__group { padding: clamp(20px, 2.2vw, 30px) clamp(20px, 2.2vw, 28px) clamp(10px, 1.4vw, 16px); }
.look-minimal .b-fact { padding: clamp(18px, 2vw, 26px) clamp(18px, 2vw, 24px); }
/* .b-card carries a media image to its edges and pads its own body, so it is left alone. */
.look-minimal .b-card__price { background: transparent; color: var(--accent); }
.look-minimal .b-card__media img { aspect-ratio: 1; }
.look-minimal .motion-on [data-reveal] { transition-duration: 1.1s; }
.look-minimal .b-marquee { background: transparent; color: var(--ink-soft); border-block: 1px solid var(--line); }
.look-minimal .b-shot img { aspect-ratio: 4/5; }


/* ── this site ── */
:root{--ink:#11161f;--ground:#f2f1ec;--accent:#cba14e;--on-accent:#11161f;--accent-ink:color-mix(in srgb,var(--accent) 55%,var(--ink));--display:'Playfair Display',Georgia,serif;--body:'Inter',system-ui,sans-serif;--display-weight:500;--display-tracking:-0.02em}