/*
 * Topia — Villa landing page.
 *
 * Written with CSS logical properties (margin-inline, padding-inline,
 * inset-inline) so one file serves Arabic (RTL) and Turkish/English (LTR)
 * with no mirrored copy. Direction comes from <html dir>, set by is_rtl().
 *
 * Typography is Arabic-first for the same reason: Arabic is the default
 * language at the site root, so the base leading and tracking are the values
 * Arabic needs, and :dir(ltr) opts Turkish/English back into the tighter
 * Latin metrics of the Figma frame. See the tokens block for the detail.
 *
 * Fonts: drop files into ../fonts/ and uncomment the @font-face blocks.
 * Until then the fallback stack renders correctly on every platform.
 */

/* ------------------------------------------------------------ breakpoints */
/*
 * The Figma source is ONE 1440px frame. There is no mobile design and there
 * never was, but most of this sector's traffic is mobile — so the ladder
 * below is a design decision taken here, not a translation of one taken in
 * Figma. Recorded so the next person knows which parts are the client's
 * design and which parts are ours.
 *
 *   base      one column throughout; hero image 4/3 with the name panel
 *   (<480)    stacked underneath it, not laid over it
 *   >= 480    gallery goes two-up
 *   >= 640    badges go two-up with the divider between them
 *   >= 768    specs go five-up (see that section for why five and not three)
 *   >= 1024   the desktop layout arrives as one piece: hero 16/10 with the
 *             panel overlaid, intro splits into copy + side image, gallery
 *             three-up
 *   >= 1280   the container reaches its 1200px cap; only type keeps moving,
 *             up to the Figma sizes at 1440
 *
 * Type does NOT step at these widths. It runs as two straight fluid ramps —
 * one from 360 to 1024, a second and much steeper one from 1024 to 1440
 * where the Figma sizes live — and the two meet exactly at 1024 so nothing
 * jumps. The tokens block carries the arithmetic.
 *
 * One rule when adding to this file: every layout media query uses a width
 * from the ladder above. A new ad-hoc value (720, 860, 620 — all of which
 * used to be in here) creates a range nobody has ever looked at.
 */

/* ------------------------------------------------------------------ fonts */
/*
@font-face {
	font-family: 'Inter';
	src: url('../fonts/InterVariable.woff2') format('woff2');
	font-weight: 100 900;
	font-display: swap;
	font-style: normal;
}
@font-face {
	font-family: 'IBM Plex Sans Arabic';
	src: url('../fonts/IBMPlexSansArabic-Regular.woff2') format('woff2');
	font-weight: 400;
	font-display: swap;
}
@font-face {
	font-family: 'IBM Plex Sans Arabic';
	src: url('../fonts/IBMPlexSansArabic-Bold.woff2') format('woff2');
	font-weight: 700;
	font-display: swap;
}
*/

/* ----------------------------------------------------------------- tokens */
.tv {
	--tv-green:  #244743;
	--tv-maroon: #6a1d29;
	--tv-cream:  #ffefe7;
	--tv-ink:    #050f27; /* PENDING CLIENT DECISION: UI-kit leftover, may become --tv-green */
	--tv-white:  #ffffff;
	--tv-muted:  rgba(5, 15, 39, 0.72);
	/* Grid rules inside the green panels — see the specs section. */
	--tv-rule:   rgba(255, 255, 255, 0.25);

	/*
	 * Radii scale with the page. A 36px corner on a 328px-wide card at 360px
	 * is a different shape from a 36px corner on a 1200px one — it reads as a
	 * pill, not as the soft rectangle in the Figma frame. The maxima are the
	 * design values, reached at ~1030px and above.
	 */
	--tv-radius-lg: clamp(20px, 3.5vw, 36px);
	--tv-radius-md: clamp(16px, 2.5vw, 26px);

	--tv-font-latin:  'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
	--tv-font-arabic: 'IBM Plex Sans Arabic', 'Noto Naskh Arabic', 'Segoe UI', Tahoma, sans-serif;

	/*
	 * Leading and tracking.
	 *
	 * The Figma frame is Latin-only, so its metrics — 1.02 on the villa name,
	 * 1.08 on the headline — assume shallow descenders. Arabic, the site's
	 * default language, does not have shallow descenders: ج ح خ ع غ م ن ي
	 * drop well below the baseline and carry dots under them. At 1.02 the
	 * line box is smaller than the glyphs it holds, so lines collide and,
	 * inside .tv-hero's overflow: hidden, the descenders are cut off.
	 *
	 * So the base values here are the Arabic-safe ones, and :dir(ltr) opts
	 * back into the tight Figma metrics. That order is deliberate: should
	 * :dir() ever fail to match, the page falls back to leading that is
	 * merely roomy — never to leading that clips glyphs.
	 *
	 * Tracking follows the same rule. Negative letter-spacing drags Arabic
	 * letterforms into their neighbours and damages the cursive joins, so
	 * the Figma tracking is Latin-only too.
	 *
	 * The floor for these values is the font box of the fallback stack, since
	 * the @font-face blocks above are still commented out: Tahoma 1.21,
	 * Geeza Pro 1.23, Segoe UI 1.33 (ascent 2210 + descent 514 over a 2048 em).
	 * Nothing below is under 1.35, so no line box is smaller than the glyphs
	 * it carries on any of the three platforms.
	 */
	--tv-lh-display: 1.35; /* hero villa name  — Figma 1.02 */
	--tv-lh-heading: 1.4;  /* intro headline   — Figma 1.08 */
	--tv-lh-lead:    1.5;  /* kicker, location — Figma 1.2  */
	--tv-lh-item:    1.5;  /* badge rows       — Figma 1.15 */
	--tv-lh-button:  1.4;  /* CTA label        — Figma 1.1  */
	--tv-lh-label:   1.6;  /* spec labels      — Figma 1.35 */
	--tv-lh-copy:    1.9;  /* body paragraphs  — Figma 1.75 */

	--tv-track-display: 0;
	--tv-track-heading: 0;

	/*
	 * Type scale — the mobile ramp, 360px to 1024px.
	 *
	 * Every one of these used to be a single clamp() stretched from a phone
	 * all the way to 1440. That is one straight line through two very
	 * different design problems, and the middle of it was nonsense: the villa
	 * name computed to 52.6px and the intro headline to 40px at a 667px
	 * viewport, sizes that only make sense with a 1200px canvas behind them.
	 *
	 * So each token is now two ramps. This block is the first: it starts at a
	 * size chosen for a 360px phone and climbs gently, reaching at 1024 the
	 * exact value the desktop block below starts from. Because they meet,
	 * nothing jumps at the breakpoint even though the slope trebles there.
	 *
	 * Read the comment on each line as "value at 360px → value at 1024px".
	 * The min and max of each clamp are those same two numbers, so the ramp
	 * is flat outside its range and no token can run away.
	 */
	--tv-fs-display:  clamp(2rem,      1.051rem + 4.217vw, 3.75rem);  /* 32 → 60 */
	--tv-fs-heading:  clamp(1.625rem,  1.015rem + 2.711vw, 2.75rem);  /* 26 → 44 */
	--tv-fs-kicker:   clamp(0.875rem,  0.604rem + 1.205vw, 1.375rem); /* 14 → 22 */
	--tv-fs-location: clamp(0.875rem,  0.672rem + 0.904vw, 1.25rem);  /* 14 → 20 */
	--tv-fs-item:     clamp(1rem,      0.729rem + 1.205vw, 1.5rem);   /* 16 → 24 */
	--tv-fs-button:   clamp(1.0625rem, 0.826rem + 1.054vw, 1.5rem);   /* 17 → 24 */
	--tv-fs-label:    clamp(0.875rem,  0.739rem + 0.602vw, 1.125rem); /* 14 → 18 */

	/*
	 * Body copy is the exception: it is already at a comfortable reading size
	 * on a phone and its design size is only 20px, so one gentle ramp covers
	 * the whole range. 16px is the floor on purpose — never smaller.
	 */
	--tv-fs-copy: clamp(1rem, 0.95rem + 0.25vw, 1.25rem); /* 16 → 20 */

	/*
	 * Hero crop. A phone is portrait, so a 16/10 letterbox of a villa is a
	 * strip of roof; the crop deepens as the viewport narrows. Declared as a
	 * token because both .tv-hero__media and the image inside it need it —
	 * see the hero section for why the image needs its own copy.
	 */
	--tv-hero-ratio: 4 / 3;

	--tv-max: 1200px;
	--tv-gutter: clamp(16px, 4vw, 40px);
	--tv-gap: clamp(24px, 4vw, 48px);

	box-sizing: border-box;
	display: block;
	max-inline-size: calc(var(--tv-max) + var(--tv-gutter) * 2);
	margin-inline: auto;
	padding-inline: var(--tv-gutter);
	padding-block: clamp(24px, 5vw, 80px);
	font-family: var(--tv-font-latin);
	/* Inherited default, so anything not styled below still gets Arabic-safe leading. */
	line-height: var(--tv-lh-lead);
	color: var(--tv-ink);
	-webkit-font-smoothing: antialiased;
}

/*
 * Type scale — the desktop ramp, 1024px to 1440px.
 *
 * These are the Figma sizes and the steep climb up to them. Each clamp's
 * minimum is the value the mobile ramp above hands over at 1024, and each
 * maximum is the size measured in the 1440 frame, so above 1440 the page
 * stops growing and simply centres.
 *
 * The negative rem intercepts are not typos: a line that gains 8.65px of
 * size per 100px of viewport, and passes through 96px at 1440, has to start
 * below zero. The clamp minimum is what actually holds at 1024.
 */
@media (min-width: 1024px) {
	.tv {
		--tv-fs-display:  clamp(3.75rem,  -1.788rem + 8.654vw, 6rem);     /* 60 → 96 */
		--tv-fs-heading:  clamp(2.75rem,  -2.173rem + 7.692vw, 4.75rem);  /* 44 → 76 */
		--tv-fs-kicker:   clamp(1.375rem, -0.779rem + 3.365vw, 2.25rem);  /* 22 → 36 */
		--tv-fs-location: clamp(1.25rem,  -0.596rem + 2.885vw, 2rem);     /* 20 → 32 */
		--tv-fs-item:     clamp(1.5rem,   -2.193rem + 5.769vw, 3rem);     /* 24 → 48 */
		--tv-fs-button:   clamp(1.5rem,   -2.193rem + 5.769vw, 3rem);     /* 24 → 48 */
		--tv-fs-label:    clamp(1.125rem, -0.106rem + 1.923vw, 1.625rem); /* 18 → 26 */
	}
}

/* Latin (tr / en) restores the Figma metrics. Arabic keeps the base values. */
.tv:dir(ltr) {
	--tv-lh-display: 1.02;
	--tv-lh-heading: 1.08;
	--tv-lh-lead:    1.2;
	--tv-lh-item:    1.15;
	--tv-lh-button:  1.1;
	--tv-lh-label:   1.35;
	--tv-lh-copy:    1.75;

	--tv-track-display: -0.01em;
	--tv-track-heading: -0.015em;
}

html[lang^='ar'] .tv,
.tv:dir(rtl) {
	font-family: var(--tv-font-arabic);
}

.tv *,
.tv *::before,
.tv *::after {
	box-sizing: inherit;
}

/*
 * The default for every image on the page: fluid width, intrinsic height.
 *
 * CAREFUL — this selector is (0,1,1). A component rule written as
 * `.tv-something__img` is (0,1,0) and LOSES to it, silently, which is
 * exactly how the hero spent three versions ignoring its own block-size.
 * Any component that needs to override block-size here must be written
 * `.tv .tv-something__img` — (0,2,0) — or higher.
 */
.tv img {
	max-inline-size: 100%;
	block-size: auto;
	display: block;
}

/*
 * Icons are sized on ONE axis. Never both.
 *
 * The Figma exports are nowhere near a common shape: waves is 121x67 and
 * shield is 80x98 — 1.81:1 down to 0.82:1, a 2.2x spread. Forcing a square
 * box on that set would not actually distort them, because SVG's default
 * preserveAspectRatio is `meet` and they would letterbox instead. It would do
 * something subtler and worse: a wide icon would paint at 55% of the box
 * height, floating in dead space, reading as a smaller and lighter glyph than
 * the square one sitting next to it.
 *
 * So block-size is set and inline-size is left alone. Inline SVG that has a
 * viewBox and no width/height attributes carries an intrinsic aspect ratio,
 * so `inline-size: auto` resolves straight out of the viewBox: every icon gets
 * its true proportions, nothing is squashed, nothing is cropped, and no icon
 * needs to know about any other.
 *
 * Height is the fixed axis rather than width because these sit in rows of
 * text, and matching x-height is what makes a mixed set read as one set.
 *
 * Each context sets --tv-icon-h; the fallback keeps a stray icon sane. Note
 * that the em in those values resolves against the icon's OWN font-size, which
 * it inherits from the row it sits in — that is the same number in every case
 * here, because no wrapper span changes font-size.
 */
.tv .tv-icon {
	display: block;
	block-size: var(--tv-icon-h, 1.2em);
	inline-size: auto;
	flex: none;
}

.tv :is(h1, h2, p, ul, li, figure) {
	margin: 0;
}

.tv ul {
	list-style: none;
	padding: 0;
}

/* ------------------------------------------------------------- containment */
/*
 * This page loads 37 stylesheets it does not own (Houzez, Elementor,
 * RevSlider, Click-to-Chat). They cannot see the tv- classes, so whatever
 * they leak in arrives through bare element selectors — one of them
 * underlines .tv-hero__kicker. The properties below are the ones that break
 * Arabic when they leak: forced tracking pulls cursive joins apart, a forced
 * Latin font-family drops Arabic onto a fallback with different metrics, and
 * a physical text-align or direction lands the text on the wrong side.
 *
 * Specificity rather than !important: (0,1,1) beats a bare `p` and ties
 * `.something p`, and this stylesheet enqueues at priority 20, so it comes
 * after the theme in <head> and wins ties. Every component rule below is
 * written with a `.tv ` prefix — (0,2,0) — so it stays above this block.
 * Keep that prefix when adding rules, or this reset will overrule them.
 */
.tv :is(h1, h2, p, ul, li, a, span, figure) {
	font-family: inherit;
	font-style: normal;
	direction: inherit;
	letter-spacing: normal;
	word-spacing: normal;
	text-align: start;
	text-decoration: none;
	text-indent: 0;
	text-shadow: none;
	text-transform: none;
}

/*
 * Leading is set on the block that owns it and inherited by the text inside,
 * so a stray `p { line-height: 1.2 }` cannot re-tighten an Arabic paragraph.
 * Headings are absent here on purpose — they each set their own below.
 */
.tv :is(p, span, li) {
	line-height: inherit;
}

/* Shown until real images are uploaded. */
.tv-placeholder {
	display: block;
	inline-size: 100%;
	block-size: 100%;
	min-block-size: 180px;
	background:
		repeating-linear-gradient(
			45deg,
			rgba(36, 71, 67, 0.08) 0 12px,
			rgba(36, 71, 67, 0.16) 12px 24px
		);
}

.tv-visually-hidden {
	position: absolute;
	inline-size: 1px;
	block-size: 1px;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

.tv-skip {
	position: absolute;
	inset-block-start: -100px;
	inset-inline-start: 16px;
	z-index: 100;
	padding: 12px 20px;
	background: var(--tv-green);
	color: var(--tv-white);
	border-radius: 8px;
	text-decoration: none;
}

.tv-skip:focus {
	inset-block-start: 16px;
}

/* ------------------------------------------------------------------- hero */
.tv-hero {
	position: relative;
	border-radius: var(--tv-radius-lg);
	overflow: hidden;
	isolation: isolate;
	background: var(--tv-cream);
}

.tv-hero__media {
	aspect-ratio: var(--tv-hero-ratio);
}

/*
 * The image carries the crop ratio itself, not just the box around it.
 *
 * Two separate things are going on here and both were broken:
 *
 * 1. The `.tv ` prefix. Without it this selector is (0,1,0) and loses to
 *    `.tv img { block-size: auto }` above, so block-size: 100% never applied
 *    and object-fit: cover had nothing to fit — the hero simply rendered the
 *    file at its own proportions. Miralay's hero happens to be portrait, so
 *    the page looked plausible and the bug stayed hidden. A landscape hero
 *    on V02 would have reshaped the whole page on its own.
 *
 * 2. aspect-ratio on the image as well as on the wrapper. block-size: 100%
 *    is a percentage, and a percentage needs a definite height on the parent
 *    to resolve against. .tv-hero__media's height is derived from its own
 *    aspect-ratio, which browsers do treat as definite — but that is the one
 *    link in the chain that depends on a parent, and the panel is absolutely
 *    positioned against this box. Giving the image the ratio directly means
 *    the crop holds even if the percentage ever fails to resolve.
 *
 * Net effect: the hero is the ratio the design asks for, at every width, for
 * every villa, whatever shape the uploaded file happens to be.
 */
.tv .tv-hero__img,
.tv .tv-hero__media .tv-placeholder {
	inline-size: 100%;
	block-size: 100%;
	aspect-ratio: var(--tv-hero-ratio);
	object-fit: cover;
}

/*
 * Base state is the stacked one: the panel sits under the image, full width,
 * solid cream. It only lifts on top of the image at >= 1024 — see below.
 */
.tv-hero__panel {
	inline-size: 100%;
	padding: clamp(20px, 4vw, 48px);
	background: var(--tv-cream);
}

.tv-hero__logo-img {
	inline-size: clamp(120px, 18vw, 200px);
	block-size: auto;
	margin-block-end: clamp(12px, 2vw, 24px);
}

.tv .tv-hero__kicker {
	font-size: var(--tv-fs-kicker);
	line-height: var(--tv-lh-lead);
	color: var(--tv-ink);
}

.tv .tv-hero__name {
	font-size: var(--tv-fs-display);
	line-height: var(--tv-lh-display);
	font-weight: 700;
	letter-spacing: var(--tv-track-display);
	color: var(--tv-ink);

	/*
	 * A villa name is a single word in every language — ميرالاي, MIRALAY — and
	 * must never be split. overflow-wrap: normal was already the computed
	 * value and did not hold, so the break is coming from word-break or
	 * hyphens set upstream; both are answered here. keep-all suppresses
	 * implicit break opportunities between letters in every script, not only
	 * CJK, which is exactly what a one-word display line needs.
	 */
	overflow-wrap: normal;
	word-break: keep-all;
	-webkit-hyphens: none;
	hyphens: none;
}

.tv .tv-hero__location {
	--tv-icon-h: 1.1em;

	display: flex;
	align-items: center;
	gap: 8px;
	margin-block-start: clamp(8px, 1.5vw, 16px);
	font-size: var(--tv-fs-location);
	line-height: var(--tv-lh-lead);
	color: var(--tv-muted);
}

@media (min-width: 480px) {
	.tv {
		--tv-hero-ratio: 3 / 2;
	}
}

/*
 * The overlaid panel — the Figma composition — starts here and nowhere
 * earlier. Below 1024 it stays stacked under the image; everything in this
 * block is desktop-only.
 *
 * The geometry is measured off the source frame, not estimated. Rectangle 789
 * (node 405:126) is x=120, y=80, w=642, h=737. Its x of 120 matters as much as
 * its width: the 1440 frame has 120px margins, so the content column runs
 * 120..1320 and the panel starts flush against the hero's leading edge. That
 * fixes the hero at 1200 wide, and makes the panel 642/1200 = 53.5% of it.
 *
 * A percentage rather than a clamped pixel width, so the proportion is what
 * survives: 53.5% of the hero at every desktop width, 642px once the container
 * reaches its 1200px cap, and 505px against the 944px hero at 1024.
 *
 * That is wider than the 45% this used to resolve to, which sounds like more
 * of the photograph hidden — but the long fade below means only the first half
 * of the panel is opaque. Solid cream now covers 51% of 53.5%, about 27% of
 * the hero. The old narrower panel was opaque across 72% of itself, roughly
 * 33% of the hero. The design's panel is wider AND shows more villa.
 *
 * Full height (inset-block: 0) because the rectangle is 737 tall in a hero
 * that is itself 737 tall — it meets the hero's top, bottom and leading edges.
 * No corner radius: the three met edges are clipped by .tv-hero's own
 * overflow, and the fourth dissolves into the gradient.
 */
@media (min-width: 1024px) {
	.tv {
		--tv-hero-ratio: 16 / 10;
	}

	/*
	 * Vertical placement, measured off the frame.
	 *
	 * Every offset below is a fraction of the panel's own height (y=80, h=737
	 * in the source):
	 *
	 *   Rectangle 788  logo      y=149 h=82   ->   9.4%, band ends 20.5%
	 *   404:123        "Villa"   y=321        ->  32.7%
	 *   404:121        name      y=358 h=103  ->  37.7%, ends 51.7%
	 *   405:128        location  y=470 h=33   ->  52.9%, ends 57.4%
	 *
	 * The text block therefore finishes a little past halfway and the entire
	 * lower ~43% of the panel is deliberately empty. That emptiness is the
	 * composition — it is what stops the panel reading as a filled card.
	 *
	 * WHY GRID ROWS AND NOT MARGINS. A percentage margin or padding resolves
	 * against the containing block's INLINE size, in both axes — margin-block
	 * included. `margin-block-start: 12%` on the kicker would have been 12% of
	 * the panel's width, and would drift the moment the panel's proportions
	 * changed. Grid row tracks are the exception: a percentage row resolves
	 * against the container's block size, which is definite here because the
	 * panel is pinned with inset-block: 0. So the rows carry the geometry.
	 *
	 * Only the first two tracks are fixed. Row 1 is the space above the logo
	 * and row 2 is the logo's band, and because they sum to 32.7% the kicker
	 * begins exactly on its measured mark no matter how tall the logo file
	 * turns out to be. The three text rows are auto, so they take the height
	 * their own type needs, and 1fr eats the remainder as the empty lower half.
	 *
	 * padding-block goes to zero here: percentages resolve against the content
	 * box, so leaving the 48px block padding in place would push every mark
	 * down by 6.4% of the panel. The inline padding stays — that is the text
	 * inset, and it is unrelated.
	 */
	.tv-hero__panel {
		position: absolute;
		inset-block: 0;
		inset-inline-start: 0;
		inline-size: 53.5%; /* 642 / 1200 in the source frame */
		padding-block: 0;
		display: grid;
		grid-template-rows:
			9.4%  /* clear space above the logo            */
			23.3% /* logo band — 9.4 + 23.3 lands on 32.7% */
			auto  /* kicker                                */
			auto  /* name                                  */
			auto  /* location                              */
			1fr;  /* the empty lower half                  */
	}

	.tv .tv-hero__logo {
		grid-row: 2;
		align-self: start;
	}

	.tv .tv-hero__kicker {
		grid-row: 3;
	}

	.tv .tv-hero__name {
		grid-row: 4;
	}

	.tv .tv-hero__location {
		grid-row: 5;
	}

	/* The grid owns the spacing under the logo now. */
	.tv-hero__logo-img {
		margin-block-end: 0;
	}

	/*
	 * The fade from the panel into the photograph.
	 *
	 * This used to be written `linear-gradient(to inline-end, ...)` with an
	 * @supports fallback to flat cream. Measured, not assumed:
	 * CSS.supports('background', 'linear-gradient(to inline-end, red, blue)')
	 * returns false in current Chrome, and the panel's computed
	 * background-image is `none`. `to inline-end` is CSS Images 4 and no
	 * shipping engine implements it — so the declaration has always been
	 * invalid, the fallback has always won, and this fade has never once
	 * rendered. The desktop hero has been showing a hard cream edge.
	 *
	 * There is no logical keyword that works today, so direction is selected
	 * with :dir() — the same mechanism this file already uses to give Latin
	 * its own leading, not a [dir="rtl"] override block. The rule CLAUDE.md
	 * sets is that a physical value must not be used where a logical one
	 * exists; here none does, in any engine.
	 *
	 * Base stays flat cream so that an engine without :dir() lands on exactly
	 * today's appearance rather than on a fade running the wrong way.
	 *
	 * RTL puts the panel on the right, so it fades leftward into the image;
	 * LTR is the mirror. Revisit if `to inline-end` ever ships.
	 *
	 * The 51% stop is measured, not chosen. Rendering Rectangle 789 on its own
	 * and reading the pixels across its middle row: the fill holds #ffefe7
	 * exactly until x=330 of 642 — 51.4% — and then ramps to nothing at the
	 * trailing edge. Recovering the alpha from that ramp (the render is
	 * flattened onto white, so it arrives as colour rather than transparency)
	 * gives 1.00 / 0.81 / 0.63 / 0.44 / 0.25 / 0.00 at 50..100% — a straight
	 * line, which is why two stops describe it and no easing is needed.
	 *
	 * So the fade occupies the whole back half of the panel. At 72% it ran over
	 * 28% and read as an edge with a soft lip rather than as the image emerging
	 * through the cream.
	 */
	.tv-hero__panel:dir(rtl) {
		background: linear-gradient(
			to left,
			var(--tv-cream) 0%,
			var(--tv-cream) 51%,
			rgba(255, 239, 231, 0) 100%
		);
	}

	.tv-hero__panel:dir(ltr) {
		background: linear-gradient(
			to right,
			var(--tv-cream) 0%,
			var(--tv-cream) 51%,
			rgba(255, 239, 231, 0) 100%
		);
	}
}

/* ----------------------------------------------------------------- badges */
.tv-badges {
	margin-block-start: var(--tv-gap);
	background: var(--tv-green);
	color: var(--tv-white);
	border-radius: var(--tv-radius-lg);
	padding: clamp(20px, 3vw, 36px) clamp(20px, 4vw, 56px);
}

.tv-badges__list {
	display: grid;
	grid-template-columns: 1fr;
	gap: clamp(16px, 3vw, 32px);
}

/*
 * Icon height in em rather than vw so it keeps the Figma proportion — a 64px
 * icon against 48px text — at every step of the type ramp instead of drifting
 * out of scale in the middle of it.
 */
.tv .tv-badges__item {
	--tv-icon-h: 1.33em;

	display: flex;
	align-items: center;
	gap: clamp(12px, 2vw, 24px);
	font-size: var(--tv-fs-item);
	line-height: var(--tv-lh-item);
}

@media (min-width: 640px) {
	.tv-badges__list {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		align-items: center;
	}

	.tv-badges__item + .tv-badges__item {
		border-inline-start: 1px solid rgba(255, 255, 255, 0.35);
		padding-inline-start: clamp(16px, 3vw, 40px);
	}
}

/*
 * Badge count is villa data, not a stylesheet constant. The rule above sets
 * two tracks — Miralay's count — so a third badge wraps to a second row at
 * every width. :has() scopes the three-track layout to villas that actually
 * have a third badge, leaving two-badge villas byte-identical.
 */
@media (min-width: 640px) {
	.tv-badges__list:has(> :nth-child(3)) {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

/* -------------------------------------------------------------------- CTA */
.tv-cta-wrap {
	margin-block-start: var(--tv-gap);
	display: flex;
	justify-content: center;
}

.tv-cta-wrap--last {
	margin-block-start: clamp(32px, 6vw, 72px);
}

.tv .tv-cta {
	/* em for the same reason as the badge icon: 56px against 48px text in Figma. */
	--tv-icon-h: 1.2em;

	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: clamp(10px, 1.5vw, 20px);
	inline-size: min(100%, 716px);
	/*
	 * The 14px block-padding floor is not cosmetic: with the 17px label and
	 * its 1.4 leading it puts the button at ~52px tall on a phone, clear of
	 * the 44px minimum touch target. Do not lower it.
	 */
	padding: clamp(14px, 2.2vw, 30px) clamp(20px, 3vw, 40px);
	background: var(--tv-maroon);
	color: var(--tv-white);
	border-radius: var(--tv-radius-md);
	font-size: var(--tv-fs-button);
	font-weight: 500;
	line-height: var(--tv-lh-button);
	text-align: center;
	text-decoration: none;
	transition: background-color 0.18s ease, transform 0.18s ease;
}

.tv .tv-cta:hover,
.tv .tv-cta:focus-visible {
	background: #57161f;
	color: var(--tv-white);
	transform: translateY(-2px);
}

.tv .tv-cta:focus-visible {
	outline: 3px solid var(--tv-green);
	outline-offset: 3px;
}

/* Must stay at or above the :hover rule's specificity, or a press while
   hovering keeps the lifted transform. */
.tv .tv-cta:active {
	transform: translateY(0);
}

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

	.tv .tv-cta:hover,
	.tv .tv-cta:focus-visible {
		transform: none;
	}
}

/* ------------------------------------------------------------------ intro */
/*
 * The headline, the body copy and the side image are one region sharing one
 * grid: headline and image side by side on the top row, paragraphs across the
 * full width underneath.
 *
 * Measured off the frame, all three from the inventory (1440 frame, content
 * column x=120..1320, so 1200 wide):
 *
 *   Frame 7   headline    x=120  y=1325  w=721   h=432
 *   Rect 790  image       x=910  y=1314  w=410   h=388
 *   407:737   paragraphs  x=120  y=1757  w=1200  h=335
 *
 * Which gives the whole layout: 721 + 69 + 410 = 1200 across, so the columns
 * are 60.08% and 34.17% with a 5.75% gutter; the image's right edge at 1320 is
 * the content column's right edge; and the paragraph block's width of 1200 is
 * the full container, not a column — it spans both.
 *
 * The image is 388 tall against a 432 headline and starts 11px above it, so it
 * is top-aligned and 44px short of the row. It is NOT stretched. An earlier
 * version of this file spanned the image down both rows and stretched it to
 * kill the space beside the copy; that inverted the design — it made the
 * picture a column-height slab and pushed the paragraphs into a narrow column
 * they were never in. The space is handled by alignment now, which is what the
 * frame does.
 *
 * Below 1024 the grid collapses to one column and the three parts stack in
 * source order: headline, image, paragraphs.
 */
.tv-intro {
	margin-block-start: clamp(40px, 7vw, 96px);
	display: grid;
	gap: var(--tv-gap);
	align-items: start;
}

/*
 * Spacing inside this region comes from the grid gap alone. The shared reset
 * above zeroes margins on the elements the theme usually leaks onto, but the
 * copy block and the media box are divs, which that list does not cover — and
 * a grid item's margin cannot collapse away the way a block's could before
 * these three became grid items.
 */
.tv .tv-intro > * {
	margin: 0;
}

.tv .tv-intro__headline {
	font-size: var(--tv-fs-heading);
	line-height: var(--tv-lh-heading);
	font-weight: 700;
	letter-spacing: var(--tv-track-heading);
	color: var(--tv-ink);
	text-wrap: balance;
}

.tv-intro__img,
.tv-intro__media .tv-placeholder {
	inline-size: 100%;
	border-radius: var(--tv-radius-md);
	object-fit: cover;
	/* Rectangle 790 is 410x388. The old 41/39 was a near miss at 1.0513. */
	aspect-ratio: 410 / 388;
}

@media (min-width: 1024px) {
	.tv-intro {
		/*
		 * The design's own numbers as the ratio. fr distributes what is left
		 * after the gutter, so 721fr : 410fr across a 94.25% remainder lands on
		 * 60.08% and 34.17% — the measured column widths, exactly, at any
		 * container size. minmax(0, ...) so a long unbroken word cannot force a
		 * track wider than its share.
		 */
		grid-template-columns: minmax(0, 721fr) minmax(0, 410fr);
		grid-template-rows: auto auto;
		column-gap: 5.75%; /* 69 / 1200 */
		row-gap: clamp(24px, 3vw, 48px);
	}

	.tv .tv-intro__headline {
		grid-column: 1;
		grid-row: 1;
	}

	/*
	 * Top row, second column, its own height, bottom edge flush with the
	 * headline's.
	 *
	 * The two columns of this row hold things that size by completely
	 * unrelated rules. The image is continuous — its height is the column
	 * width over 410/388, so it slides smoothly as the container grows. The
	 * headline is discrete: it jumps a whole line box at a time, and the line
	 * box is 1.4 in Arabic against 1.08 in Latin. Measured across 1024..1600,
	 * the headline runs 185px to 426px in Arabic and 143px to 328px in Latin,
	 * while the image runs 305px to 388px. Neither column is reliably the
	 * taller one: below about 1340 the image is, above it the Arabic headline
	 * is, and the crossover moves with the language.
	 *
	 * So aligning to the row's start leaves a ragged bottom edge whose size and
	 * even direction depend on the viewport and the language. Aligning to the
	 * end pins the one edge that matters compositionally: whichever column is
	 * taller, both finish on the same line, and the paragraph band below starts
	 * against a single straight boundary. That is what makes the two rows read
	 * as one block rather than as a picture hung above a gap.
	 *
	 * Not stretch. 1.4.0 spanned this image down both rows and stretched it,
	 * which bought the same flush edge by cropping the photograph to whatever
	 * height the text happened to need, and cost the design's 410/388. The
	 * ratio is fixed; only the alignment moves.
	 */
	.tv-intro__media {
		grid-column: 2;
		grid-row: 1;
		align-self: end;
	}

	/*
	 * The paragraphs are a full-width band, not a column: 1200 wide in the
	 * frame, the same as the container. The 68ch cap still applies inside it
	 * (see .tv-body) — the block spans the grid, the measure stays readable.
	 */
	.tv .tv-body {
		grid-column: 1 / -1;
		grid-row: 2;
	}
}

/* ------------------------------------------------------------------- body */
/* Placed in the .tv-intro grid — see the >= 1024px block above for its column. */
.tv .tv-body {
	display: grid;
	gap: 1em;
	/* Caps the line at a readable measure — the Figma file ran ~150 characters. */
	max-inline-size: 68ch;
	font-size: var(--tv-fs-copy);
	line-height: var(--tv-lh-copy);
	color: var(--tv-muted);
}

/* ------------------------------------------------------------------ specs */
.tv-specs {
	margin-block-start: clamp(40px, 6vw, 88px);
	background: var(--tv-green);
	color: var(--tv-white);
	border-radius: var(--tv-radius-lg);
	padding: clamp(24px, 4vw, 48px) clamp(16px, 3vw, 40px);
}

/*
 * Two columns, then five. Nothing in between, and that is deliberate.
 *
 * Miralay has ten specs, and ten divides evenly by 1, 2, 5 and 10 only. Any
 * other column count leaves a short final row, and because each tile centres
 * its own icon and label, a lone orphan tile lands in the middle of an empty
 * row and reads as a rendering fault rather than as a list that ran out. The
 * three-column step this file used to have produced exactly that: 3 + 3 + 3 + 1.
 *
 * NOTE for V02 and after: if a villa ships a spec count that is not a
 * multiple of five, revisit this — the divisibility, not the width, is what
 * picks these numbers.
 */
.tv-specs__grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	/*
	 * No gap, deliberately. The rules below are borders on the cells, and a
	 * gap would cut every line into one segment per cell with a hole at each
	 * crossing. The breathing room the gap used to provide is now padding
	 * INSIDE each cell, so neighbouring borders meet and the lines run
	 * unbroken from edge to edge.
	 */
	gap: 0;
}

/*
 * The rule grid.
 *
 * Every cell draws on its two start edges — one inline, one block — and the
 * cells that sit against the panel's own edges paint theirs transparent. That
 * is the whole mechanism, and it is why this survives the column count
 * changing underneath it: at five columns it produces four verticals and one
 * horizontal, at two columns one vertical and four horizontals, and in both
 * cases nothing is left stranded on an outer edge.
 *
 * Transparent rather than `border: 0` so that every cell keeps an identical
 * box. Removing the border outright would leave the first column and first row
 * 1px wider in content area than the rest, and centred content would sit half
 * a pixel off from its neighbours.
 */
.tv .tv-specs__item {
	--tv-icon-h: clamp(32px, 3.5vw, 52px);

	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	gap: 12px;
	/* Half the old grid gap per side, so cell-to-cell spacing is unchanged. */
	padding: clamp(10px, 1.5vw, 20px) clamp(6px, 1vw, 12px);
	border-inline-start: 1px solid var(--tv-rule);
	border-block-start: 1px solid var(--tv-rule);
}

/*
 * Which cells are on an outer edge depends on the column count, so these come
 * as one mutually exclusive pair rather than a single rule. They are the only
 * max-width query in the file; keep the boundary identical to the grid's own
 * 768 step above, or a column will lose its rule or grow a stray one.
 */
@media (max-width: 767px) {
	.tv .tv-specs__item:nth-child(2n + 1) {
		border-inline-start-color: transparent;
	}

	.tv .tv-specs__item:nth-child(-n + 2) {
		border-block-start-color: transparent;
	}
}

@media (min-width: 768px) {
	.tv .tv-specs__item:nth-child(5n + 1) {
		border-inline-start-color: transparent;
	}

	.tv .tv-specs__item:nth-child(-n + 5) {
		border-block-start-color: transparent;
	}
}

.tv .tv-specs__label {
	font-size: var(--tv-fs-label);
	line-height: var(--tv-lh-label);
	text-align: center;
}

@media (min-width: 768px) {
	.tv-specs__grid {
		grid-template-columns: repeat(5, minmax(0, 1fr));
	}
}

@media (min-width: 1024px) {
	/*
	 * The measure cap belongs to the wide layout only. Below 1024 the tile is
	 * already narrower than 14ch worth of label, so the cap adds nothing and
	 * costs a line break: "Yüksek kaliteli ısı yalıtımı" wraps to three lines
	 * inside a cap it does not need instead of two inside the tile.
	 */
	.tv .tv-specs__label {
		max-inline-size: 14ch;
	}
}

/* ---------------------------------------------------------------- gallery */
/*
 * These are the only interior shots on the page, so they carry the whole
 * "what is it actually like inside" job. Three columns started at 620px,
 * which put each shot at 194px on a phone-sized viewport — a thumbnail, not
 * something you can read a kitchen from. One column, then two from 480, then
 * three only once there is a desktop's worth of room: at 768 that is a 343px
 * shot instead of a 222px one.
 */
.tv-gallery {
	margin-block-start: clamp(32px, 5vw, 64px);
}

.tv-gallery__grid {
	display: grid;
	grid-template-columns: 1fr;
	gap: clamp(16px, 2.5vw, 32px);
}

.tv-gallery__item {
	margin: 0;
}

.tv-gallery__item img,
.tv-gallery__item .tv-placeholder {
	inline-size: 100%;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	border-radius: var(--tv-radius-lg);
}

@media (min-width: 480px) {
	.tv-gallery__grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (min-width: 1024px) {
	.tv-gallery__grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}
}

/* --------------------------------------------- suppress duplicate WA bubble */
/*
 * Click-to-Chat is set to global_display: show, which would add a third
 * WhatsApp entry point to a page that already has two. The cleaner long-term
 * fix is the plugin's own per-page exclusion; this is the belt-and-braces one.
 */
body.topia-lp .ht-ctc,
body.topia-lp #ht-ctc-chat {
	display: none !important;
}

/* --------------------------------------------------------------- motion */
/*
 * Scroll-driven reveals, CSS-only via animation-timeline: view().
 * Supported in Chrome/Edge/Safari 26; Firefox ignores the whole @supports
 * block, so content simply appears with no animation — no broken state, and
 * still zero JavaScript.
 *
 * Deliberately restrained: motion here serves attention (specs) and
 * affordance (CTA). Everything else stays still.
 */
@media (prefers-reduced-motion: no-preference) {
@supports (animation-timeline: view()) {

@keyframes tv-rise {
from { opacity: 0; transform: translateY(24px); }
to   { opacity: 1; transform: translateY(0); }
}

/*
 * Spec tiles fade without moving. They used to ride tv-rise like everything
 * else, which was fine while they were free-floating tiles — but they are
 * cells in a ruled grid now, and a staggered translate slides each cell's
 * borders out of line with its neighbours', so the rule grid visibly tears
 * apart and reassembles as the panel scrolls in. Opacity leaves the geometry
 * alone, and the stagger below still does the work of leading the eye.
 */
@keyframes tv-fade {
from { opacity: 0; }
to   { opacity: 1; }
}

.tv-gallery__item,
.tv-badges__item,
.tv-intro__headline,
.tv-intro__media {
animation: tv-rise linear both;
animation-timeline: view();
animation-range: entry 5% cover 22%;
}

.tv-specs__item {
animation: tv-fade linear both;
animation-timeline: view();
animation-range: entry 5% cover 22%;
}

/*
 * Stagger the ten spec tiles so the eye travels the grid instead of
 * receiving all ten at once. Small offsets — this is a nudge, not a
 * performance.
 */
.tv-specs__item:nth-child(1)  { animation-range: entry 5%  cover 20%; }
.tv-specs__item:nth-child(2)  { animation-range: entry 7%  cover 22%; }
.tv-specs__item:nth-child(3)  { animation-range: entry 9%  cover 24%; }
.tv-specs__item:nth-child(4)  { animation-range: entry 11% cover 26%; }
.tv-specs__item:nth-child(5)  { animation-range: entry 13% cover 28%; }
.tv-specs__item:nth-child(6)  { animation-range: entry 10% cover 25%; }
.tv-specs__item:nth-child(7)  { animation-range: entry 12% cover 27%; }
.tv-specs__item:nth-child(8)  { animation-range: entry 14% cover 29%; }
.tv-specs__item:nth-child(9)  { animation-range: entry 16% cover 31%; }
.tv-specs__item:nth-child(10) { animation-range: entry 18% cover 33%; }

.tv-gallery__item:nth-child(2) { animation-range: entry 8%  cover 25%; }
.tv-gallery__item:nth-child(3) { animation-range: entry 11% cover 28%; }
}
}

/*
 * Hover affordances, behind (hover: hover).
 *
 * On a touch screen :hover latches after a tap and does not release until
 * something else is tapped, so an un-guarded scale(1.04) leaves one gallery
 * image permanently enlarged and one spec tile permanently lifted after the
 * visitor so much as brushes them while scrolling. The query keeps these to
 * pointing devices, which are the only ones that can express "hover" anyway.
 */
@media (hover: hover) {
.tv-gallery__item img {
transition: transform 0.4s ease, filter 0.4s ease;
}

.tv-gallery__item:hover img,
.tv-gallery__item:focus-within img {
transform: scale(1.04);
filter: brightness(1.05);
}

/*
 * Tiles tint instead of lifting, for the same reason they fade instead of
 * rising: translating one cell of a ruled grid breaks the lines it shares
 * with its four neighbours. A wash of light over the cell reads as the same
 * affordance and leaves the grid intact.
 */
.tv-specs__item {
transition: background-color 0.25s ease;
}

.tv-specs__item:hover {
background-color: rgba(255, 255, 255, 0.06);
}
}

@media (prefers-reduced-motion: reduce) {
.tv-gallery__item img,
.tv-specs__item {
transition: none;
}

.tv-gallery__item:hover img {
transform: none;
}
}

/* ------------------------------------------------- per-villa presentation */
/*
 * Surface colour, CTA colour, body alignment and the intro column split are
 * villa data, not stylesheet constants: V01 and V02 differ on all four in
 * Figma. Each falls back to V01's value, so a villa without a `theme` block
 * renders byte-identically to before this rule existed.
 *
 * The `.tv ` prefix is required, not decorative — see the reset near the top
 * of this file, which claims (0,1,1) over every p inside .tv.
 */
.tv .tv-badges { background: var(--tv-surface, var(--tv-green)); }
.tv .tv-specs  { background: var(--tv-surface, var(--tv-green)); }
.tv .tv-cta    { background: var(--tv-cta-bg, var(--tv-maroon)); }
.tv .tv-body p { text-align: var(--tv-body-align, start); }

@media (min-width: 1024px) {
	.tv .tv-intro {
		grid-template-columns: var(--tv-intro-cols, minmax(0, 721fr) minmax(0, 410fr));
		column-gap: var(--tv-intro-gap, 5.75%);
	}
}

/*
 * Interaction states carry the palette too. Both fall back to V01's literal
 * values, so a villa without a `theme` block keeps the exact computed colours
 * it had before this rule existed.
 *
 * Each ties the specificity of the rule it follows and wins on order, and
 * each sets one property only: `color` and `transform` still come from the
 * :hover rule above, and `outline-color` leaves `3px solid` and the offset
 * from the shorthand untouched.
 */
.tv .tv-cta:hover,
.tv .tv-cta:focus-visible {
	background: var(--tv-cta-hover, #57161f);
}

.tv .tv-cta:focus-visible {
	outline-color: var(--tv-focus-ring, var(--tv-green));
}

/*
 * Intro-row alignment is villa data too. The stylesheet pins the image's
 * bottom edge (see .tv-intro__media above) but leaves the headline at the
 * row's start, which only looks right while the headline is the taller
 * column. Miralay's Arabic headline is above ~1340; Yuvana's never is, and
 * the difference shows as dead space under it.
 *
 * .tv-body spans the full grid but is capped at 68ch, so with zero inline
 * margins the box hugs the start edge and only its text centres. `auto`
 * centres the box itself. Both fall back to today's computed values.
 */
.tv .tv-intro__headline {
	align-self: var(--tv-headline-align, auto);
}

.tv .tv-body {
	margin-inline: var(--tv-body-inline, 0);
}

/* ------------------------------------------------------- whatsapp form */
/*
 * Replaces the bottom CTA button. It reads the same per-villa tokens the
 * button does — --tv-cta-bg, --tv-cta-hover, --tv-focus-ring — so a villa
 * that has already declared its palette gets a matching form for free, and
 * one that has not falls back to V01's literals.
 *
 * Logical properties throughout: this block is laid out by the same rules
 * in Arabic and in Latin, and there is no [dir] override anywhere.
 */
.tv-waform {
	margin-block-start: clamp(32px, 6vw, 72px);
	display: flex;
	flex-direction: column;
	align-items: center;
}

.tv .tv-waform__form {
	display: grid;
	grid-template-columns: 1fr;
	gap: clamp(12px, 1.5vw, 20px);
	inline-size: min(100%, 716px);
}

/*
 * The reset near the top of this file lists h1, h2, p, ul, li, a, span and
 * figure — form controls are not among them, and neither is a button. The
 * bottom CTA was an <a> and inherited that protection; this one is a
 * <button>, so the same theme declarations the reset exists to block can
 * still reach it. Same property list, applied to the two controls.
 */
.tv .tv-waform__input,
.tv .tv-waform__btn {
	font-family: inherit;
	font-style: normal;
	direction: inherit;
	letter-spacing: normal;
	word-spacing: normal;
	text-decoration: none;
	text-indent: 0;
	text-shadow: none;
	text-transform: none;
	box-shadow: none;
	appearance: none;
	min-inline-size: 0;
}

/*
 * 16px is a floor, not a preference: iOS Safari zooms the viewport when a
 * focused input is below it, and the page never zooms back out.
 */
.tv .tv-waform__input {
	inline-size: 100%;
	min-block-size: 44px;
	padding: clamp(12px, 1.6vw, 18px) clamp(16px, 2vw, 24px);
	background: var(--tv-white);
	color: var(--tv-ink);
	border: 1px solid rgba(5, 15, 39, 0.18);
	border-radius: var(--tv-radius-md);
	font-family: inherit;
	font-size: max(1rem, var(--tv-fs-copy));
	line-height: 1.4;
	text-align: start;
}

.tv .tv-waform__input::placeholder {
	color: var(--tv-muted);
}

.tv .tv-waform__input:focus-visible {
	outline: 3px solid var(--tv-focus-ring, var(--tv-green));
	outline-offset: 2px;
	border-color: transparent;
}

/* Mirrors .tv-cta so the two read as the same control in two states. */
.tv .tv-waform__btn {
	--tv-icon-h: 1.2em;

	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: clamp(10px, 1.5vw, 20px);
	inline-size: 100%;
	min-block-size: 44px;
	padding: clamp(14px, 2.2vw, 30px) clamp(20px, 3vw, 40px);
	background: var(--tv-cta-bg, var(--tv-maroon));
	color: var(--tv-white);
	border: 0;
	border-radius: var(--tv-radius-md);
	font-family: inherit;
	font-size: var(--tv-fs-button);
	font-weight: 500;
	line-height: var(--tv-lh-button);
	text-align: center;
	cursor: pointer;
	transition: background-color 0.18s ease, transform 0.18s ease;
}

.tv .tv-waform__btn:hover,
.tv .tv-waform__btn:focus-visible {
	background: var(--tv-cta-hover, #57161f);
	transform: translateY(-2px);
}

.tv .tv-waform__btn:focus-visible {
	outline: 3px solid var(--tv-focus-ring, var(--tv-green));
	outline-offset: 3px;
}

/* Same reason as .tv-cta:active — a press while hovering must not stay lifted. */
.tv .tv-waform__btn:active {
	transform: translateY(0);
}

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

	.tv .tv-waform__btn:hover,
	.tv .tv-waform__btn:focus-visible {
		transform: none;
	}
}

/* Field and button side by side once there is room for both. */
@media (min-width: 640px) {
	.tv .tv-waform__form {
		grid-template-columns: minmax(0, 1fr) auto;
		align-items: stretch;
	}

	.tv .tv-waform__btn {
		inline-size: auto;
	}
}

/*
 * Without this the field is a box of text with nothing to say a list is
 * behind it: the label is visually hidden by design, and several browsers
 * draw no affordance at all for a datalist. A visitor who cannot see the
 * suggestions sends the prefill unchanged, which is the one outcome the
 * form exists to improve on.
 *
 * Not a placeholder — the input carries a value, so a placeholder would
 * never paint.
 */
.tv .tv-waform__hint {
	inline-size: min(100%, 716px);
	margin-block-start: clamp(8px, 1vw, 12px);
	color: var(--tv-muted);
	font-size: var(--tv-fs-label);
	line-height: 1.5;
	text-align: start;
}
