/* OSTADEEZ — site shell.
   Every rule is scoped under the prefixed root class or a prefixed class of its own.
   No bare-element descendant selectors: `.card span` scores higher than `.card-badge` and
   silently defeats the child's own class. Every styled child gets its own class. */

/* ---------------------------------------------------------------- fonts */

/* ⚠ LITERAL weights here, and they must stay literal: `font-weight` inside `@font-face` is a
   DESCRIPTOR, not a property, and a descriptor cannot read a custom property. A `var()` here does
   not fall back — the descriptor fails to parse and the browser stops matching that face, so the
   page silently drops to the fallback stack. The token sweep replaced these once; this note is why
   they were put back. Everywhere OUTSIDE @font-face, weights come from the reference. */
@font-face {
	font-family: 'Vazirmatn';
	src: url('../fonts/Vazirmatn-400.woff2') format('woff2');
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'Vazirmatn';
	src: url('../fonts/Vazirmatn-500.woff2') format('woff2');
	font-weight: 500;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'Vazirmatn';
	src: url('../fonts/Vazirmatn-700.woff2') format('woff2');
	font-weight: 700;
	font-style: normal;
	font-display: swap;
}
@font-face {
	font-family: 'Vazirmatn';
	src: url('../fonts/Vazirmatn-900.woff2') format('woff2');
	font-weight: 900;
	font-style: normal;
	font-display: swap;
}

/* ---------------------------------------------------------------- tokens
   Colour values are injected as custom properties from PHP (per-project palette is DATA).
   The fallbacks here only matter if that injection is ever missing. */

/* Scale taken from the approved wireframes, not invented: 15px base, 1.8 line-height,
   1160px wrap with 22px gutters, 64px header, 16/14/10px radii. */
/* ==================================================================================
 * THE REFERENCE. Every size, weight, radius and space in this theme comes from here.
 *
 * ⚠ WHY IT EXISTS. Before 2026-09-02 the stylesheets carried **33 distinct font-size values** —
 * 13px and 13.5px and .85rem and .88rem all meaning «secondary text», px and rem mixed for the same
 * job, and five different weights used interchangeably. Nothing was wrong with any single value;
 * the problem was that there was no answer to «what size is a caption on this site», so every new
 * component invented one. That is what makes a design drift, and it is what makes two things that
 * should look identical differ by half a pixel.
 *
 * The scale below is DERIVED from what was already in use, with near-neighbours collapsed onto one
 * step. A component picks a step; it does not pick a number. If a design needs a size that is not
 * here, the scale gains a step — it does not gain an exception.
 *
 * `check-css-architecture.php` fails on a bare font-size literal outside this block.
 * ================================================================================== */

.ostz {
	/* ---- space ---- */
	--ostz-space-1: 4px;
	--ostz-space-2: 8px;
	--ostz-space-3: 12px;
	--ostz-space-4: 16px;
	--ostz-space-6: 22px;
	--ostz-space-8: 30px;
	--ostz-space-12: 44px;
	--ostz-space-16: 62px;

	/* ---- radius ---- */
	--ostz-radius-xs: 8px;
	--ostz-radius-sm: 10px;
	--ostz-radius: 14px;
	--ostz-radius-lg: 16px;
	--ostz-radius-pill: 999px;

	/* ---- type scale ----
	   2xs  badges, counts, the smallest label a person should be asked to read
	   xs   captions, meta lines, breadcrumbs
	   sm   secondary UI text: filter rows, card meta, notes
	   base UI text: buttons, labels, table cells
	   md   body copy
	   lg   lede paragraphs and small section titles
	   xl   section titles
	   2xl  panel titles
	   The three responsive steps are for page-level headings only. */
	/* ⚠ `--ostz-text-xs` (11px) IS GONE, and it was the ONE value behind every «text under the
	   12px floor» finding of the 2026-09-05 sweep: the brand descriptor in header and footer, the
	   cart badge, the blog's category chips, and the shop's «تخفیف!» / «دراپِ محدود» / «تمام شد»
	   badges — the smallest type on the site sitting on the labels that carry the drop model. One
	   token, twelve surfaces. Its seventeen consumers were repointed at `xs` in the same edit
	   rather than aliasing it to 12px: two names for one value is how the scale drifted before. */
	--ostz-text-xs: 12px;
	--ostz-text-sm: 13px;
	--ostz-text-base: 14px;
	--ostz-text-md: 15px;
	--ostz-text-lg: 17px;
	--ostz-text-xl: 20px;
	--ostz-text-2xl: 22px;

	--ostz-title-sm: clamp(20px, 5.6vw, 26px);
	--ostz-title: clamp(21px, 3vw, 30px);
	--ostz-title-lg: clamp(23px, 3.4vw, 32px);
	--ostz-display: clamp(34px, 6vw, 68px);

	/* ---- weight ---- four steps, not five. 400 body · 500 quiet label ·
	   700 the working weight for anything that is a control or a title ·
	   900 reserved for the page's own heading and the wordmark. */
	--ostz-weight-normal: 400;
	--ostz-weight-medium: 500;
	--ostz-weight-bold: 700;
	--ostz-weight-black: 900;

	/* ---- line height ---- */
	--ostz-leading-tight: 1.2;
	--ostz-leading-heading: 1.35;
	--ostz-leading-snug: 1.6;
	--ostz-leading-body: 1.8;

	/* ---- layout ---- */
	--ostz-shell: 1160px;
	--ostz-header-h: 64px;
	/* A column of prose wider than this stops being readable — roughly 70 characters at the body
	   size. The article templates were running to the full 1160px shell, i.e. 85 characters. */
	--ostz-measure: 68ch;

	--ostz-font: 'Vazirmatn', Tahoma, 'Segoe UI', Arial, sans-serif;
}

/* One global normalize: the `hidden` attribute scores 0-1-0 and loses to any class that
   sets display, so an element the JS hides stays fully visible with no error anywhere. */
[hidden] {
	display: none !important;
}

/*
 * ⚠ BORDER-BOX, and its absence was a real measured defect rather than a missing convention.
 *
 * The theme had no box model reset at all — only `ostadeez-woo-select.css` and the lightbox set
 * `border-box` on the few elements they owned. So every `inline-size: 100%` on an element that
 * also has padding overflowed its container by the padding, and every `min-block-size` was
 * CONTENT height: the account form's fields asked for a 44px minimum and rendered **70px** tall
 * (44 + 24 padding + 2 border), while the same component on the 404 measured 48px because a
 * different rule reached it. Two sizes for one field, and neither was the one written down.
 *
 * The reset is scoped to our root and inherited from it, so it can never reach markup outside
 * `.ostz` — this stylesheet is loaded onto pages the theme does not fully own.
 */
.ostz {
	box-sizing: border-box;
}

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

/* ---------------------------------------------------------------- base */

.ostz {
	margin: 0;
	background: var(--ostz-bg, #0b0c10);
	color: var(--ostz-text, #f2f4f8);
	font-family: var(--ostz-font);
	font-size: var(--ostz-text-md);
	line-height: var(--ostz-leading-body);
	font-feature-settings: 'tnum';
	-webkit-font-smoothing: antialiased;
	overflow-x: hidden;
}

/*
 * `.ostz h2` is 0-1-1 and beat `.ostz-card-title { font-weight: 700 }` at 0-1-0, so EVERY product
 * card title on the site rendered at 800 — heavier than the design says, on the archive, the front
 * page and the related-products strip. Nothing errored and the file that declares 700 read
 * perfectly.
 *
 * ⚠ This was `:not([class*="ostz-"])` and is now `:where()`. Both fix the specificity; only one of
 * them is honest about what it means. `:not([class*="ostz-"])` says «skip anything whose class
 * contains our prefix», which is a GUESS that our prefix only ever appears on elements we own —
 * and that guess is false, as the image rule below found out the hard way. `:where()` says the
 * thing actually meant: this is a DEFAULT, and any component beats it.
 */
/* uniform */
:where(.ostz) h1,
:where(.ostz) h2,
:where(.ostz) h3 {
	margin: 0;
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-heading);
}

/* Latin runs inside Persian sentences need isolation or they render scrambled. */
.ostz-eng {
	direction: ltr;
	unicode-bidi: isolate;
}

.ostz-rtl {
	direction: rtl;
}

/*
 * ⚠ `:where()`, and the `:not([class*="ostz-"])` it replaces is a MEASURED failure — the exact
 * shape of bug this file keeps documenting, committed by the fix for an earlier one.
 *
 * The original problem: `.ostz img` scores 0-1-1 and silently out-specifies any image's own class
 * at 0-1-0, which killed `.ostz-card-img { height: 100% }` and rendered every product card image
 * at its upload ratio.
 *
 * The fix was `:not([class*="ostz-"])` — «apply only to images that do not carry our prefix». That
 * reads as «images we do not own», and it is not the same statement. WordPress writes its OWN
 * class onto every sized thumbnail: `the_post_thumbnail('ostz-hero')` produces
 * `class="attachment-ostz-hero size-ostz-hero wp-post-image"`. That string CONTAINS `ostz-`, so
 * the exclusion fired, `height: auto` never applied, and the image kept the `height="800"`
 * attribute WordPress prints — MEASURED on every single article: a 1200×800 photograph rendered
 * 573×800, vertically stretched, on the page the reader came for.
 *
 * `:where(.ostz) img` scores 0-0-1: it reaches every image and loses to any component class. No
 * guess about who owns what, and one rule instead of two.
 */
/* uniform */
:where(.ostz) img,
:where(.ostz) video {
	max-inline-size: 100%;
	block-size: auto;
}

/* uniform — every link on this site takes its colour from context, including WooCommerce's own
   (.button, .page-numbers, .remove), none of which carry a prefixed class we could target. This
   is the sanctioned bare-element case: the rule must reach every link or unstyled ones go blue. */
/*
 * ⚠ `:where()` and not `.ostz a`, and this is the same measured bug the `img` and heading rules
 * above carry — the third and last place it lived.
 *
 * `.ostz a { color: inherit }` scores 0-1-1 and SILENTLY out-specifies every component's own
 * 0-1-0 colour rule. That is why `ostadeez-scheme.css` had to re-state the chip's colour at
 * 0-2-0 to get it back, why `ostadeez-updates.css` carries a comment explaining that its card
 * link needed the same treatment, and why every future component that happens to be an `<a>`
 * would have had to know the trick. A rule nobody can use without a workaround is not a default.
 *
 * `:where()` contributes ZERO specificity, so this whole selector scores 0-0-1 — one element.
 * An unclassed link still inherits; ANY component class beats it without qualification.
 *
 * Chosen over `:not([class*="ostz-"])` deliberately: that spelling would drop our own classed
 * anchors to the browser's default blue, and five of them (`ostz-anb-link`, `ostz-brand-link`,
 * `ostz-card-media`, `ostz-origin`, `ostz-update-product`) declare no colour of their own because
 * inheriting is the right answer for them.
 *
 * `:where()` is 2021 and universally supported; the sheet already depends on `color-mix()`, which
 * is two years newer. Unlike `@layer` — refused for exactly this reason — an old browser dropping
 * this ONE rule gets blue links, not an unstyled store.
 */
/* uniform */
:where(.ostz) a {
	color: inherit;
}

.ostz :focus-visible {
	outline: 2px solid var(--ostz-accent, #00e0d5);
	outline-offset: 2px;
}

/* Latin runs inside Persian sentences scramble without isolation. */
.ostz-bidi {
	unicode-bidi: isolate;
}

/*
 * ⚠ MEASURED DEFECT, do not reintroduce the old idiom: this link used to hide itself with
 * `inset-inline-start: -9999px`. That is correct in a left-to-right document, where the overflow
 * falls off the left edge and browsers do not count it — but this document is RTL, where the
 * inline start is the RIGHT edge, so the link was pushed 9999px past it and every page on the site
 * became ~11,000px wide with a real horizontal scrollbar. Nothing errored, and no HTTP-level check
 * could ever see it; it only shows up when the page is laid out in a browser.
 *
 * The clip technique has no direction to get wrong, and it keeps the link in the accessibility
 * tree (unlike display:none), which is the entire point of a skip link.
 */
.ostz-skip:not(:focus):not(:active) {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.ostz-skip {
	position: absolute;
	inset-inline-start: var(--ostz-space-4);
	top: var(--ostz-space-4);
	z-index: 999;
	padding: var(--ostz-space-3) var(--ostz-space-4);
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	font-weight: var(--ostz-weight-bold);
	text-decoration: none;
}

/*
 * ⚠ MEASURED DEFECT, sitewide. WordPress and WooCommerce emit `.screen-reader-text` on labels
 * that must exist for assistive technology but must never be seen. Core does NOT ship a
 * front-end rule for it — WooCommerce's stylesheet used to, and this project deliberately
 * dequeues every WooCommerce stylesheet. The result: the quantity label printed the whole
 * product name above the stepper, and a paragraph of variation boilerplate printed under every
 * variable product card in every archive. Owning the class is the price of owning the CSS.
 *
 * Clip, not display:none — the text has to stay in the accessibility tree, which is its entire
 * reason for existing. Not parked off-screen either: an inline-start offset is the RTL
 * scrollbar trap documented above.
 */
.screen-reader-text:not(:focus):not(:active) {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
	word-wrap: normal;
}

/* ---------------------------------------------------------------- header */

/* ---------------------------------------------------------------- announcement bar */

.ostz-anb {
	background: var(--ostz-bg-raised-2, #1b1e27);
	border-block-end: 1px solid var(--ostz-line-strong, #333846);
	font-size: var(--ostz-text-sm);
}

.ostz-anb-inner {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--ostz-space-3);
	max-width: var(--ostz-shell);
	margin-inline: auto;
	padding: 7px var(--ostz-space-6);
}

.ostz-anb-msg {
	color: var(--ostz-text-dim, #aab0be);
	text-align: center;
}

.ostz-anb-msg strong {
	color: var(--ostz-accent, #00e0d5);
	font-weight: var(--ostz-weight-bold);
}

/* Measured at 19x20 before the min-size: under the 24x24 floor, and it is the only way to dismiss
   a bar that sits above everything else on every page. */
.ostz-anb-close {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	min-width: 28px;
	min-height: 28px;
	margin-inline-start: auto;
	padding: 2px 6px;
	border: 0;
	border-radius: 8px;
	background: none;
	color: var(--ostz-text-faint, #828a9c);
	font-family: inherit;
	font-size: var(--ostz-text-lg);
	line-height: 1;
	cursor: pointer;
}

.ostz-anb-close:hover {
	color: var(--ostz-text, #f2f4f8);
}

/* ---------------------------------------------------------------- header */

.ostz-header {
	position: sticky;
	top: 0;
	z-index: 40;
	background: color-mix(in srgb, var(--ostz-bg, #0b0c10) 85%, transparent);
	backdrop-filter: blur(10px);
	border-block-end: 1px solid var(--ostz-line, #262a35);
}

.ostz-header-inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--ostz-space-6);
	max-width: var(--ostz-shell);
	min-height: var(--ostz-header-h);
	margin-inline: auto;
	padding-inline: var(--ostz-space-6);
}

.ostz-brand {
	display: flex;
	flex-direction: column;
	line-height: var(--ostz-leading-tight);
}

/* The home link measured 23px tall. Inline-flex with a floor rather than padding, so the wordmark
   does not shift within the header row. */
.ostz-brand-link {
	display: inline-flex;
	align-items: center;
	min-height: 24px;
	text-decoration: none;
}

/*
 * ⚠ FLAT, not the two-tone gradient it used to carry. 2026-09-02, user's decision.
 *
 * Beyond the look, the gradient made the wordmark hard to reason about: clipped text computes as
 * `color: transparent`, so every contrast probe on this site had to special-case it, and it read as
 * a 1.0:1 failure in three separate measurements before being recognised each time. A brand mark
 * that no tool can measure is a brand mark nobody checks.
 */
.ostz-brand-word {
	font-weight: var(--ostz-weight-black);
	font-size: var(--ostz-text-xl);
	letter-spacing: .5px;
	direction: ltr;
	unicode-bidi: isolate;
	color: var(--ostz-accent, #00e0d5);
}

.ostz-brand-descriptor,
.ostz-footer-descriptor {
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
	font-weight: var(--ostz-weight-medium);
}

/* ---------------------------------------------------------------- nav
   The links are always present and always visible. On narrow screens the row scrolls
   horizontally inside itself — the page never scrolls sideways and no link is hidden. */

.ostz-nav {
	flex: 1 1 auto;
	min-width: 0;
}

.ostz-nav-list {
	display: flex;
	align-items: center;
	gap: var(--ostz-space-1);
	margin: 0;
	padding: 0;
	list-style: none;
	overflow-x: auto;
	scrollbar-width: none;
	overscroll-behavior-inline: contain;
}

.ostz-nav-list::-webkit-scrollbar {
	display: none;
}

.ostz-nav-item {
	flex: 0 0 auto;
}

.ostz-nav-link {
	display: block;
	padding: var(--ostz-space-2) 13px;
	min-height: 44px;
	line-height: 28px;
	border-radius: var(--ostz-radius-sm);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-base);
	font-weight: var(--ostz-weight-medium);
	text-decoration: none;
	white-space: nowrap;
	transition: color .15s, background-color .15s;
}

.ostz-nav-link:hover {
	color: var(--ostz-text, #f2f4f8);
	background: var(--ostz-bg-raised, #15171e);
}

/*
 * ⚠ An INSET box-shadow on a 10px-radius box does not draw an underline — it draws a curved band
 * that follows the corners, and because the link is 44px tall inside a 60px item it floated clear
 * of the header's own border and read as a stray outline. Seen in a capture at 820px.
 * A real underline is a pseudo-element pinned to the item's bottom edge, so it stays straight and
 * sits where the eye expects the active marker to be.
 */
.ostz-nav-link[aria-current='page'] {
	color: var(--ostz-text, #f2f4f8);
	position: relative;
}

.ostz-nav-link[aria-current='page']::after {
	content: '';
	position: absolute;
	inset-inline: 13px;
	inset-block-end: 6px;
	height: 2px;
	border-radius: 2px;
	background: var(--ostz-accent, #00e0d5);
}

/* ---------------------------------------------------------------- header actions */

.ostz-header-actions {
	display: flex;
	align-items: center;
	gap: var(--ostz-space-2);
	flex: 0 0 auto;
}

.ostz-cart-link,
.ostz-account-link {
	position: relative;
	display: inline-flex;
	align-items: center;
	gap: var(--ostz-space-2);
	min-height: 44px;
	padding-inline: var(--ostz-space-3);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-raised, #15171e);
	color: var(--ostz-text, #f2f4f8);
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-medium);
	text-decoration: none;
}

/* The word beside the count. It disappears below the phone breakpoint, where the bar has no room
   for it and the footer bar carries the labelled version of the same control — but it stays in the
   DOM, because the count on its own is «۳» with no accessible name. */
.ostz-cart-label {
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-medium);
}

.ostz-cart-count {
	display: grid;
	place-items: center;
	min-width: 18px;
	height: 18px;
	padding-inline: 4px;
	border-radius: 9px;
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	font-size: var(--ostz-text-xs);
	font-weight: var(--ostz-weight-bold);
	unicode-bidi: isolate;
}

/* ---------------------------------------------------------------- main */

.ostz-main {
	display: block;
	max-width: var(--ostz-shell);
	margin-inline: auto;
	padding: var(--ostz-space-8) var(--ostz-space-4) var(--ostz-space-16);
	min-height: 60vh;
}

/*
 * WooCommerce notices on a template WooCommerce does not own. Its own hooks
 * (woocommerce_before_shop_loop, _before_single_product, _before_cart …) never fire here, so a
 * refusal raised on the front page, an article or a plain page used to be written to the session
 * and silently dropped. header.php prints them into this wrapper, guarded so it can never
 * double-print on a Woo template.
 *
 * ⚠ It lives in the BASE sheet, not a per-section one: the widest template that renders it is
 * "every non-Woo template", and a component declared in a sheet narrower than its own reach is
 * how four separate surfaces on this site ended up unstyled. `:empty` keeps it out of the flow on
 * the overwhelmingly common case where there is nothing to say.
 */
.ostz-woo-notices:empty {
	display: none;
}

.ostz-woo-notices {
	margin-block-end: var(--ostz-space-6);
}

/*
 * THE SHELL WRAPPER and THE PAGE BODY — two names the templates have been printing all along with
 * no rule behind either.
 *
 * `.ostz-wrap` is written by the drop banner and the updates strip to mean «the site's column»;
 * `.ostz-page` is `post_class()`'s wrapper on an ordinary page, the projects template, and every
 * WooCommerce shortcode page. Both were relying on a parent to give them their width, which is
 * true until one of them is used somewhere else — and the drop banner is a full-bleed strip, so
 * its inner wrapper had nothing holding it to the column at all.
 */
.ostz-wrap {
	max-inline-size: var(--ostz-shell);
	margin-inline: auto;
	padding-inline: var(--ostz-space-4);
}

.ostz-page {
	padding-block: var(--ostz-space-8) var(--ostz-space-16);
}

/*
 * The body of a page that is NOT prose — the cart, the checkout, My Account. It exists to be the
 * thing `.ostz-prose` is not: no reading measure. See the note at the top of `page.php` for the
 * measured damage that made it necessary (a 573px cart with a 0px-wide thumbnail).
 */
.ostz-pagebody {
	min-inline-size: 0;
}

/*
 * THE PAGE HEAD. One block for every list view — the blog, the five archive kinds, the search
 * results — so the sentence at the top of a page cannot drift between templates. It stays in this
 * sheet rather than becoming a module because it is the page's own furniture, printed by
 * `ostz_archive_head()` on templates that share nothing else.
 */
.ostz-pagehead {
	padding-block: var(--ostz-space-8) 0;
}

.ostz-page-title {
	margin: 0 0 var(--ostz-space-6);
	font-size: var(--ostz-title);
	font-weight: var(--ostz-weight-black);
	line-height: var(--ostz-leading-tight);
}

/* Inside the head the title's own bottom margin is too much: the description and the count belong
   to it, and 22px of air between a heading and its own subtitle reads as two separate things. */
.ostz-pagehead .ostz-page-title {
	margin-block-end: var(--ostz-space-2);
}

.ostz-lede {
	max-width: 62ch;
	color: var(--ostz-text-dim, #aab0be);
}

/* «۱۲ مطلب». The count a reader uses to decide whether to scroll — quiet, and never the loudest
   thing under a heading. */
.ostz-pagehead-meta {
	margin: var(--ostz-space-2) 0 0;
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
	unicode-bidi: isolate;
}

.ostz-visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
}

/* Offsets an in-page anchor so the sticky header does not cover the target. */
.ostz-anchor {
	display: block;
	scroll-margin-top: calc(var(--ostz-header-real, var(--ostz-header-h)) + var(--ostz-space-4));
}

.ostz-noscroll {
	overflow: hidden;
}

/* ---------------------------------------------------------------- hero */

/* Values ported from the approved landing wireframe, not re-invented. */
.ostz-hero {
	position: relative;
	display: flex;
	align-items: flex-end;
	min-height: 88vh;
	margin-block-end: var(--ostz-space-12);
	border-radius: var(--ostz-radius-lg);
	overflow: hidden;
	background: var(--ostz-bg, #0b0c10);
}

/* Phones: a full-viewport hero would put the first product a whole screen away. */
@media (max-width: 720px) {
	.ostz-hero {
		min-height: 72vh;
	}
}

.ostz-hero-bg {
	position: absolute;
	inset: 0;
	/*
	 * ⚠ ONE accent wash, not the cyan-and-magenta pair. The two-tone glow was the NFS look the rest
	 * of the UI has now dropped, and a hero that still shouts it while every button beside it is
	 * flat reads as two designs on one page. This is atmosphere behind the content — a single
	 * low-opacity wash over the page's own ground — not a brand fill.
	 */
	background:
		radial-gradient(120% 90% at 20% 15%, color-mix(in srgb, var(--ostz-accent, #00e0d5) 12%, transparent), transparent 60%),
		linear-gradient(180deg, var(--ostz-bg-raised, #15171e) 0%, var(--ostz-bg, #0b0c10) 60%, var(--ostz-bg, #0b0c10) 100%);
}

.ostz-hero-grid {
	position: absolute;
	inset: 0;
	opacity: .5;
	background-image:
		linear-gradient(var(--ostz-line, #262a35) 1px, transparent 1px),
		linear-gradient(90deg, var(--ostz-line, #262a35) 1px, transparent 1px);
	background-size: 64px 64px;
	-webkit-mask-image: radial-gradient(80% 70% at 60% 30%, #000, transparent 80%);
	mask-image: radial-gradient(80% 70% at 60% 30%, #000, transparent 80%);
}

.ostz-hero-scan {
	position: absolute;
	inset: 0;
	background: repeating-linear-gradient(180deg, rgba(255, 255, 255, .03) 0 1px, transparent 1px 4px);
	pointer-events: none;
}

/* The footage is a background layer behind the same content, never the hero itself. */
.ostz-hero-media {
	position: absolute;
	inset: 0;
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* With footage present the gradient becomes a legibility scrim instead of the backdrop. */
.ostz-hero-has-media .ostz-hero-bg {
	z-index: 1;
	background: linear-gradient(180deg, transparent 0%, color-mix(in srgb, var(--ostz-bg, #0b0c10) 82%, transparent) 100%);
}

.ostz-hero-has-media .ostz-hero-grid {
	display: none;
}

/* The hero already sits inside the shell, so this only needs its own breathing room. */
.ostz-hero-in {
	position: relative;
	z-index: 2;
	width: 100%;
	padding: 56px var(--ostz-space-8) 60px;
}

.ostz-kicker {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	margin-block-end: 20px;
	padding: 5px 12px;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: 20px;
	background: color-mix(in srgb, var(--ostz-accent, #00e0d5) 6%, transparent);
	color: var(--ostz-accent, #00e0d5);
	font-size: var(--ostz-text-xs);
	letter-spacing: 2px;
	unicode-bidi: isolate;
}

.ostz-kicker-dot {
	width: 7px;
	height: 7px;
	border-radius: 50%;
	background: currentColor;
	box-shadow: 0 0 10px currentColor;
}

.ostz-hero-title {
	max-width: 18ch;
	margin: 0;
	font-size: var(--ostz-display);
	font-weight: var(--ostz-weight-black);
	line-height: var(--ostz-leading-tight);
	letter-spacing: -1px;
}

/* The one highlighted word in the hero heading. Flat accent for the same reason as the wordmark. */
.ostz-hero-em {
	color: var(--ostz-accent, #00e0d5);
}

.ostz-hero-lead {
	max-width: 52ch;
	margin-block-start: 16px;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-lg);
}

.ostz-hero-cta {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	margin-block-start: 28px;
}

.ostz-hero-facts {
	display: flex;
	flex-wrap: wrap;
	gap: 26px;
	margin: 34px 0 0;
	padding: 22px 0 0;
	border-block-start: 1px solid var(--ostz-line, #262a35);
	list-style: none;
}

.ostz-hero-fact {
	margin: 0;
}

.ostz-hero-fact-n {
	display: block;
	font-size: var(--ostz-text-2xl);
	font-weight: var(--ostz-weight-black);
	unicode-bidi: isolate;
}

.ostz-hero-fact-l {
	font-size: var(--ostz-text-xs);
	color: var(--ostz-text-faint, #828a9c);
}

.ostz-hero-sound {
	position: absolute;
	inset-block-end: var(--ostz-space-4);
	inset-inline-end: var(--ostz-space-4);
	min-height: 44px;
	padding-inline: var(--ostz-space-4);
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: 999px;
	background: rgba(0, 0, 0, .55);
	color: var(--ostz-text, #eceef2);
	font-family: inherit;
	font-size: var(--ostz-text-sm);
	cursor: pointer;
}

.ostz-home-title {
	max-width: 22ch;
}

/* ---------------------------------------------------------------- sections */

.ostz-section {
	margin-block-start: var(--ostz-space-16);
}

.ostz-section-head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--ostz-space-4);
}

.ostz-section-title {
	margin: 0 0 var(--ostz-space-6);
	font-size: var(--ostz-text-xl);
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-heading);
}

/* ⚠ `inline-flex` + a 24px minimum, not a padding guess: measured 78.7×23.4 on the front page,
   just under the floor. An inline `<a>` takes its height from the line box, so a line-height
   change anywhere upstream silently re-breaks it; a declared minimum cannot. */
.ostz-section-more {
	display: inline-flex;
	align-items: center;
	min-block-size: 24px;
	color: var(--ostz-accent, #00e0d5);
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-medium);
	text-decoration: none;
}

/*
 * ⚠ THE CHIP IS GONE FROM THIS FILE TOO, and it took three passes to finish the job.
 *
 * 2026-09-02: `.ostz-chip`, its hover and `.ostz-chip-count` were declared here AND in
 * ostadeez-shop.css with different values — radius 999px vs 22px, a different line token, and a
 * hover fallback carrying the retired green. Load order gave every page the shop copy, so this
 * block was dead everywhere; it was deleted and the shop copy kept.
 * 2026-09-03: the shop copy turned out to be in the wrong sheet as well — see the note in
 * ostadeez-shop.css — and `.ostz-chip-row` / `.ostz-chip-name` were still sitting here, which is
 * how a component keeps a foot in two files. All of it is in modules/ostadeez-m-chip.css.
 */

/* ---------------------------------------------------------------- capture strip */

.ostz-capture {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--ostz-space-6);
	margin-block-start: var(--ostz-space-16);
	padding: var(--ostz-space-8) var(--ostz-space-6);
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: var(--ostz-radius-lg);
	background:
		radial-gradient(80% 140% at 90% 0, color-mix(in srgb, var(--ostz-accent-2, #c026ff) 10%, transparent), transparent 60%),
		var(--ostz-bg-raised, #15171e);
}

.ostz-capture-body {
	flex: 1 1 280px;
	min-width: 0;
}

.ostz-capture-title {
	margin: 0 0 var(--ostz-space-2);
	font-size: var(--ostz-text-lg);
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-snug);
}

.ostz-capture-note {
	margin: 0;
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
}

.ostz-capture-form {
	flex: 0 1 380px;
	min-width: 260px;
}

/* ---------------------------------------------------------------- modal */

.ostz-modal {
	position: fixed;
	inset: 0;
	z-index: 200;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: var(--ostz-space-4);
}

.ostz-modal-backdrop {
	position: absolute;
	inset: 0;
	background: rgba(0, 0, 0, .7);
}

.ostz-modal-box {
	position: relative;
	width: min(560px, 100%);
	max-height: 85vh;
	overflow-y: auto;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: var(--ostz-radius-lg);
	background: var(--ostz-bg-raised, #15171e);
}

.ostz-modal-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--ostz-space-4);
	padding: var(--ostz-space-4) var(--ostz-space-6);
	border-block-end: 1px solid var(--ostz-line, #262a35);
}

.ostz-modal-title {
	margin: 0;
	font-size: var(--ostz-text-lg);
	font-weight: var(--ostz-weight-bold);
}

.ostz-modal-close {
	min-width: 44px;
	min-height: 44px;
	border: 0;
	background: transparent;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-2xl);
	line-height: 1;
	cursor: pointer;
}

.ostz-modal-body {
	padding: var(--ostz-space-6);
}

.ostz-modal-note,
.ostz-modal-empty {
	margin: 0 0 var(--ostz-space-4);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-base);
}

.ostz-modal-empty {
	margin: 0;
}

/* Wide content scrolls inside its own container — the page never scrolls sideways. */
/* ⚠ The ONE declaration of this class. It used to be declared here AND in
   `modules/ostadeez-m-table.css` — same effect, two homes, `max-width` in one and
   `max-inline-size` in the other — and load order decided which a page got. That module is gone;
   this is the survivor. */
.ostz-table-scroll {
	max-inline-size: 100%;
	overflow-x: auto;
}

.ostz-size-table {
	width: 100%;
	border-collapse: collapse;
	font-size: var(--ostz-text-sm);
}

.ostz-size-table th,
.ostz-size-table td {
	padding: var(--ostz-space-2) var(--ostz-space-3);
	border-block-end: 1px solid var(--ostz-line, #262a35);
	text-align: start;
}

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

.ostz-footer {
	border-block-start: 1px solid var(--ostz-line, #262a35);
	background: var(--ostz-bg-sunken, #07070a);
}

.ostz-footer-inner {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--ostz-space-6);
	max-width: var(--ostz-shell);
	margin-inline: auto;
	padding: var(--ostz-space-12) var(--ostz-space-4);
}

.ostz-footer-brand {
	display: flex;
	flex-direction: column;
}

/* The footer's link block. `margin-inline-start: auto` is what puts it opposite the wordmark on a
   wide screen and lets it fall under it on a narrow one, without a second layout rule. */
.ostz-footer-nav {
	margin-inline-start: auto;
}

.ostz-footer-item {
	margin: 0;
}

.ostz-footer-list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ostz-space-4);
	margin: 0;
	padding: 0;
	list-style: none;
}

/* Policy links are the last thing a buyer checks before paying, so they are readable rather than
   whispered — and each is a 24px target, not a 17px line of text. */
.ostz-footer-link {
	display: inline-flex;
	align-items: center;
	min-height: 24px;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
	text-decoration: none;
}

.ostz-footer-link:hover {
	color: var(--ostz-accent, #00e0d5);
}

.ostz-footer-social {
	display: flex;
	gap: var(--ostz-space-3);
	margin-inline-start: auto;
}

.ostz-social-link {
	min-height: 44px;
	display: inline-flex;
	align-items: center;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
	text-decoration: none;
	unicode-bidi: isolate;
}

.ostz-social-link:hover {
	color: var(--ostz-text, #eceef2);
}

.ostz-footer-legal {
	flex: 1 0 100%;
	margin: 0;
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
}

/* ---------------------------------------------------------------- contact page
   Two tracks side by side, as the wireframe has them. Stacked, the pair reads as one long form
   with a heading dropped into the middle of it, and the business enquiry — the one that matters
   commercially — ends up below the fold on every visit. */

.ostz-contact-cols {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--ostz-space-6);
	align-items: start;
}

@media (max-width: 900px) {
	.ostz-contact-cols {
		grid-template-columns: 1fr;
	}
}

.ostz-contact-card {
	padding: var(--ostz-space-6);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-lg);
	background: var(--ostz-bg-raised, #15171e);
}

.ostz-contact-card > h2 {
	margin: 0 0 var(--ostz-space-2);
	font-size: var(--ostz-text-xl);
	font-weight: var(--ostz-weight-black);
}

.ostz-contact-card > p {
	margin: 0 0 var(--ostz-space-5);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
}

/* The plugin centres and caps its form at 640px, which leaves it floating inside the card. */
.ostz-contact-card .dmf-form {
	max-width: none;
	margin: 0;
}

.ostz-contact-channels {
	margin-block-start: var(--ostz-space-6);
}

.ostz-channels {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
	gap: var(--ostz-space-3);
	margin: 0 0 var(--ostz-space-4);
	padding: 0;
	list-style: none;
}

.ostz-channel {
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: 12px 14px;
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-raised-2, #1b1e27);
}

.ostz-channel-k {
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
}

/*
 * ⚠ `direction: ltr` is not optional here, and isolation alone does NOT fix it. Inside an
 * isolated run the base direction is still inherited RTL, so a leading neutral character is
 * placed by that base: «@ostadeez» rendered as «ostadeez@» on the live page. Email addresses
 * looked fine and hid the problem, because they start with a letter.
 *
 * `text-align: end` under `direction: ltr` resolves to the right edge, so the handle keeps the
 * card's visual alignment while its characters run in the correct order.
 */
/* Measured 216.6×23.4 — the e-mail addresses and handles on the contact page are the only way to
   reach us from that page, and they were under the tap floor on every one. */
.ostz-channel-v {
	display: inline-flex;
	align-items: center;
	min-block-size: 24px;
	direction: ltr;
	unicode-bidi: isolate;
	text-align: end;
	color: var(--ostz-accent, #00e0d5);
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-bold);
	text-decoration: none;
	overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------- narrow screens */

@media (max-width: 900px) {
	.ostz-header-inner {
		flex-wrap: wrap;
		gap: var(--ostz-space-3);
		padding-block: var(--ostz-space-3);
	}

	.ostz-nav {
		order: 3;
		flex-basis: 100%;
		margin-inline: calc(var(--ostz-space-4) * -1);
		padding-inline: var(--ostz-space-4);
	}

	.ostz-header-actions {
		margin-inline-start: auto;
	}

	.ostz-footer-social {
		margin-inline-start: 0;
	}
}

/* ================================================================ article
 *
 * ⚠ MOVED to modules/ostadeez-m-article.css and modules/ostadeez-m-crumb.css on 2026-09-03.
 *
 * `.ostz-single`, `.ostz-prose` and the entry breadcrumb lived here because main.css is the sheet
 * that loads everywhere — the right instinct and the wrong file. main.css is layers 1 and 2 of the
 * architecture, tokens and element defaults; an article is a COMPONENT. Keeping it here is exactly
 * what let `.ostz-crumb--entry` be declared in one sheet while the `.ostz-crumb` base it varies
 * lived in another that an article never loads, so the trail on every blog post had a variant and
 * no base.
 *
 * Nothing about the article or the breadcrumb may be declared here again.
 */


	.ostz-anb-inner {
		display: flex;
		align-items: center;
		gap: 8px;
		white-space: nowrap;
		overflow: hidden;
	}

	.ostz-anb-inner > * {
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	/* The page head: the title stays prominent, the supporting line is capped at two lines. */
	.ostz-page-title {
		font-size: var(--ostz-title-sm);
		margin-block: 6px;
	}

	.ostz-lede {
		display: -webkit-box;
		-webkit-line-clamp: 2;
		-webkit-box-orient: vertical;
		overflow: hidden;
		font-size: var(--ostz-text-sm);
		line-height: var(--ostz-leading-snug);
		margin-block: 0;
	}
}
