/* 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 ---- */
	/* 1400px by the owner's decision 2026-09-27 (was 1160: «the site reads narrow»). Prose does
	   not follow it — articles keep --ostz-measure below. */
	--ostz-shell: 1400px;
	--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 several of them (`ostz-anb-link`, `ostz-brand-link`,
 * `ostz-card-media`) 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;
}

/* ---------------------------------------------------------------- the header: NOT HERE

   ⚠ Moved to `modules/ostadeez-m-header.css` on 2026-09-22 — announcement bar, wordmark, nav and
   header actions, with their mobile rules. This sheet is tokens and element defaults; the header
   is a component. Nothing about it may be declared here again.
*/


/* ---------------------------------------------------------------- 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;
}

/* ---------------------------------------------------------------- the hero: NOT HERE

   ⚠ Moved to `modules/ostadeez-m-hero.css` on 2026-09-22 and loaded only on the front page.
   It is one template's section; this sheet is tokens and element defaults.
*/

/* ---------------------------------------------------------------- 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;
}

/* ---------------------------------------------------------------- the size dialog: NOT HERE

   ⚠ Moved to `modules/ostadeez-m-size-guide.css` on 2026-09-22 and loaded only on a product page.
*/

/* ---------------------------------------------------------------- the footer: NOT HERE

   ⚠ Moved to `modules/ostadeez-m-footer.css` on 2026-09-22, with its one mobile rule.
*/


/* ---------------------------------------------------------------- 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);
}

/* templates/template-contact.php: the head (title + one sentence) and the editor's own content
   under the cards — which is where the FAQ shortcode lives. The FAQ markup is dm-faq-pro's. */
.ostz-contact-head {
	margin-block-end: var(--ostz-space-6);
}

.ostz-contact-head .ostz-page-title {
	margin-block-end: var(--ostz-space-2);
}

/* 0-2-0 on purpose: `.ostz-contact-card > p` (0-1-1) set its margins to 0 0 22 and the two lines
   touched (measured gap 0). */
.ostz-contact-card > .ostz-contact-hours {
	margin-block: var(--ostz-space-2) 0;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
}

/* Not `.ostz-prose`: at the 68ch reading measure the accordion was 573px on a 1400px page. */
.ostz-contact-more {
	max-inline-size: 900px;
	margin-block-start: var(--ostz-space-12);
}

.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 */

/*
 * ⚠ A REAL DEFECT WAS REPAIRED HERE ON 2026-09-22, and it had been rendering on every desktop.
 *
 * When the article section was lifted out of this block on 2026-09-03, the media query's closing
 * brace was left where it was — so the three rules that followed it (`.ostz-anb-inner`, the page
 * title and the lede) sat at TOP LEVEL, indented as if they were still inside, and applied at
 * EVERY width. MEASURED at 1280px before the fix: the archive's heading rendered 26px, the cap of
 * the phone step, instead of the 30px its own step gives at that width; the supporting line was
 * clamped to two lines at 13px, the phone size; and the announcement bar was single-line and
 * clipped. Nothing errored, the file read perfectly, and a stray `}` sat at the end of it.
 *
 * The header's two rules moved to the header module with everything else of the header's.
 */
@media (max-width: 900px) {

	/* 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;
	}
}
