
/* --- ostadeez-m-button.css --- */
/* BUTTON — the component the theme always meant to have.
   Second component migrated out of the legacy sheets, 2026-09-02.
   Replaces: ostadeez-woo-baseline.css `.ostz … a.button/button.button/input.button` + `.alt` +
   the disabled state, and ostadeez-shop.css `.ostz .button, .ostz-btn` + `.ostz .ostz-card .button`.
   All of those were deleted from those files in the same edit.

   ⚠ WHY THIS EXISTS AT ALL. The theme carried THREE competing definitions of «a button»:
   the baseline's element-qualified `.ostz a.button` (0-2-1), the shop skin's `.ostz .button`
   (0-2-0) that was written to be the loud primary, and the card's `.ostz .ostz-card .button`
   (0-3-0). Every real button on this site is an `a`, a `button` or an `input`, so the baseline
   always beat the primary style: MEASURED, `.ostz .button` painted on ZERO of the 15 elements it
   matched, and `.ostz-btn` was used in no template at all. The store's declared visual hierarchy
   did not exist — «افزودن به سبد خرید» rendered identically to «اطلاعات بیشتر» on every card.
   One component, one file, and the variant is chosen in PHP rather than lost in a specificity race.

   Every value below is what the site ALREADY rendered, so the default look does not change. The
   only deliberate change is that the card's add-to-cart now asks for `--primary`. */

/* ⚠ `@mirrors` modules/ostadeez-b-woo.css — the platform-class bridge carries this same body on
   purpose (see the note there). This file is the SOURCE: edit here first. */
.ostz-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--ostz-space-2);
	/*
	 * ⚠ ONE CONTROL HEIGHT. MEASURED on the cart's action row: «بروزرسانی سبد خرید» rendered 43px
	 * and «اعمال کدتخفیف» 48px, side by side in the same cell, because the second was stretched by
	 * the field beside it and the first was not. Nothing was wrong with either value; there simply
	 * was no answer to «how tall is a control on this site», so the row disagreed with itself.
	 * 48px is the field's height, so a button next to an input now lines up by construction
	 * instead of by accident.
	 */
	min-block-size: 48px;
	padding: 12px 22px;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: var(--ostz-radius-sm, 10px);
	background: var(--ostz-bg-raised-2, #1b1e27);
	color: var(--ostz-text, #f2f4f8);
	font-family: inherit;
	font-size: var(--ostz-text-base);
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-tight);
	text-decoration: none;
	cursor: pointer;
	transition: border-color .18s ease, color .18s ease, background .18s ease;
}

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

/* PRIMARY — the action a screen wants taken. Solid accent, so it reads as the answer to the page
   rather than one option among several. */
.ostz-btn--primary {
	border-color: transparent;
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	font-weight: var(--ostz-weight-bold);
}

.ostz-btn--primary:hover {
	border-color: transparent;
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	filter: brightness(1.08);
}

/* BRAND — the single most important action on a screen, and never more than one of them: the buy
   button, place order, proceed to checkout. Louder than primary on purpose.
   ⚠ The `--brand` spelling is gone: `front-page.php` writes `ostz-btn-brand` and nothing anywhere
   wrote the double-dash form, so it was a second name for one declaration. Measured 2026-09-05. */
.ostz-btn-brand {
	border-color: transparent;
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	font-weight: var(--ostz-weight-bold);
}

.ostz-btn-brand:hover {
	border-color: transparent;
	color: var(--ostz-accent-ink, #08090c);
	filter: brightness(1.08);
}

/* GHOST — the option beside the answer. Same size and rhythm as the default so a pair lines up,
   but no filled ground: it must read as «the other way», never as a second primary.

   ⚠ This variant and `--primary` were the two names in this component that had NO rule anywhere.
   They were written in `ostadeez-shop.css`, which only loads on WooCommerce views and the front
   page — so `ostz-btn-ghost` on the 404 and `ostz-btn-primary` on a single update rendered as bare
   `.ostz-btn`, on the two templates least likely to be looked at. Declaring them HERE is the fix,
   because this component rides the shell and therefore exists on every template. */
.ostz-btn--ghost,
.ostz-btn-ghost {
	border-color: var(--ostz-line-strong, #333846);
	background: transparent;
	color: var(--ostz-text-dim, #aab0be);
}

.ostz-btn--ghost:hover,
.ostz-btn-ghost:hover {
	border-color: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent, #00e0d5);
}

/* The single-dash spelling of the primary variant, kept for the same reason the badge keeps its
   aliases: the name is written by PHP in more than one file. */
.ostz-btn-primary {
	border-color: transparent;
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	font-weight: var(--ostz-weight-bold);
}

.ostz-btn-primary:hover {
	border-color: transparent;
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	filter: brightness(1.08);
}

/* ⚠ FIVE VARIANTS WERE REMOVED HERE on 2026-09-05 — `--block`, `--small`, `--link`, `--danger`
   and `--hero` — and the reason is worth keeping so they are not written back on reflex.
   Every one was a variant of a design SYSTEM with no consumer: measured across 44 rendered pages,
   not one element wore any of them, and each shipped in the bundle on every page that loaded the
   button module. `--hero` was the worst of the five, because `modules/ostadeez-b-woo.css` carries
   a byte-identical body keyed to `#place_order` / `.checkout-button` — the two names WooCommerce
   actually prints — so the site had two declarations of one look and used the other one.

   A variant is written when a screen needs it, against markup that exists. Four lines is not a
   saving worth pre-empting, and a system nobody uses is not a system. */

/* COMPACT — the card size. A product card carries two actions in a 202px column; at the full size
   they wrap and the card grows. */
.ostz-btn--compact {
	min-block-size: 34px;
	padding: 0 var(--ostz-space-3);
	border-radius: 9px;
	font-size: var(--ostz-text-xs);
	font-weight: var(--ostz-weight-bold);
}

.ostz-btn--compact.ostz-btn--primary {
	font-weight: var(--ostz-weight-bold);
}

/*
 * ⚠ The disabled state must reach the CLASS as well as the attribute. WooCommerce marks a variable
 * product's add-to-cart with `class="disabled wc-variation-selection-needed"` and no `disabled`
 * attribute, so an attribute-only rule leaves the button at full strength while the platform
 * considers it unusable — measured on the product page, where the button looked live and did
 * nothing until a variation was chosen.
 */
.ostz-btn:disabled,
.ostz-btn.disabled,
.ostz-btn.is-disabled {
	opacity: .5;
	cursor: not-allowed;
	filter: none;
}

/* --- ostadeez-m-chip.css --- */
/* CHIP — a pill that FILTERS or NAVIGATES, and that can be on or off.

   The category row on the shop archive, the same row on the front page, the routes out of an empty
   cart, and the category suggestions on a search results page.

   ⚠ WHY IT MOVED. It was declared in `ostadeez-shop.css`, which loads on WooCommerce views and the
   front page only. `search.php` prints chips too, and a plain `?s=` search is not a WooCommerce
   view — so the search page's category row rendered as bare links with no pill, no border and no
   tap target, on the one screen a person reaches when they could not find something. Exactly the
   shape of the dead-button bug fixed the same day: a shell component parked in a per-section sheet
   is correct on the templates that sheet reaches and invisible on the ones it does not.

   ⚠ A CHIP IS NOT A TAG. This one is a control with an on state; `ostadeez-m-tag.css` is a label
   that says what something is. They look related on purpose and they are not interchangeable — a
   tag has no «on», and giving one an on state is how a taxonomy row starts pretending to be a
   filter. */

.ostz-chips {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ostz-space-2);
}

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

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

.ostz-chip {
	display: inline-flex;
	align-items: center;
	gap: 7px;
	min-block-size: 44px;
	padding: 7px 15px;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: var(--ostz-radius-pill);
	background: var(--ostz-bg-raised, #15171e);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
	text-decoration: none;
	transition: border-color .15s ease, color .15s ease, background .15s ease;
}

.ostz-chip:hover,
.ostz-chip:focus-visible {
	border-color: var(--ostz-text-faint, #828a9c);
	color: var(--ostz-text, #f2f4f8);
}

/* ON — the filter that is applied. Border, ground AND weight all change: a chip whose only «on»
   signal is a colour is unreadable to anyone who cannot see that colour, and unreadable to
   everyone in bright light. */
.ostz-chip-on,
.ostz-chip--on {
	border-color: var(--ostz-accent, #00e0d5);
	background: color-mix(in srgb, var(--ostz-accent, #00e0d5) 14%, var(--ostz-bg-raised, #15171e));
	color: var(--ostz-text, #f2f4f8);
	font-weight: var(--ostz-weight-bold);
}

.ostz-chip-name {
	font-size: var(--ostz-text-base);
	font-weight: var(--ostz-weight-medium);
}

.ostz-chip-count {
	padding-inline: 7px;
	border-radius: var(--ostz-radius-sm);
	background: color-mix(in srgb, var(--ostz-text, #f2f4f8) 8%, transparent);
	font-size: var(--ostz-text-xs);
	unicode-bidi: isolate;
}

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

   The row scrolls sideways INSIDE itself rather than wrapping to four lines above the grid; the
   scrollbar is hidden because the chips overflowing is the affordance. */

@media (max-width: 520px) {
	/* ⚠ `min-inline-size: 0` is what makes `overflow-x` mean anything here. A flex/grid ITEM
	   defaults to `min-width: auto`, i.e. it refuses to shrink below its content, so a `nowrap`
	   row simply GROWS and the scroller never engages. MEASURED 2026-09-05 on the search page at
	   390px: the box computed 429px inside a 358px parent and the whole document dragged 19px
	   sideways — an RTL page that scrolls horizontally for no visible reason. */
	.ostz-chips {
		flex-wrap: nowrap;
		gap: 6px;
		min-inline-size: 0;
		max-inline-size: 100%;
		overflow-x: auto;
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
	}

	.ostz-chips::-webkit-scrollbar {
		display: none;
	}

	.ostz-chip {
		flex: none;
		min-block-size: 38px;
		padding: 6px 12px;
	}
}

/* --- ostadeez-m-empty.css --- */
/* EMPTY STATE — the screen that has nothing to show, and therefore has to say what to do instead.

   Six surfaces reach it: no search results, a filter set that matches nothing, an empty cart, the
   account's orders / downloads / payment-methods panels, and a blog archive with no posts.

   ⚠ WHY IT IS A COMPONENT AND NOT A SENTENCE. `.ostz-empty` was declared in
   `ostadeez-filters.css`, which loads only on the shop archive — so `index.php`'s
   «هنوز چیزی اینجا منتشر نشده است» rendered as a bare paragraph, and the account panels rendered
   WooCommerce's `woocommerce-info` with no design at all. The same state, three looks, two of them
   accidental.

   ⚠ NO-DEAD-END is built into the shape, not left to the caller. The actions row is a REQUIRED
   part: a screen that says «چیزی نیست» and stops is an unfinished feature, and the most common way
   that ships is a component that makes the sentence easy and the route optional. Here the route is
   the part with the styling. */

.ostz-empty {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--ostz-space-3);
	padding: var(--ostz-space-12) var(--ostz-space-6);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius);
	background: var(--ostz-bg-raised, #15171e);
	text-align: center;
}

/* A quiet mark, not an illustration. Big enough to signal «this is a state, not a failure»,
   plain enough that it never competes with the route out. */
.ostz-empty-mark {
	display: grid;
	place-items: center;
	inline-size: 54px;
	block-size: 54px;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: 50%;
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xl);
	line-height: 1;
}

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

.ostz-empty-note {
	max-inline-size: 46ch;
	margin: 0;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-md);
	line-height: var(--ostz-leading-snug);
}

/* The row that makes this component worth having. */
.ostz-empty-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--ostz-space-3);
	margin-block-start: var(--ostz-space-2);
}

/* Chips are a legitimate route out — «از اینجا شروع کن» on the empty cart lists real categories —
   so they centre with everything else. */
.ostz-empty .ostz-chips {
	justify-content: center;
}

/* ---------------------------------------------------------------- inline variant

   Inside a panel that already has its own border — an account tab, a filtered column — a second
   framed box inside the first reads as a rendering fault. Same content, no frame. */

.ostz-empty--inline {
	padding: var(--ostz-space-8) var(--ostz-space-4);
	border: 0;
	background: none;
}

/* ---------------------------------------------------------------- the lead-in row

   «از اینجا شروع کن:» followed by chips — the empty cart's own shape, kept because it reads better
   in a flow than a centred block does when the page already has a heading above it. */

.ostz-empty-links {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--ostz-space-3);
	margin-block-start: var(--ostz-space-6);
}

.ostz-empty-lead {
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
}

.ostz-empty-links .ostz-chips {
	margin: 0;
	padding: 0;
	list-style: none;
}

/* --- ostadeez-m-form.css --- */
/* FORM — every field on the site: search, newsletter, contact, cart coupon, checkout, My Account.

   ⚠ TRIMMED 2026-09-05, and what was removed is the point.

   This file was written as a full form SYSTEM — `.ostz-form`, `.ostz-field`, `.ostz-field-label`,
   `.ostz-field-hint`, `.ostz-field-error`, `.ostz-check` and the rest — against markup that does
   not exist. Every form on this site is printed by somebody else (WooCommerce, the checkout-field
   plugin, the contact-form builder), so no element ever wore one of those classes, while
   `modules/ostadeez-b-woo.css` carried a SECOND copy of the same bodies keyed to the platform's
   own class names and did all the actual work. MEASURED: 15 of this file's 18 classes matched
   zero elements across 44 rendered pages, and the file shipped in all six generated bundles.

   What is left is what renders: the control itself, the `:where()` block that reaches the fields
   we cannot class, and the inline/search form. If a form we OWN is ever built, the system comes
   back — written against markup that exists, in one place, not two.

   ⚠ THE `:where()` BLOCK IS THE LOAD-BEARING HALF and it carries no class of ours, so a
   class-based audit reports this file as almost entirely dead. It is not. Read the block before
   touching it. */

/* ---------------------------------------------------------------- the control */

.ostz-input {
	inline-size: 100%;
	min-block-size: 44px;
	padding: 12px 14px;
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-sunken, #111318);
	color: var(--ostz-text, #f2f4f8);
	font-family: inherit;
	font-size: var(--ostz-text-base);
	line-height: var(--ostz-leading-snug);
	/* ⚠ Declared, not inherited. A field is dropped into containers that centre their content —
	   the empty state and the 404 both do — and an input that inherits `text-align: center` puts
	   the caret in the middle of the box and the typed value with it. Measured on the 404. */
	text-align: start;
	transition: border-color .15s ease, box-shadow .15s ease;
}

.ostz-input::placeholder {
	color: var(--ostz-text-faint, #828a9c);
}

.ostz-input:focus,
.ostz-input:focus-visible {
	outline: none;
	border-color: var(--ostz-accent, #00e0d5);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--ostz-accent, #00e0d5) 22%, transparent);
}

.ostz-input:disabled,
.ostz-input[readonly] {
	opacity: .6;
	cursor: not-allowed;
}

/* ---------------------------------------------------------------- the fields we cannot class

   ⚠ Most fields on this site are printed by somebody else — WooCommerce, the checkout-field
   plugin, the contact form builder — with no class of ours on them. Something has to reach them,
   or «the site's field style» is true only of the fields we happen to write ourselves.

   ⚠ `:where()` is the whole trick, and it is the same fix the anchor rule in ostadeez-main.css
   uses. A bare `.ostz select` scores 0-1-1 and SILENTLY beats `.ostz-input` at 0-1-0 — so the
   generic rule would out-specify the component it is supposed to be a fallback for, and any field
   we DID class would still be sized by this block. `:where(.ostz)` contributes zero, so this
   whole selector scores 0-0-1: it reaches everything and loses to anything.

   These values are the component's, not a second opinion. The block this replaces lived in
   `ostadeez-shop.css` at 46px/11px against the component's 44px/12px — one control, two sizes. */

/* uniform */
:where(.ostz) select,
:where(.ostz) textarea,
:where(.ostz) input[type='text'],
:where(.ostz) input[type='search'],
:where(.ostz) input[type='tel'],
:where(.ostz) input[type='email'],
:where(.ostz) input[type='url'],
:where(.ostz) input[type='number'],
:where(.ostz) input[type='password'] {
	inline-size: 100%;
	min-block-size: 44px;
	padding: 12px 14px;
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-sm);
	/* Sunk fields read as editable against a raised panel. */
	background: var(--ostz-bg-sunken, #111318);
	color: var(--ostz-text, #f2f4f8);
	font-family: inherit;
	font-size: var(--ostz-text-base);
	line-height: var(--ostz-leading-snug);
	text-align: start;
}

/* uniform */
:where(.ostz) textarea {
	min-block-size: 120px;
	resize: vertical;
}

/* uniform */
:where(.ostz) input::placeholder,
:where(.ostz) textarea::placeholder {
	color: var(--ostz-text-faint, #828a9c);
}

/* uniform */
:where(.ostz) select:focus,
:where(.ostz) textarea:focus,
:where(.ostz) input:focus {
	outline: none;
	border-color: var(--ostz-accent, #00e0d5);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--ostz-accent, #00e0d5) 22%, transparent);
}

/* ---------------------------------------------------------------- inline form

   A single field with its button on the same row: search, newsletter, coupon, «کد رهگیری». The
   pair is one control to the reader, so it is one component here rather than three ad-hoc copies. */

/*
 * The search form is the inline form with a name of its own, because `get_search_form()` is called
 * from places we do not control — core's own fallbacks, a widget, a block — and the surrounding
 * context cannot be assumed. It carries its own width so it is never handed the full 1160px shell
 * inside an empty state that is otherwise centred.
 */
.ostz-searchform {
	inline-size: 100%;
	max-inline-size: 420px;
	margin-block-start: var(--ostz-space-4);
}

.ostz-inline-form {
	display: flex;
	gap: var(--ostz-space-2);
	align-items: stretch;
}

.ostz-inline-form .ostz-input {
	flex: 1;
	min-inline-size: 0;
}

@media (max-width: 520px) {
	.ostz-inline-form {
		flex-wrap: wrap;
	}

	.ostz-inline-form .ostz-input {
		flex: 1 1 100%;
	}
}

/* --- ostadeez-b-wp.css --- */
/* BRIDGE — WordPress core's own class names → our components.

   The second of the two files allowed to name a class we do not own. `ostadeez-b-woo.css` maps
   WooCommerce; this one maps WordPress itself, and it exists because the two platforms print
   different markup on different templates: the blog pager is core's, the shop pager is
   WooCommerce's, and a single bridge would have to load on every template to catch both.

   ⚠ Everything here is markup we CANNOT add a class to. `the_posts_pagination()` takes no class
   argument for its leaf; the editor writes `wp-caption` and the alignment classes into post
   content at save time; `post_password_required()` prints its own form. Where a filter or an owned
   template does let us add our own class, the rule belongs in the component and not here — that is
   the difference between this file shrinking and this file becoming the stylesheet. */

/* ---------------------------------------------------------------- pagination (no module: rule 7)

   `the_posts_pagination()` renders:
     nav.navigation.pagination > .nav-links > a.page-numbers | span.page-numbers.current | .dots
   `.page-numbers` is the same leaf WooCommerce uses, which is why one component serves both. */

.ostz .navigation.pagination {
	margin-block: var(--ostz-space-8) 0;
}

.ostz .navigation.pagination .nav-links {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--ostz-space-2);
}

/* ⚠ `@mirrors` ostadeez-b-woo.css — see the note there. WordPress core's pager and WooCommerce's
   are the same component wearing two platform class names. */
.ostz .navigation.pagination .page-numbers {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-inline-size: 40px;
	min-block-size: 40px;
	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-dim, #aab0be);
	font-size: var(--ostz-text-base);
	font-weight: var(--ostz-weight-medium);
	line-height: 1;
	text-decoration: none;
	font-variant-numeric: tabular-nums;
	unicode-bidi: isolate;
	transition: border-color .15s ease, color .15s ease, background .15s ease;
}

.ostz .navigation.pagination a.page-numbers:hover,
.ostz .navigation.pagination a.page-numbers:focus-visible {
	border-color: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent, #00e0d5);
}

.ostz .navigation.pagination .page-numbers.current {
	border-color: var(--ostz-accent, #00e0d5);
	background: color-mix(in srgb, var(--ostz-accent, #00e0d5) 14%, var(--ostz-bg-raised, #15171e));
	color: var(--ostz-text, #f2f4f8);
	font-weight: var(--ostz-weight-bold);
}

.ostz .navigation.pagination .page-numbers.dots {
	min-inline-size: 24px;
	border: 0;
	background: none;
	color: var(--ostz-text-faint, #828a9c);
}

.ostz .navigation.pagination .prev,
.ostz .navigation.pagination .next {
	padding-inline: var(--ostz-space-4);
}

/* ---------------------------------------------------------------- editor output inside prose

   Everything the block and classic editors write into `post_content`. These classes are stored in
   the DATABASE, not generated at render, so they cannot be renamed or filtered away — an old post
   carries whatever the editor wrote the day it was saved. */

.ostz .wp-caption {
	max-inline-size: 100%;
	margin-block-end: var(--ostz-space-4);
}

/* uniform — every caption on the site looks the same, and `figcaption` is included on purpose:
   the block editor writes a bare `<figcaption>` with no class at all on an image block, so a rule
   naming only the classes misses the markup a modern post actually contains. The three spellings
   are three editor generations, not three designs. */
.ostz .wp-caption-text,
.ostz .wp-element-caption,
.ostz figcaption {
	margin-block-start: var(--ostz-space-2);
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
	line-height: var(--ostz-leading-snug);
	text-align: center;
}

/*
 * ⚠ `alignleft` / `alignright` are PHYSICAL words baked into old post content, and they mean what
 * they say — an editor who typed «left» meant the left of the page, not the start of the line. So
 * they stay physical. `float` has no logical form anyway.
 */
.ostz .alignleft {
	float: left;
	margin: 0 var(--ostz-space-4) var(--ostz-space-4) 0;
}

.ostz .alignright {
	float: right;
	margin: 0 0 var(--ostz-space-4) var(--ostz-space-4);
}

.ostz .aligncenter {
	display: block;
	margin-inline: auto;
}

.ostz .alignwide,
.ostz .alignfull {
	max-inline-size: 100%;
}

/* The classic gallery shortcode. Still produced by older posts and by the media library UI. */
.ostz .gallery {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--ostz-space-3);
	margin-block-end: var(--ostz-space-4);
}

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

/* uniform — every gallery thumbnail is the same shape. */
.ostz .gallery-icon img {
	inline-size: 100%;
	block-size: auto;
	border-radius: var(--ostz-radius-sm);
}

@media (max-width: 520px) {
	.ostz .gallery {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

/* ---------------------------------------------------------------- password-protected content

   Core prints this form itself, with no filter for the field markup. A visitor who meets it has
   been told «this is protected» and nothing else, so it is given the same panel every other form
   on the site gets rather than a naked input on the page ground. */

.ostz .post-password-form {
	max-inline-size: 480px;
	padding: var(--ostz-space-6);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius);
	background: var(--ostz-bg-raised, #15171e);
}

/* uniform — the form has exactly one label. */
.ostz .post-password-form label {
	display: block;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
}

.ostz .post-password-form input[type='password'] {
	inline-size: 100%;
	min-block-size: 44px;
	margin-block: 6px var(--ostz-space-3);
	padding: 12px 14px;
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-sunken, #111318);
	color: var(--ostz-text, #f2f4f8);
	font-family: inherit;
	font-size: var(--ostz-text-base);
}

/* ---------------------------------------------------------------- core's own helpers

   `.screen-reader-text` is printed by core in the search form, the pagination and several widgets.
   Core's stylesheet is not loaded here, so without this it renders as VISIBLE text — «Search for:»
   above every search box.

   ⚠ Clipped in place, never parked at a negative physical offset: in an RTL document the physical
   left is the scrollable side, so `left: -9999px` gives the page ten thousand pixels of empty
   width and it drags sideways. That has shipped twice in this codebase family. */
.ostz .screen-reader-text {
	position: absolute;
	inline-size: 1px;
	block-size: 1px;
	margin: -1px;
	padding: 0;
	inset-inline-start: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* …but a skip link inside it must come back when it is focused, or a keyboard user is sent to a
   control they cannot see. */
.ostz .screen-reader-text:focus {
	inline-size: auto;
	block-size: auto;
	margin: 0;
	padding: var(--ostz-space-2) var(--ostz-space-3);
	clip-path: none;
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	z-index: 100;
}

/* A sticky post is a listing state, so it is marked on the card rather than on the page. */
.ostz .ostz-entry.sticky .ostz-entry-link {
	border-color: var(--ostz-accent, #00e0d5);
}

/* --- ostadeez-m-crumb.css --- */
/* BREADCRUMB — the trail that says where a page sits.

   ⚠ THE FOURTH «shell component parked in a section sheet» found in this pass, and the one with a
   visible symptom nobody had reported. The base — `.ostz-crumb`, `-list`, `-item`, `-link` — was
   declared in `ostadeez-shop.css`, which loads on WooCommerce views and the front page. But
   `ostz_breadcrumb_entry()` prints a trail on every single ARTICLE and every project update, and
   those templates do not load the shop skin. `ostadeez-main.css` re-declared just enough of it
   (`.ostz-crumb--entry` plus its list) that the trail looked roughly right, so the missing piece
   was invisible: `.ostz-crumb-link` had no rule at all on an article, which meant no tap-target
   padding and no hover — a 17px text link on the page most likely to be reached from a search
   result.

   That is also why the entry variant lived in a different file from the base it varies: a variant
   cannot be in a sheet the base does not reach. Base and both variants are here now, and both
   copies were deleted. */

.ostz-crumb {
	margin-block-end: var(--ostz-space-3);
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
}

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

/* ⚠ The separator is a RIGHT-pointing single quote in an RTL document, which is correct: `›` is a
   neutral character, so the bidi algorithm mirrors it to point the way the text runs. Replacing it
   with `‹` — which reads correctly in a source file — makes it point backwards on the page. */
.ostz-crumb-item + .ostz-crumb-item::before {
	content: '›';
	margin-inline-end: var(--ostz-space-2);
	color: var(--ostz-line-strong, #333846);
}

/* The padding is what lifts a 17px-tall text link to a 24px target without changing how it looks;
   the negative inline margin keeps the trail reading as tight as it did. */
.ostz-crumb-link {
	display: inline-flex;
	align-items: center;
	min-block-size: 24px;
	padding-inline: 4px;
	margin-inline: -4px;
	color: inherit;
	text-decoration: none;
}

.ostz-crumb-link:hover,
.ostz-crumb-link:focus-visible {
	color: var(--ostz-text-dim, #aab0be);
}

/* PRODUCT — the product page prints its trail above WooCommerce's two-column summary, so it needs
   the spacing the page-head block would otherwise have given it. */
.ostz-crumb--product {
	margin-block-end: var(--ostz-space-4);
}

/* ENTRY — an article's trail follows the article's measure, not the page shell's, or it starts
   4cm to the side of the heading it belongs to. */
.ostz-crumb--entry {
	max-inline-size: var(--ostz-measure);
	margin-inline: auto;
	padding-block-start: var(--ostz-space-6);
}

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

   The trail scrolls sideways inside itself rather than wrapping to three lines above the title. */

@media (max-width: 520px) {
	.ostz-crumb-list {
		flex-wrap: nowrap;
		overflow-x: auto;
		white-space: nowrap;
		font-size: var(--ostz-text-xs);
		scrollbar-width: none;
	}

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

/* --- ostadeez-m-header.css --- */
/* OSTADEEZ — THE HEADER: announcement bar, wordmark, nav, header actions.
   Split out of `ostadeez-main.css` on 2026-09-22 (theme 0.50.0), owner's rule: a thing that
   renders on every page is a COMPONENT with its own stylesheet, not a chapter of the token sheet.

   `ostadeez-main.css` is layers 1 and 2 of this architecture — tokens and element defaults. The
   header is layer 4. It is on every page, so it still LOADS everywhere; what changes is that it
   can now be found, measured and changed in one file instead of inside 260 lines of somebody
   else's sheet. The article and the breadcrumb left the same file for the same reason on
   2026-09-03.

   ⚠ ITS MOBILE RULES CAME WITH IT. A component whose media query stays behind is a component that
   is correct on desktop and wrong on phones, and nobody reads two files to find out. */

/* ---------------------------------------------------------------- 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.
 */
/*
 * The uploaded site logo. Before this rule existed the theme had NO size rule for it at all:
 * the first real logo rendered at its own 593px and pushed the header to 774px tall. The cap is
 * on HEIGHT with auto width, so a wordmark of any aspect keeps its proportion.
 *
 * The mark is black ink, so the dark scheme swaps to the inverted file instead of inverting in
 * CSS — a filter would also invert any future colour in the mark.
 */
.custom-logo-link {
	display: inline-flex;
	align-items: center;
}

.custom-logo {
	height: auto;
	max-height: 34px;
	width: auto;
	max-width: min(190px, 46vw);
}

[data-theme="dark"] .custom-logo {
	filter: invert(1);
}

.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;
}

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

	/* ⚠ THESE TWO WERE OUTSIDE ANY MEDIA QUERY until 2026-09-22 — see the note in
	   `ostadeez-main.css`. The announcement bar was single-line and clipped at EVERY width. */
	.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;
	}
}

/* --- ostadeez-m-footer.css --- */
/* OSTADEEZ — THE FOOTER.
   Split out of `ostadeez-main.css` on 2026-09-22 (theme 0.50.0), same reasoning as the header:
   on every page, therefore a component, therefore its own file. Its one mobile rule came with it. */

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

@media (max-width: 900px) {

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

/* --- ostadeez-m-home.css --- */
/* OSTADEEZ — THE HOME PAGE v2 (front-page.php). Replaces ostadeez-m-hero.css (2026-09-27).
   A TEMPLATE section loaded by a `when` callback on the front page only. Every class here is
   printed by front-page.php or toggled by assets/js/ostadeez-home.js. Structure and values from
   prd/ostadeez/wireframes/ostadeez-wf-home-v2.html. The dark bands use fixed ink on purpose —
   they are dark in both schemes, like a photograph is. */

/* ---------------------------------------------------------------- page frame */

/* Full-bleed sections WITHOUT `100vw` (it counts the scrollbar and makes the page wider than the
   viewport — measured on the project page). `.ostz-main` gives up its column on the home page;
   each block that wants the column wears `.ostz-wrap`. */
.home .ostz-main {
	max-inline-size: none;
	padding-block-start: 0;
	padding-inline: 0;
}

.ostz-home-sec {
	padding-block: var(--ostz-space-12) 0;
}

/* Every section head on this page keeps 16px from what it introduces — the Instagram grid touched
   its head (measured gap 0). */
.ostz-home-sec > .ostz-section-head {
	margin-block-end: var(--ostz-space-4);
}

/* margin-block-end: the kicker sat ON its heading, measured gap 0 (پویش ظاهر 2026-09-27). */
.ostz-home-kicker {
	margin: 0 0 var(--ostz-space-1);
	color: var(--ostz-accent, #00e0d5);
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-bold);
}

.ostz-home-kicker-ink {
	color: #e8a07c;
}

.ostz-home-btn-light {
	border-color: transparent;
	background: #f2f4f8;
	color: #0b0c10;
	font-weight: var(--ostz-weight-bold);
}

.ostz-home-btn-light:hover {
	background: #ffffff;
	color: #0b0c10;
}

.ostz-home-btn-ghost {
	border-color: rgb(242 244 248 / 0.6);
	background: transparent;
	color: #f2f4f8;
}

.ostz-home-btn-ghost:hover {
	border-color: #f2f4f8;
	color: #ffffff;
}

/* ---------------------------------------------------------------- 1 · hero */

.ostz-home-hero {
	position: relative;
	display: grid;
	align-items: end;
	min-block-size: min(86vh, 820px);
	overflow: hidden;
	background: #0b0c10;
	color: #f2f4f8;
}

.ostz-home-hero-media {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

.ostz-home-hero::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient(to top, rgb(8 9 12 / 0.88) 0%, rgb(8 9 12 / 0.5) 45%, rgb(8 9 12 / 0.1) 100%);
}

.ostz-home-hero-body {
	position: relative;
	z-index: 1;
	inline-size: 100%;
	padding-block: var(--ostz-space-16);
}

.ostz-home-hero-title {
	max-inline-size: 16ch;
	margin: 0 0 var(--ostz-space-4);
	font-size: clamp(30px, 6vw, 64px);
	font-weight: var(--ostz-weight-black);
	line-height: 1.15;
}

.ostz-home-hero-sub {
	max-inline-size: 52ch;
	margin: 0 0 var(--ostz-space-6);
	color: #d7dbe3;
	font-size: var(--ostz-text-lg);
}

.ostz-home-hero-cta {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ostz-space-3);
	margin: 0;
}

.ostz-home-hero-pause {
	position: absolute;
	z-index: 2;
	inset-block-end: var(--ostz-space-6);
	inset-inline-end: var(--ostz-space-6);
	display: grid;
	place-items: center;
	inline-size: 44px;
	block-size: 44px;
	border: 1px solid rgb(242 244 248 / 0.6);
	border-radius: 50%;
	background: rgb(8 9 12 / 0.55);
	color: #f2f4f8;
	font: inherit;
	cursor: pointer;
}

.ostz-home-hero-pause:focus-visible,
.ostz-home-arrow:focus-visible,
.ostz-home-dot:focus-visible {
	outline: 3px solid var(--ostz-accent, #00e0d5);
	outline-offset: 2px;
}

/* ⚠ MEASURED 2026-09-27 at 375×812: at 78vh the hero ended at y=851 and its two buttons sat
   UNDER the fixed footer bar (top at 755) on first view. 62vh keeps them above it. */
@media (max-width: 720px) {
	.ostz-home-hero {
		min-block-size: 62vh;
	}

	.ostz-home-hero-body {
		padding-block: var(--ostz-space-8);
	}
}

/* ---------------------------------------------------------------- 2 · category circles */

.ostz-home-cats {
	display: flex;
	gap: var(--ostz-space-6);
	margin: 0;
	padding: 0 0 var(--ostz-space-2);
	list-style: none;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
}

.ostz-home-cat {
	flex: 0 0 auto;
	scroll-snap-align: start;
}

.ostz-home-cat-link {
	display: grid;
	justify-items: center;
	gap: var(--ostz-space-2);
	inline-size: 120px;
	color: var(--ostz-text, #f2f4f8);
	font-weight: var(--ostz-weight-bold);
	text-align: center;
	text-decoration: none;
}

.ostz-home-cat-img {
	position: relative;
	display: block;
	inline-size: 112px;
	block-size: 112px;
	overflow: hidden;
	border: 3px solid var(--ostz-bg-raised, #15171e);
	border-radius: 50%;
	background: var(--ostz-bg-sunken, #111318);
	box-shadow: 0 0 0 1px var(--ostz-line-strong, #333846);
}

.ostz-home-cat-pic {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

.ostz-home-cat-link:hover .ostz-home-cat-img {
	box-shadow: 0 0 0 2px var(--ostz-accent, #00e0d5);
}

/* ---------------------------------------------------------------- 3 · banner slider */

.ostz-home-slider {
	padding-block-start: var(--ostz-space-12);
}

.ostz-home-slides {
	display: flex;
	margin: 0;
	padding: 0;
	list-style: none;
	overflow-x: auto;
	scroll-snap-type: x mandatory;
	scrollbar-width: none;
}

.ostz-home-slides::-webkit-scrollbar {
	display: none;
}

.ostz-home-slide {
	flex: 0 0 100%;
	scroll-snap-align: start;
}

.ostz-home-slide-link {
	position: relative;
	display: block;
	aspect-ratio: 21 / 7;
	overflow: hidden;
	background: var(--ostz-bg-sunken, #111318);
	color: #f2f4f8;
	text-decoration: none;
}

.ostz-home-slide-img {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

/* ⚠ 0.75→0.1 over 60% MEASURED 3.56:1 on the caption text at 768 (the caption reaches further into
   the light part of the photo on a narrower screen). Stronger and longer now; re-measured. */
.ostz-home-slide-link::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient(to left, rgb(8 9 12 / 0.85) 0%, rgb(8 9 12 / 0.6) 50%, rgb(8 9 12 / 0.15) 100%);
}

.ostz-home-slide-cap {
	position: absolute;
	z-index: 1;
	inset: 0;
	display: flex;
	flex-direction: column;
	justify-content: center;
	gap: var(--ostz-space-2);
}

.ostz-home-slide-title {
	max-inline-size: 18ch;
	font-size: clamp(22px, 3.6vw, 44px);
	font-weight: var(--ostz-weight-black);
	line-height: 1.2;
}

.ostz-home-slide-text {
	max-inline-size: 40ch;
	color: #d7dbe3;
	font-size: var(--ostz-text-md);
}

.ostz-home-slider-ctl {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: var(--ostz-space-2);
	padding-block-start: var(--ostz-space-3);
}

.ostz-home-arrow {
	display: grid;
	place-items: center;
	inline-size: 36px;
	block-size: 36px;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: 50%;
	background: var(--ostz-bg-raised, #15171e);
	color: var(--ostz-text, #f2f4f8);
	font: inherit;
	cursor: pointer;
}

.ostz-home-dot {
	inline-size: 28px;
	block-size: 28px;
	padding: 0;
	border: 0;
	background: none;
	cursor: pointer;
}

.ostz-home-dot::before {
	content: '';
	display: block;
	inline-size: 10px;
	block-size: 10px;
	margin: auto;
	border-radius: 5px;
	background: var(--ostz-line-strong, #333846);
}

.ostz-home-dot[aria-current="true"]::before {
	inline-size: 24px;
	background: var(--ostz-accent, #00e0d5);
}

@media (max-width: 720px) {
	.ostz-home-slide-link {
		aspect-ratio: 4 / 5;
	}

	.ostz-home-slide-link::after {
		background: linear-gradient(to top, rgb(8 9 12 / 0.8) 0%, rgb(8 9 12 / 0.1) 60%);
	}

	.ostz-home-slide-cap {
		justify-content: flex-end;
		padding-block-end: var(--ostz-space-8);
	}
}

/* ---------------------------------------------------------------- 4 · product rails */

/* 0-3-1 on purpose: it must beat the shop sheet's four-column `.ostz.home ul.products` (0-2-1)
   and its two mobile overrides. The card inside is the shared component, untouched. */
.ostz.home ul.products.ostz-home-rail {
	display: grid;
	grid-auto-flow: column;
	grid-template-columns: none;
	grid-auto-columns: minmax(220px, calc((100% - 3 * var(--ostz-space-4)) / 4));
	gap: var(--ostz-space-4);
	margin: 0;
	padding: 0 0 var(--ostz-space-2);
	overflow-x: auto;
	scroll-snap-type: x mandatory;
}

.ostz.home ul.products.ostz-home-rail > li {
	scroll-snap-align: start;
	min-inline-size: 0;
}

@media (max-width: 900px) {
	.ostz.home ul.products.ostz-home-rail {
		grid-template-columns: none;
		grid-auto-columns: 62%;
	}
}

/* ---------------------------------------------------------------- 5 · nostalgia band */

.ostz-home-band {
	display: grid;
	grid-template-columns: 1.4fr 1fr;
	margin-block-start: var(--ostz-space-12);
	background: #0b0c10;
	color: #f2f4f8;
}

.ostz-home-band-img {
	position: relative;
	min-block-size: 440px;
	background: #15171e;
}

.ostz-home-band-pic {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

.ostz-home-band-body {
	align-self: center;
	padding: var(--ostz-space-12);
}

.ostz-home-band-title {
	margin: var(--ostz-space-2) 0 var(--ostz-space-3);
	font-size: clamp(26px, 3.6vw, 42px);
	font-weight: var(--ostz-weight-black);
	line-height: 1.2;
}

.ostz-home-band-text {
	margin: 0 0 var(--ostz-space-6);
	color: #c3c8d2;
}

/* ---------------------------------------------------------------- 6 · instagram grid */

.ostz-home-ugc {
	display: grid;
	grid-template-columns: repeat(6, minmax(0, 1fr));
	gap: var(--ostz-space-3);
	margin: 0;
	padding: 0;
	list-style: none;
}

.ostz-home-ugc-item {
	position: relative;
	aspect-ratio: 1;
	overflow: hidden;
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-sunken, #111318);
}

.ostz-home-ugc-img {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

/* ---------------------------------------------------------------- 7 · shop by category, dark */

.ostz-home-dark {
	margin-block-start: var(--ostz-space-12);
	padding-block: var(--ostz-space-12);
	background: #0b0c10;
	color: #f2f4f8;
}

.ostz-home-dark-in {
	display: grid;
	grid-template-columns: 1fr 3fr;
	gap: var(--ostz-space-8);
	align-items: center;
}

.ostz-home-dark-title {
	margin: var(--ostz-space-2) 0 var(--ostz-space-6);
	font-size: clamp(24px, 3vw, 34px);
	font-weight: var(--ostz-weight-black);
	line-height: 1.2;
}

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

.ostz-home-dark-link {
	display: grid;
	gap: var(--ostz-space-1);
	color: #f2f4f8;
	text-decoration: none;
}

.ostz-home-dark-img {
	position: relative;
	display: block;
	aspect-ratio: 4 / 5;
	margin-block-end: var(--ostz-space-2);
	overflow: hidden;
	border-radius: var(--ostz-radius);
	background: #15171e;
}

.ostz-home-dark-pic {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
	transition: transform 0.35s ease;
}

.ostz-home-dark-link:hover .ostz-home-dark-pic {
	transform: scale(1.04);
}

.ostz-home-dark-name {
	font-size: var(--ostz-text-lg);
	font-weight: var(--ostz-weight-black);
}

.ostz-home-dark-count {
	color: #aab0be;
	font-size: var(--ostz-text-sm);
}

/* ---------------------------------------------------------------- 8 · statement */

.ostz-home-statement {
	margin-block-start: var(--ostz-space-12);
	padding-block: var(--ostz-space-16);
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
}

.ostz-home-statement-text {
	max-inline-size: 30ch;
	margin: 0 auto;
	font-size: clamp(22px, 3.6vw, 40px);
	font-weight: var(--ostz-weight-black);
	line-height: 1.45;
	text-align: center;
}

/* ---------------------------------------------------------------- 9 · faq */

.ostz-home-faq {
	max-inline-size: 900px;
}

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

@media (max-width: 900px) {
	.ostz-home-band,
	.ostz-home-dark-in {
		grid-template-columns: 1fr;
	}

	.ostz-home-band-img {
		min-block-size: 260px;
	}

	.ostz-home-band-body {
		padding: var(--ostz-space-8) var(--ostz-space-4);
	}

	.ostz-home-ugc {
		grid-template-columns: repeat(3, minmax(0, 1fr));
	}

	.ostz-home-dark-grid {
		gap: var(--ostz-space-2);
	}
}

@media (prefers-reduced-motion: reduce) {
	.ostz-home-dark-pic {
		transition: none;
	}
}

/* --- ostadeez-m-entry-card.css --- */
/* ENTRY CARD — an article in a listing. The blog archive, the category and tag archives, the
   author and date archives, and the article half of the search results.

   ⚠ SIMPLIFIED 2026-09-22, owner's decision («یک طرح ساده انتخاب کن»). What it was: a framed card
   with a border, a raised ground, a radius, a hover lift, a category kicker, a three-line excerpt,
   a reading-time chip, a lettered placeholder for posts with no picture, and a separate WIDE lead
   variant for the first card on the blog. Nine moving parts for a link to an article.

   What it is now: a picture, a title, a date. No frame, no hover lift, no second arrangement —
   ONE shape on every listing, which is also what makes «one source» true rather than merely
   claimed. The parts that were removed are named here so nobody re-adds one by accident and so
   the decision can be reversed knowing exactly what it cost:
     · the kicker (the category line) — the term still reaches the reader on the article itself
     · the excerpt — the title carries the promise; a three-line summary under every card is what
       made a list of six posts read as a wall
     · the reading time — a guess presented as a fact
     · `--empty` — a post with no picture now simply has no picture, instead of a fake grey block
       with a letter in it, which is the shape a BROKEN image has
     · `--wide` — the lead variant, and with it the circular-height trap its own comment recorded

   ⚠ WHY BORDERLESS IS NOT «less designed»: the product card is framed because it is a sales unit
   that must read as an object you can pick up. An article is text. Framing both the same way is
   what made the blog look like a shop with the prices missing. The two cards are deliberately NOT
   the same component and must never be merged — `ostadeez-shop.css` owns the product card and
   nothing about it may be declared here. */

/* ---------------------------------------------------------------- the grid */

.ostz-list {
	display: grid;
	grid-template-columns: repeat(3, minmax(0, 1fr));
	gap: var(--ostz-space-6);
	margin-block: var(--ostz-space-6) 0;
	padding: 0;
	list-style: none;
}

@media (max-width: 900px) {
	.ostz-list {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (max-width: 520px) {
	.ostz-list {
		grid-template-columns: 1fr;
		gap: var(--ostz-space-6);
	}
}

/* ---------------------------------------------------------------- the card */

/* ⚠ `min-inline-size: 0` stays, and it is the one line here that is not about the design. A grid
   item's `min-width` is `auto`, i.e. its content's intrinsic minimum, so ONE article whose title or
   excerpt held a pasted URL pinned the single-column track at 552px inside a 390px viewport
   (MEASURED 2026-09-21). Removing it re-opens that. */
.ostz-entry {
	display: flex;
	min-inline-size: 0;
	margin: 0;
	block-size: 100%;
}

/*
 * The whole card is ONE anchor. Flow content inside `<a>` is valid, it gives the reader the
 * biggest possible target, and it leaves exactly one link in the accessibility tree for one
 * destination instead of the image/title/«ادامه» trio that every second WordPress theme ships.
 *
 * Consequence, stated so it is not re-litigated: nothing inside the card may be a link of its own.
 */
.ostz-entry-link {
	display: flex;
	flex-direction: column;
	gap: var(--ostz-space-3);
	inline-size: 100%;
	min-inline-size: 0;
	color: inherit;
	text-decoration: none;
}

/* ---------------------------------------------------------------- media

   3:2, not the product card's 4:5. A square crops a photograph of a place or a person to its
   middle; an article's image is nearly always landscape and is nearly always the thing that made
   the reader stop. The ratio is on the IMAGE as well as on the box: `aspect-ratio` on a block
   container is only a suggestion until the content overflows it. */

.ostz-entry-media {
	display: block;
	overflow: hidden;
	border-radius: var(--ostz-radius);
	background: var(--ostz-bg-sunken, #111318);
	aspect-ratio: 3 / 2;
}

/* A post with no picture keeps the card's shape rather than collapsing its row. MEASURED: without
   this the card came out 57px beside 310px neighbours in the same grid row. Flat, no glyph, no
   letter — a mark inside it is what made the old placeholder read as a failed image. */
.ostz-entry-media--blank {
	background:
		linear-gradient(
			135deg,
			color-mix(in srgb, var(--ostz-accent, #00e0d5) 10%, transparent),
			transparent 62%
		),
		var(--ostz-bg-sunken, #111318);
}

.ostz-entry-img {
	display: block;
	inline-size: 100%;
	block-size: 100%;
	aspect-ratio: 3 / 2;
	object-fit: cover;
	transition: opacity .18s ease;
}

.ostz-entry-link:hover .ostz-entry-img,
.ostz-entry-link:focus-visible .ostz-entry-img {
	opacity: .88;
}

/* ---------------------------------------------------------------- body */

.ostz-entry-body {
	display: flex;
	flex-direction: column;
	gap: var(--ostz-space-2);
	min-inline-size: 0;
}

/*
 * Two lines, clamped. An unclamped title is what makes a grid of cards ragged, and a title that
 * needs more than two lines at this size is a title problem, not a layout problem.
 *
 * `overflow-wrap` is the 2026-09-21 fix: without it a pasted URL in a title cannot break and
 * becomes the track's floor.
 */
.ostz-entry-title {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	line-clamp: 2;
	overflow: hidden;
	margin: 0;
	color: var(--ostz-text, #f2f4f8);
	font-size: var(--ostz-text-lg);
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-snug);
	overflow-wrap: anywhere;
	transition: color .18s ease;
}

.ostz-entry-link:hover .ostz-entry-title,
.ostz-entry-link:focus-visible .ostz-entry-title {
	color: var(--ostz-accent, #00e0d5);
}

/* The date, alone. `isolate` because a Persian date beside Latin punctuation otherwise reorders. */
.ostz-entry-date {
	unicode-bidi: isolate;
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
}

/* --- ostadeez-m-badge.css --- */
/* BADGE — the state marker that rides a product card's media box.
   First component migrated out of the legacy sheets, 2026-09-02.
   Replaces: ostadeez-shop.css `.ostz-badge` / `-soon` / `-sold` (deleted there in the same edit).
   Extended by: ostadeez-drop.css `.ostz-badge-drop`, which keeps working — it only re-colours.

   Two measured defects fixed here, both invisible in dark mode:
   - the scrim was a hardcoded `rgba(0,0,0,.55)` with no light-scheme value, while the TEXT colour
     came from tokens that do flip. Light mode therefore rendered dark ink on a dark scrim:
     «پایان یافت» 1.01:1 and «به‌زودی» 1.09:1. The scrim is now a local custom property with a
     value per scheme.
   - «پایان یافت» used --ostz-text-faint and measured 4.38:1 at 11px even in dark mode. It uses
     --ostz-text-dim now. Deliberate visual change, recorded in the project map. */

.ostz-badge {
	--ostz-badge-scrim: rgba(0, 0, 0, .55);

	position: absolute;
	inset-block-start: 10px;
	inset-inline-start: 10px;
	z-index: 2;
	padding: 3px 10px;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: 16px;
	background: var(--ostz-badge-scrim);
	font-size: var(--ostz-text-xs);
}

[data-theme='light'] .ostz-badge {
	--ostz-badge-scrim: rgba(255, 255, 255, .82);
}

/* ⚠ These were TWO copies of the same body under two spellings, `--soon` and `-soon`, described
   in a comment as aliases kept for safety. Measured 2026-09-05: `ostz_card_badge()` writes the
   SINGLE-dash names and nothing anywhere writes the double-dash ones, so the "alias" was the dead
   half and the two copies were free to drift apart. One name, one declaration. */
.ostz-badge-soon {
	border-color: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent, #00e0d5);
}

.ostz-badge-sold {
	color: var(--ostz-text-dim, #aab0be);
}

/* Back-order: the card must say the wait out loud. Warm, not alarming — this is a state the
   brand sells in, not a failure. */
.ostz-badge-backorder {
	border-color: var(--ostz-accent-2, #ff2fb3);
	color: var(--ostz-text, #f2f4f8);
}

/* --- ostadeez-m-product-card.css --- */
/* OSTADEEZ — THE PRODUCT CARD. One component, one file.
   Migrated 2026-09-22 (theme 0.47.0), owner's decision: a card renders on more than one template,
   so it is a COMPONENT and it owns its appearance; the page owns only the grid it sits in.

   Replaces, and all of them were DELETED in the same edit — the deletion is the point, a module
   that merely restates a legacy rule has doubled the problem:
     · ostadeez-shop.css    «product card» + the card half of «price»  (the shell)
     · ostadeez-archive.css «the card»                                 (cat/rating/colours/price/button)
     · ostadeez-pdp.css     «product cards»                            (a SECOND full shell — see below)
     · ostadeez-scheme.css  `.ostz-card .dm-waitlist-btn`              (a card rule in the plugin skin)

   ⚠ WHAT THE FOURTH HOME COST, because it is the reason this file exists rather than «tidiness».
   `ostadeez-pdp.css` declared its own card on `.related .product` / `.upsells .product` — border,
   radius, background, hover — on the `<li>` that ALREADY CONTAINS `<article class="ostz-card">`
   with its own border and background. Every related and up-sell card on a product page therefore
   rendered as TWO NESTED FRAMES. Nothing errored, and the block's own header said it was scoped
   «on purpose» — which was true of the intent and false of the result.
   Same block also re-declared the grid that `ostadeez-shop.css` had already fixed (`auto-fit`,
   capped at 268px, written against the «a lonely card became a banner» bug) as a fixed 4 columns.
   `ostadeez-pdp` depends on `ostadeez-shop`, so it printed LAST and won: that fix had never once
   been in effect on the page it was written for.
   And most of that block styled `.woocommerce-loop-product__title` — a class this theme REMOVES
   and replaces with `.ostz-card-title`, so it matched nothing at all.

   RENDERED BY: `inc/ostadeez-woocommerce.php` (shell + badge) and `inc/ostadeez-archive.php`
   (category, rating, colour dots, discount percentage). Both print on EVERY surface since 0.47.0.
   GRIDS ARE NOT HERE. A grid belongs to the template that draws it — see each page sheet. */

/* ---------------------------------------------------------------- the shell */

.ostz-card {
	position: relative; /* the badge's anchor */
	display: flex;
	flex-direction: column;
	min-inline-size: 0;
	block-size: 100%;
	overflow: hidden;
	border: 1px solid var(--ostz-line);
	border-radius: var(--ostz-radius-lg);
	background: var(--ostz-bg-raised);
	transition: border-color .18s ease, transform .18s ease, box-shadow .18s ease;
}

.ostz-card:hover {
	border-color: var(--ostz-line-strong);
	transform: translateY(-4px);
	box-shadow: 0 2px 4px rgba(28, 26, 23, .06), 0 18px 34px -18px rgba(28, 26, 23, .28);
}

/* ---------------------------------------------------------------- the media

   ⚠ 4:5, matching the product gallery, so a garment is the same shape wherever it appears. The
   ratio is carried by THIS box and the image fills it — `aspect-ratio` on a block container is
   only a suggestion until its content is constrained, which is how a 600×750 upload used to
   stretch the box to 251px. */
.ostz-card-media {
	position: relative;
	display: block;
	aspect-ratio: 4 / 5;
	overflow: hidden;
	border-block-end: 1px solid var(--ostz-line);
	background: var(--ostz-bg-sunken);
}

/* ⚠ FIXED IN THE MIGRATION, and it was a real contradiction rather than a tidy-up: the media box
   asked for 4:5 while the image inside it asked for `aspect-ratio: 1`, so every card photograph
   was cropped square inside a 4:5 window and the two comments in the old file each described a
   different intention. The image now simply FILLS its box, which is what both of them wanted. */
.ostz-card-img,
.ostz-card-img-empty {
	display: block;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

/* Out of stock: dimmed, never hidden — a product the shop really sells must stay findable. It
   lives with the base it varies, or the variant applies on surfaces the base never reaches. */
li.product.outofstock .ostz-card-media {
	opacity: .55;
}

/* ---------------------------------------------------------------- the body */

.ostz-card-body {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: 5px;
	min-inline-size: 0;
	padding: 12px 14px 14px;
}

/* ⚠ `flex: 1` above plus `margin-block-start: auto` here is what bottom-aligns the CTAs across a
   row. Without it a two-line title pushed its own button 20px below its neighbours' — measured,
   three buttons at y=1089 and one at 1109 — and a row of buttons that do not line up reads as a
   rendering fault rather than as a grid. */
.ostz-card-body > .button,
.ostz-card-body > .ostz-card-actions,
.ostz-card-body > .dm-eng-archive-actions:not(:empty) {
	margin-block-start: auto;
}

/* The waitlist row rides directly under the button rather than being pushed down itself, or the
   two actions separate and stop reading as one group. */
.ostz-card-body > .button ~ .dm-eng-archive-actions {
	margin-block-start: 0;
}

/* ⚠ The engagement plugin prints this wrapper on EVERY card, empty on the ones with no waitlist.
   An empty flex child still takes the row gap, so it added a stray 9px under the button on most
   of the grid — and it is also what stopped the rule above bottom-aligning the right element. */
.ostz-card-body > .dm-eng-archive-actions:empty {
	display: none;
}

/* ---------------------------------------------------------------- the title

   13px, the shop's own step. ⚠ The padding is not decoration: the title is the card's only real
   link (the media above it is `aria-hidden` with `tabindex="-1"`) and its line box measures 20.8px,
   i.e. under the 24px tap floor on its own. 20.8 + 2 + 2 = 24.8. A padding tuned against one font
   size is a trap the next size change re-opens, so the number is stated against the measurement. */
.ostz-card-title {
	margin: 0;
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-snug);
}

.ostz-card-link {
	display: inline-block;
	padding-block: 2px;
	color: var(--ostz-text);
	text-decoration: none;
}

.ostz-card-link:hover {
	color: var(--ostz-accent);
}

/* ---------------------------------------------------------------- the content the archive used
   to keep to itself

   ⚠ These four printed ONLY on the shop and the product taxonomies until 0.47.0, because the hooks
   that print them were behind `ostz_is_archive_screen()`. The front page and the related strip
   therefore showed WooCommerce's own «تخفیف!» with no number, and no category, rating or colours
   at all — one shop, two vocabularies, and the weaker one on the surfaces people land on first. */

.ostz-card-cat {
	margin: 0;
	color: var(--ostz-text-faint);
	font-size: var(--ostz-text-xs);
	font-weight: var(--ostz-weight-bold);
}

.ostz-card-rate {
	display: flex;
	align-items: center;
	gap: 6px;
	margin: 0;
	min-inline-size: 0;
	color: var(--ostz-text-faint);
	font-size: var(--ostz-text-xs);
}

.ostz-card-rate .star-rating { /* uniform — every star row in a card is identical */
	margin: 0;
	color: var(--ostz-amber);
}

.ostz-card-colours {
	display: flex;
	align-items: center;
	gap: 6px;
	margin: 0;
	min-inline-size: 0;
}

.ostz-card-dot {
	display: block;
	flex: 0 0 auto;
	inline-size: 16px;
	block-size: 16px;
	border: 1px solid var(--ostz-line-strong);
	border-radius: 50%;
	background: var(--ostz-bg-sunken);
}

.ostz-card-colours-n {
	color: var(--ostz-text-faint);
	font-size: var(--ostz-text-xs);
}

/* ---------------------------------------------------------------- the price

   ⚠ WooCommerce prints the price with its own class and no class of ours, so it is reached
   through `.ostz-card .price`. That is the one platform name in this file and it is deliberate:
   the alternative is a filter that does not exist on this hook. */

.ostz-card .price {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 8px;
	min-inline-size: 0;
	margin: auto 0 0;
	padding-block-start: 6px;
	color: var(--ostz-text);
	font-size: var(--ostz-text-md);
	font-weight: var(--ostz-weight-black);
}

.ostz-card .price del {
	color: var(--ostz-text-faint);
	/* ⚠ A STEP, not `.8em`. The old rule was relative to the price's 15px, i.e. 12px — which IS a
	   step (`xs`), so the value does not change; what changes is that the architecture check can
	   now see it, and that a future price-size change cannot silently drag this under the 12px
	   floor the way a relative unit would. */
	font-size: var(--ostz-text-xs);
	font-weight: var(--ostz-weight-medium);
	text-decoration: line-through;
	opacity: 1;
}

/* ⚠ WooCommerce wraps the sale price in `<ins>`, whose browser default is an underline; with its
   stylesheet dequeued nothing ever reset it, so every discounted price read as clickable. The
   strike on `<del>` is MEANING and stays; the underline on `<ins>` is a default nobody chose. */
.ostz-card .price ins {
	background: none;
	color: inherit;
	text-decoration: none;
}

.ostz-card .ostz-card-actions {
	margin: 0;
}

/* ---------------------------------------------------------------- the action

   The button is the button COMPONENT at its compact size (modules/ostadeez-m-button.css);
   `ostz_loop_button_class()` writes `ostz-btn ostz-btn--compact` in PHP and marks the purchasable
   one `--primary`. Only the card's own metrics are here. */
.ostz-card .button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	margin-block-start: 10px;
	min-block-size: 40px;
	border-radius: var(--ostz-radius);
	font-size: var(--ostz-text-xs);
	font-weight: var(--ostz-weight-bold);
}

/* The waitlist control sits under a compact button and matches its size. Moved here from
   `ostadeez-scheme.css` on 2026-09-22: a rule about the CARD belongs to the card, not to the
   sheet that skins plugin surfaces — and that sheet loads on templates with no card at all. */
.ostz-card .dm-waitlist-btn {
	inline-size: 100%;
	min-block-size: 34px;
	font-size: var(--ostz-text-xs);
}

/* ---------------------------------------------------------------- quiet variant

   On the product page the card's action is SECONDARY: the page already has a primary button, and
   two equally loud buttons on one screen means neither answers «what do I press». The variant
   lives with its base (the old home was the product sheet, i.e. a variant in a sheet its base did
   not reach), and the page turns it on by adding the class — never by re-declaring the button. */
/* ⚠ `.ostz` IS PART OF THE SELECTOR ON PURPOSE, and the rig is what proved it. The WooCommerce
   bridge states the primary button as `.ostz a.button.ostz-btn--primary` (0-3-1) and, being a
   bridge, it prints AFTER this module — so the plain `.ostz-card--quiet a.button…` at the same
   0-3-1 lost the tie and the card's button rendered full terracotta, rgb(156,66,33), measured on
   the product page. One extra class makes it 0-4-1 and the variant wins on merit instead of on
   load order. No `!important`: the bridge is not wrong, it is simply more general. */
.ostz .ostz-card--quiet .button,
.ostz .ostz-card--quiet a.button.ostz-btn--primary,
.ostz .ostz-card--quiet button.button.ostz-btn--primary {
	border: 1px solid var(--ostz-line-strong);
	background: var(--ostz-bg-sunken);
	color: var(--ostz-text);
}

.ostz .ostz-card--quiet a.button.ostz-btn--primary:hover,
.ostz .ostz-card--quiet button.button.ostz-btn--primary:hover {
	border-color: var(--ostz-accent);
	color: var(--ostz-accent);
}

/* --- ostadeez-b-woo.css --- */
/* BRIDGE — WooCommerce's class names → our components.
   One of the two files allowed to name a class we do not own. See modules/_ARCHITECTURE.md.

   WooCommerce prints its own classes from its own templates and we cannot remove them: they are
   read by JavaScript (`_woo-class-contract.json`) and by the platform's own markup. So a platform
   name is mapped to a component HERE, once, instead of being styled directly in half a dozen
   places. Where a filter or an owned template lets us add our own class, the rule belongs in the
   component and this file loses a block.

   ⚠ 2026-09-03 — THIS FILE GREW ON PURPOSE, and the reason is the point of the whole exercise.
   Notices, form fields, money tables and the account panel were each declared TWICE, in
   `ostadeez-woo-baseline.css` and again in `ostadeez-shop.css`, with different values: notices had
   two different paddings and two different radii, the table had two different header weights, and
   the form row was built twice from scratch. Load order decided which one a page got. All of it is
   consolidated here, one declaration per surface, and both copies were deleted in the same edit.

   ⚠ THE BODY-CLASS TRAP is why most of those rules could never have worked anyway.
   `.ostz .woocommerce X` asks for a `.woocommerce` INSIDE a `.ostz`. On the shop archive and on a
   product page WooCommerce puts `woocommerce` on `<body>` — the same element `.ostz` is on — so
   the selector matches nothing; on cart, checkout and account the shortcode renders a real
   `div.woocommerce`, so it does. Same selector, two behaviours, no error either way. Every
   selector below either drops the wrapper (the platform class is unique enough on its own) or uses
   a COMPOUND body selector (`.ostz.woocommerce-cart`). */

/* ================================================================== buttons → ostadeez-m-button */

/*
 * ⚠ The selectors deliberately MIRROR the element-qualified shape the legacy sheet used
 * (`.ostz a.button`, not `.ostz .button`). Equal specificity, and this file prints after every
 * legacy sheet, so load order decides in our favour. That is the whole mechanism: no `!important`,
 * nothing of WooCommerce's overridden by force, and no specificity race to lose.
 *
 * The archive's add-to-cart already carries our own class — `ostz_loop_button_class()` adds
 * `ostz-btn ostz-btn--compact` and marks the purchasable one `--primary`.
 */

/* ⚠ `@mirrors` modules/ostadeez-m-button.css `.ostz-btn` — DELIBERATE, and the duplication is
   the mechanism: this bridge wins by LOAD ORDER over WooCommerce's own sheet instead of by
   specificity, so it must carry the full body rather than lean on the module. The two are a
   CONTRACT: they stay byte-identical, and check-css-architecture.php fails the moment one is
   edited alone. Change the module first, then mirror it here. */
.ostz a.button,
.ostz button.button,
.ostz input.button,
.ostz .woocommerce-button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--ostz-space-2);
	/* The component's one control height — see the note in modules/ostadeez-m-button.css. */
	min-block-size: 48px;
	padding: 12px 22px;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: var(--ostz-radius-sm, 10px);
	background: var(--ostz-bg-raised-2, #1b1e27);
	color: var(--ostz-text, #f2f4f8);
	font-family: inherit;
	font-size: var(--ostz-text-base);
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-tight);
	text-decoration: none;
	cursor: pointer;
	transition: border-color .18s ease, color .18s ease, background .18s ease;
}

.ostz a.button:hover,
.ostz button.button:hover,
.ostz input.button:hover,
.ostz .woocommerce-button:hover {
	border-color: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent, #00e0d5);
}

/*
 * `.alt` is WooCommerce's own word for «the important one on this screen» — place order, proceed to
 * checkout, add to cart on a product page. It maps to our brand variant.
 */
.ostz a.button.alt,
.ostz button.button.alt,
.ostz input.button.alt {
	border-color: transparent;
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	font-weight: var(--ostz-weight-bold);
}

.ostz a.button.alt:hover,
.ostz button.button.alt:hover,
.ostz input.button.alt:hover {
	border-color: transparent;
	color: var(--ostz-accent-ink, #08090c);
	filter: brightness(1.08);
}

/*
 * ⚠ Both the attribute and the class, and the class is the one that matters: WooCommerce marks a
 * variable product's add-to-cart `class="disabled wc-variation-selection-needed"` WITHOUT the
 * disabled attribute. Measured on the product page — the button rendered at full strength and did
 * nothing at all until a variation was picked, with no message anywhere.
 */
.ostz a.button.disabled,
.ostz button.button:disabled,
.ostz button.button.disabled,
.ostz input.button:disabled,
.ostz input.button.disabled {
	opacity: .5;
	cursor: not-allowed;
	filter: none;
}

/* When our own class is present it decides, so the bridge steps aside for the variants. A card's
   add-to-cart carries `.button` from WooCommerce AND `.ostz-btn--primary` from us; without this the
   bridge's element-qualified default would out-specify the component's variant. */
.ostz a.button.ostz-btn--primary,
.ostz button.button.ostz-btn--primary,
.ostz input.button.ostz-btn--primary {
	border-color: transparent;
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	font-weight: var(--ostz-weight-bold);
}

.ostz a.button.ostz-btn--primary:hover,
.ostz button.button.ostz-btn--primary:hover {
	border-color: transparent;
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	filter: brightness(1.08);
}

.ostz a.button.ostz-btn--compact,
.ostz button.button.ostz-btn--compact {
	min-block-size: 34px;
	padding: 0 var(--ostz-space-3);
	border-radius: 9px;
	font-size: var(--ostz-text-xs);
}

/*
 * HERO — the single biggest action on the site, mapped onto the button component's `--hero`
 * variant. WooCommerce prints these two by id and class from its own templates, so this is the
 * only place they can be reached.
 */
.ostz a.checkout-button,
.ostz button#place_order,
.ostz input#place_order {
	inline-size: 100%;
	min-block-size: 52px;
	border-color: transparent;
	border-radius: var(--ostz-radius);
	background: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent-ink, #08090c);
	font-size: var(--ostz-text-lg);
	font-weight: var(--ostz-weight-black);
}

.ostz a.checkout-button:hover,
.ostz button#place_order:hover {
	border-color: transparent;
	color: var(--ostz-accent-ink, #08090c);
	filter: brightness(1.08);
}

/* ================================================================== notices
   ⚠ THE HOME OF THIS COMPONENT. There is no `ostadeez-m-notice.css` any more: every notice on
   this site is printed by WooCommerce, so a module keyed to our own class name matched nothing
   and this file carried a second copy of the same body. Deleted 2026-09-05; see _ARCHITECTURE.md
   rule 7. */

.ostz .woocommerce-notices-wrapper:empty {
	display: none;
}

.ostz .woocommerce-message,
.ostz .woocommerce-info,
.ostz .woocommerce-error {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--ostz-space-3);
	margin-block: 0 var(--ostz-space-6);
	padding: 14px 18px;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-inline-start-width: 3px;
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-raised, #15171e);
	color: var(--ostz-text, #f2f4f8);
	font-size: var(--ostz-text-base);
	line-height: var(--ostz-leading-snug);
	list-style: none;
}

.ostz .woocommerce-message {
	border-inline-start-color: var(--ostz-accent, #00e0d5);
}

.ostz .woocommerce-info {
	border-inline-start-color: var(--ostz-line-strong, #333846);
}

/* ⚠ `--ostz-danger`, not the literal `#ff5d6e` / `#ffd7db` the legacy sheet carried. Those were the
   last two hardcoded colours in it and they did not follow the scheme, so an error on the light
   store rendered pale pink ink on a white card. */
.ostz .woocommerce-error {
	border-inline-start-color: var(--ostz-danger, #e2564a);
}

/* uniform — WooCommerce prints error text as list items with no class. */
.ostz .woocommerce-error li,
.ostz .woocommerce-info li,
.ostz .woocommerce-message li {
	margin: 0;
}

/* The route out of a notice, pushed to the far end of the row. */
.ostz .woocommerce-message .button,
.ostz .woocommerce-info .button {
	margin-inline-start: auto;
	padding: 8px 16px;
}

/* ================================================================== forms → ostadeez-m-form */

/*
 * ⚠ THE GAP THIS CLOSES. Twelve of these class names had no rule anywhere and WooCommerce's own
 * stylesheets are dequeued, so the account forms — edit-address, edit-account, register, lost
 * password — rendered as raw browser controls on a dark page. Checkout looked correct only because
 * `.ostz .woocommerce form .form-row` happened to catch it, and that selector reached nothing
 * outside a shortcode wrapper.
 */

.ostz .form-row,
.ostz .woocommerce-form-row {
	display: flex;
	flex-direction: column;
	gap: 6px;
	margin-block-end: var(--ostz-space-4);
	padding: 0;
}

/* uniform — every label in a WooCommerce form row is the same label. */
.ostz .form-row label,
.ostz .woocommerce-form-row label,
.ostz .woocommerce-form__label {
	display: block;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-medium);
	line-height: var(--ostz-leading-snug);
}

.ostz .required,
.ostz abbr.required {
	border: 0;
	color: var(--ostz-danger, #e2564a);
	text-decoration: none;
}

.ostz .optional {
	color: var(--ostz-text-faint, #828a9c);
}

/*
 * ⚠ THE FIELD ITSELF IS NOT DECLARED HERE, and that is the point of this pass.
 *
 * WooCommerce prints its fields as `input[type=text]`, `select` and `textarea` — real elements
 * with platform classes bolted on. `modules/ostadeez-m-form.css` reaches all of them through
 * `:where(.ostz) input[type='text']` and friends, at zero specificity, so one declaration covers
 * WooCommerce's fields, the contact form's, the newsletter's and our own. Listing every platform
 * spelling here as well would be a second definition of the same control — which is exactly what
 * this file inherited: 46px in `ostadeez-shop.css` against 44px in the bridge, one control, two
 * sizes, resolved by load order.
 *
 * What stays below is what is genuinely WooCommerce's: its wrappers, its validation classes, its
 * checkbox label, and the panels it puts forms inside.
 */

.ostz textarea.input-text,
.ostz .form-row textarea {
	min-block-size: 120px;
	resize: vertical;
}

/* The input wrapper WooCommerce puts around the control must not add its own box. */
.ostz .woocommerce-input-wrapper {
	display: block;
	inline-size: 100%;
}

/* A checkbox row is a label, not a field: the box and its words are one target.
   ⚠ MEASURED 2026-09-23 on the checkout, at 390px: clicking the label TEXT does toggle the box
   (proved by reading `checked` back), so the row IS the target — but the row measured 274×21,
   i.e. the WIDTH was never the problem and the HEIGHT was 3px under the floor. The min-height is
   what puts the target over 24, and it belongs here rather than on the input for the reason in
   the next block. */
.ostz .woocommerce-form__label-for-checkbox,
.ostz .woocommerce-form-login__rememberme {
	display: inline-flex;
	flex-direction: row;
	align-items: center;
	gap: var(--ostz-space-2);
	min-block-size: 24px;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
	cursor: pointer;
}

/* ⚠ THE 2026-09-21 FIX HERE DID NOT WORK, and nothing said so: it grew the target with
   `padding: 3px` on a `content-box`, and on 2026-09-23 the same control still measured 18×18 with
   a COMPUTED padding of 0. A checkbox that keeps its native appearance does not apply author
   padding at all — the declaration parses, matches, wins its cascade and changes nothing, which is
   the one failure shape no stylesheet review can see. The box therefore stays a plain 18px and the
   target is grown on its LABEL above, which is the element a person actually presses.
   Do not re-add padding here expecting a bigger box; it would have to be `appearance: none` plus a
   hand-drawn tick, which is a visual change nobody asked for. */
.ostz .woocommerce-form__input-checkbox,
.ostz input[type='checkbox'].input-checkbox {
	inline-size: 18px;
	block-size: 18px;
	flex: none;
	margin: 0;
	accent-color: var(--ostz-accent, #00e0d5);
	cursor: pointer;
}

.ostz .woocommerce-privacy-policy-text,
.ostz .woocommerce-form-login-toggle,
.ostz .woocommerce-LostPassword {
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
}

.ostz .woocommerce-LostPassword a,
.ostz .woocommerce-privacy-policy-text a {
	color: var(--ostz-accent, #00e0d5);
}

/* Validation state. WooCommerce sets these classes on the ROW, so the field is reached through it.
   ⚠ The border alone is not the message — `.woocommerce-invalid` gets a colour here and the reason
   is printed as a notice by the platform. Colour is the reinforcement, never the whole signal. */
.ostz .woocommerce-invalid input.input-text,
.ostz .woocommerce-invalid select,
.ostz .woocommerce-invalid .woocommerce-Input {
	border-color: var(--ostz-danger, #e2564a);
}

.ostz .woocommerce-validated input.input-text,
.ostz .woocommerce-validated .woocommerce-Input {
	border-color: var(--ostz-accent, #00e0d5);
}

/*
 * ⚠ The coupon field reports its own failure INLINE, not through the notice wrapper. WooCommerce
 * 10.x prints `p.coupon-error-notice` next to the field and marks the input `has-error`; neither
 * had a rule anywhere, so on a store that dequeues WooCommerce's stylesheet the refusal rendered
 * as a bare browser-default paragraph with no error ring on the field.
 *
 * ⚠ AND IT IS WHY A CHECK OF MINE READ «the wrong coupon says nothing at all»: a probe looking for
 * `.woocommerce-error` finds nothing here, because this message is not one. The message existed;
 * only its skin was missing. A selector list is an assumption about the platform's markup, and
 * this platform changed it.
 */
.ostz p.coupon-error-notice {
	margin: var(--ostz-space-2) 0 0;
	color: var(--ostz-danger, #e2564a);
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-medium);
	unicode-bidi: isolate;
}

.ostz input.input-text.has-error,
.ostz input.has-error {
	border-color: var(--ostz-danger, #e2564a);
}

/* Two-up rows where WooCommerce marks them. Flex basis rather than a width calculation: the old
   `width: calc(50% - 6px)` assumed a 12px gap that the row no longer has. */
.ostz .form-row-first,
.ostz .form-row-last {
	display: inline-flex;
	inline-size: calc(50% - var(--ostz-space-2));
	vertical-align: top;
}

.ostz .form-row-wide,
.ostz .form-row-full {
	inline-size: 100%;
}

@media (max-width: 520px) {
	.ostz .form-row-first,
	.ostz .form-row-last {
		inline-size: 100%;
	}
}

/* WooCommerce's own two-column wrapper, which its layout.css used to float. */
.ostz .col2-set {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--ostz-space-6);
}

.ostz .col2-set .col-1,
.ostz .col2-set .col-2 {
	min-inline-size: 0;
}

/* A form that IS the page's content sits in the same panel every other one does. */
.ostz form.login,
.ostz form.register,
.ostz form.checkout_coupon,
.ostz form.woocommerce-ResetPassword,
.ostz form.woocommerce-EditAccountForm,
.ostz form.edit-account {
	padding: var(--ostz-space-6);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius);
	background: var(--ostz-bg-raised, #15171e);
}

/* ⚠ `woocommerce-EditAccountForm` and `edit-account` sit INSIDE `.woocommerce-MyAccount-content`,
   which is already a panel. A panel inside a panel reads as a rendering fault, so the inner one
   drops its frame — the same decision `.ostz-empty--inline` makes for the same reason. */
.ostz .woocommerce-MyAccount-content form.woocommerce-EditAccountForm,
.ostz .woocommerce-MyAccount-content form.edit-account,
.ostz .woocommerce-MyAccount-content form.woocommerce-address-fields,
.ostz .woocommerce-MyAccount-content form.woocommerce-EditAccountForm fieldset {
	padding: 0;
	border: 0;
	background: none;
}

.ostz .woocommerce-address-fields__field-wrapper,
.ostz .woocommerce-additional-fields,
.ostz .woocommerce-shipping-fields,
.ostz .woocommerce-billing-fields__field-wrapper {
	min-inline-size: 0;
}

/* uniform — every section heading inside a checkout or account form is the same heading. */
.ostz .woocommerce-billing-fields h3,
.ostz .woocommerce-shipping-fields h3,
.ostz .woocommerce-additional-fields h3,
.ostz .woocommerce-EditAccountForm legend {
	margin: 0 0 var(--ostz-space-4);
	font-size: var(--ostz-text-lg);
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-heading);
}

/* ================================================================== tables
   ⚠ THE HOME OF THIS COMPONENT — `ostadeez-m-table.css` is gone for the reason in rule 7. */

/*
 * ⚠ `display: block` on the table is what makes it scroll inside its own box instead of dragging
 * the whole page sideways. It looks wrong and it is not: the rows still form a table internally
 * through anonymous boxes. The phone case is handled by stacking further down, not by this.
 */
.ostz table.shop_table,
.ostz .woocommerce-order-details,
.ostz table.woocommerce-orders-table {
	display: block;
	inline-size: 100%;
	max-inline-size: 100%;
	overflow-x: auto;
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-lg);
	border-collapse: collapse;
	background: var(--ostz-bg-raised, #15171e);
	font-size: var(--ostz-text-base);
}

/*
 * The CART is the exception above the phone breakpoint. Under `display:block` the table inside is an
 * anonymous box that shrinks to its content, and what used to stretch it to the card was the actions
 * cell being `display:flex` (which in turn stole the first column, see `td.actions`). With the cell
 * fixed, the rows stopped 160px short of the card. The cart's only wide column is the product name,
 * which wraps, so it fits without the scroll box from 769px up; the phone stacks it (see the media
 * block at the end).
 */
@media (min-width: 769px) {
	/* separate + 0 spacing: a collapsed-border table ignores border-radius, the card lost its corners */
	.ostz table.shop_table.cart {
		display: table;
		border-collapse: separate;
		border-spacing: 0;
	}
}

/* uniform — every cell in a money table. This is the rule that did not exist: only `tfoot` had
   padding, so the cart's own rows ran together with no space at all between the product name, its
   price and its quantity. */
.ostz table.shop_table th,
.ostz table.shop_table td,
.ostz table.woocommerce-orders-table th,
.ostz table.woocommerce-orders-table td,
.ostz table.woocommerce-table--order-details th,
.ostz table.woocommerce-table--order-details td {
	padding: var(--ostz-space-3) var(--ostz-space-4);
	border-block-end: 1px solid var(--ostz-line, #262a35);
	text-align: start;
	vertical-align: middle;
}

/* uniform */
.ostz table.shop_table thead th,
.ostz table.woocommerce-orders-table thead th {
	border-block-end: 1px solid var(--ostz-line-strong, #333846);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-xs);
	font-weight: var(--ostz-weight-bold);
	white-space: nowrap;
}

/* uniform */
.ostz table.shop_table tfoot th,
.ostz table.shop_table tfoot td {
	border-block-end: 0;
	border-block-start: 1px solid var(--ostz-line, #262a35);
}

/* uniform — the line the customer is looking for. */
.ostz table.shop_table tfoot tr:last-child th,
.ostz table.shop_table tfoot tr:last-child td {
	color: var(--ostz-text, #f2f4f8);
	font-size: var(--ostz-text-lg);
	font-weight: var(--ostz-weight-bold);
}

/* uniform — the row thumbnail. Square and fixed, so a column of them lines up. */
.ostz table.shop_table img {
	inline-size: 60px;
	block-size: 60px;
	aspect-ratio: 1;
	border-radius: var(--ostz-radius-xs);
	object-fit: cover;
}

/* ⚠ 92 = the 60px image + 16px padding on each side. At 76 the content box was 44px, the image's
   `max-width:100%` clamped its WIDTH while `block-size:60px` held, and a square photo rendered
   44×60 cropped to portrait on desktop only (پویش ظاهر 2026-09-19). The link is sized to the
   picture so the clickable box is the picture, not a 44×22 inline strip. */
.ostz td.product-thumbnail {
	inline-size: 92px;
}

.ostz td.product-thumbnail a {
	display: inline-block;
	vertical-align: middle;
}

/* The remove column is sized to its control. (Its old 359px came from the actions cell, see
   `td.actions` — this width alone did not fix it.) */
.ostz td.product-remove {
	inline-size: 44px;
}
/* The name is the row's subject, so it carries the weight; the money columns carry the digits. */
.ostz td.product-name a,
.ostz td.woocommerce-orders-table__cell-order-number a {
	color: var(--ostz-text, #f2f4f8);
	font-weight: var(--ostz-weight-bold);
	text-decoration: none;
}

.ostz td.product-name a:hover,
.ostz td.woocommerce-orders-table__cell-order-number a:hover {
	color: var(--ostz-accent, #00e0d5);
}

/* The variation list WooCommerce prints under a cart line. */
.ostz .variation,
.ostz .wc-item-meta {
	margin: 4px 0 0;
	padding: 0;
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
	list-style: none;
}

/* uniform — the meta list is dt/dd pairs with no classes. */
.ostz .wc-item-meta dt,
.ostz .wc-item-meta dd,
.ostz .variation dt,
.ostz .variation dd {
	display: inline;
	margin: 0;
	font-weight: var(--ostz-weight-normal);
}

/* Money cells: tabular digits, isolated, and never wrapped mid-amount. */
.ostz td.product-price,
.ostz td.product-subtotal,
.ostz td.product-total,
.ostz .cart-subtotal td,
.ostz .order-total td,
.ostz .woocommerce-Price-amount {
	font-variant-numeric: tabular-nums;
	unicode-bidi: isolate;
	white-space: nowrap;
}

/* ⚠ `woocommerce-Price-amount` is printed by WooCommerce OUTSIDE `.price` in several places — the
   order tables, the mini cart, the shipping rows — where the shop skin's `.price` rule never
   reached it. This is the one declaration that covers all of them. */
/* ⚠ No font-size here. The first draft shrank the symbol to `.9em`, which the architecture check
   rejects and rightly: a relative literal is a step the reference does not have, and «the currency
   word is slightly smaller» would then be true in this one place and nowhere else on the site.
   The symbol reads at the amount's own size. */
.ostz .woocommerce-Price-currencySymbol {
	margin-inline-start: 3px;
}

/* The remove control. Quiet until hovered — a red X on every row of a cart reads as an error. */
.ostz td.product-remove a,
.ostz a.remove {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	inline-size: 28px;
	block-size: 28px;
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: 50%;
	color: var(--ostz-text-dim, #aab0be);
	line-height: 1;
	text-decoration: none;
	transition: border-color .15s ease, color .15s ease;
}

.ostz td.product-remove a:hover,
.ostz a.remove:hover {
	border-color: var(--ostz-danger, #e2564a);
	color: var(--ostz-danger, #e2564a);
}

/*
 * The cart's action row: coupon on one side, update on the other.
 * ⚠ The cell STAYS a table-cell. It used to be `display:flex`, and a flex `<td colspan="6">` is no
 * longer a cell: the table wraps it in an anonymous cell in the FIRST column only, so its 222px of
 * coupon + button min-content became the REMOVE column's width on every row (measured 359 of
 * 1126px, the × stranded at the edge, names wrapping, the coupon field and its button stacked).
 * The two groups are laid out inside the cell instead.
 */
.ostz td.actions {
	display: table-cell;
}

.ostz td.actions > .button {
	float: inline-end;
}

.ostz .actions .coupon {
	float: inline-start;
	display: flex;
	flex-wrap: wrap;
	gap: var(--ostz-space-2);
	align-items: stretch;
	margin-block-end: var(--ostz-space-4);
}

.ostz .actions .coupon input.input-text {
	inline-size: auto;
	max-inline-size: 220px;
}

/* ================================================================== totals & checkout panels */

.ostz .cart_totals,
.ostz #order_review,
.ostz .woocommerce-checkout-review-order,
.ostz .woocommerce-order-overview,
.ostz .woocommerce-customer-details {
	padding: var(--ostz-space-6);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-lg);
	background: var(--ostz-bg-raised, #15171e);
}

/* A table already inside one of those panels drops its own frame. */
.ostz .cart_totals table.shop_table,
.ostz #order_review table.shop_table,
.ostz .woocommerce-checkout-review-order table.shop_table {
	border: 0;
	border-radius: 0;
	background: none;
}

/* uniform — every panel heading on cart, checkout and order pages. */
.ostz .cart_totals h2,
.ostz .cross-sells h2,
.ostz #order_review_heading,
.ostz .woocommerce-order-details__title,
.ostz .woocommerce-column__title {
	margin: 0 0 var(--ostz-space-4);
	font-size: var(--ostz-text-xl);
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-heading);
}

/* The totals table touched the checkout button at 0px. */
.ostz .cart_totals .wc-proceed-to-checkout {
	margin-block-start: var(--ostz-space-4);
}

.ostz .cart-collaterals {
	margin-block-start: var(--ostz-space-8);
}

/* The order-received overview is a list of facts, not a paragraph. */
.ostz ul.woocommerce-order-overview {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
	gap: var(--ostz-space-4);
	margin-block: 0 var(--ostz-space-8);
	list-style: none;
}

/* uniform */
.ostz ul.woocommerce-order-overview li {
	margin: 0;
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
}

/* uniform */
.ostz ul.woocommerce-order-overview li strong {
	display: block;
	margin-block-start: 4px;
	color: var(--ostz-text, #f2f4f8);
	font-size: var(--ostz-text-md);
	unicode-bidi: isolate;
}

.ostz .woocommerce-columns {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
	gap: var(--ostz-space-6);
}

/* uniform — the address block on an order. */
.ostz .woocommerce-customer-details address {
	padding: 0;
	border: 0;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
	font-style: normal;
	line-height: var(--ostz-leading-body);
}

/* ================================================================== shipping & payment */

/* ⚠ Values carried over UNCHANGED from the legacy sheet. The migration rule is that a component
   renders identically after the move unless the change was the point; the point here was
   deduplication, so the shipping and payment rows keep the grid, the sunken ground and the
   12px/14px padding they already had. */
.ostz ul#shipping_method,
.ostz ul.woocommerce-shipping-methods {
	display: grid;
	gap: var(--ostz-space-2);
	margin: 0;
	padding: 0;
	list-style: none;
}

/* uniform — every shipping choice is the same row. */
.ostz ul#shipping_method li,
.ostz ul.woocommerce-shipping-methods li {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--ostz-space-2);
	padding: 12px 14px;
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-sunken, #111318);
}

/* uniform */
.ostz ul#shipping_method li label,
.ostz ul.woocommerce-shipping-methods li label {
	font-size: var(--ostz-text-base);
	cursor: pointer;
}

/* uniform */
.ostz ul#shipping_method li .amount {
	margin-inline-start: auto;
	font-weight: var(--ostz-weight-bold);
	unicode-bidi: isolate;
}

/* Printed by the shipping plugin under each option: the honest wording, never a price.
   `flex-basis: 100%` is what pushes it onto its own line in WooCommerce's own shipping row, which
   is a wrapping flex row. */
.ostz .dmship-rate-subtitle {
	flex-basis: 100%;
	min-inline-size: 0;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-xs);
	line-height: var(--ostz-leading-body);
}

/* ⚠ MEASURED 2026-09-23, and it cost the whole phone layout: in dm-smart-cart-modal's one-page
   cart+checkout the shipping row is the PLUGIN's own, which already puts the subtitle on its own
   line and gives it a 64px inline padding. The rule above then asked a border-box item for 100% of
   the line ON TOP of that padding, the item could not shrink, and the page went 614px wide inside
   a 390px viewport — the pay button, the province select and the whole grid read as broken while
   the real cause was one inherited declaration. Bisected to this element: hiding it alone took the
   scroll width from 614 straight back to 390.
   ⚠ It is scoped OFF rather than deleted, because deleting it puts the subtitle back on the label's
   own line in the classic WooCommerce row — one rule, two surfaces, and only one of them was ever
   looked at (+SHARED-SURFACE). Both are re-walked after any change here.
   ⚠ `.dm-scm-ck-ship-item` is a PLUGIN class, i.e. part of the mounting contract: if an upstream
   release renames it this silently reverts and the phone layout breaks again with nothing red.
   check-plugin-contract.php is what is supposed to say so. */
.ostz .dm-scm-ck-ship-item .dmship-rate-subtitle {
	flex-basis: auto;
}

/* The shipping plugin's cash-on-delivery amount.
   Its default is that plugin's own blue accent, which MEASURED 4.10:1 on this site's raised
   checkout panel — under the floor, on the line that tells the buyer when they pay. dm-shipping
   2.2.4 exposes this token for exactly that reason and keeps the accent as its fallback, so no
   other store changed. The amount takes full-strength ink here: it is read, not decorated, which
   is also why it does not take the dim token its subtitle sibling above uses.
   ⚠ Declared on `.ostz`, and that is enough ONLY because the plugin deliberately does not declare
   this token in its own `:root` block — there is nothing to out-specify. If a future release adds
   it there, this stops working silently and check-plugin-contract.php is what will say so. */
.ostz {
	--dmship-cod-price-color: var(--ostz-text);
}

.ostz .woocommerce-shipping-destination {
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-xs);
}

.ostz #payment ul.payment_methods {
	display: grid;
	gap: var(--ostz-space-2);
	margin: 0 0 var(--ostz-space-4);
	padding: 0;
	list-style: none;
}

/* uniform — every gateway is the same row. */
.ostz #payment ul.payment_methods li {
	padding: 12px 14px;
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-sunken, #111318);
}

/* uniform */
.ostz #payment ul.payment_methods li label {
	display: inline-flex;
	align-items: center;
	gap: var(--ostz-space-2);
	color: var(--ostz-text, #f2f4f8);
	font-size: var(--ostz-text-base);
	cursor: pointer;
}

.ostz #payment div.payment_box {
	margin-block-start: var(--ostz-space-2);
	padding: 12px 14px;
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-raised-2, #1b1e27);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
}

.ostz .wc_payment_method label img {
	max-block-size: 26px;
	inline-size: auto;
	margin-inline-start: var(--ostz-space-2);
	vertical-align: middle;
}

.ostz .woocommerce-terms-and-conditions-wrapper {
	margin-block-end: var(--ostz-space-4);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
}

.ostz .woocommerce-checkout #payment .form-row.place-order {
	margin: 0;
	padding: var(--ostz-space-4) 0 0;
}

/* ================================================================== the surfaces behind a real order

   ⚠ Every rule in this block is for markup that only renders once a customer has actually done
   something — asked for a shipping estimate, mistyped a coupon, left an order unpaid, bought a
   downloadable file, saved a card. None of it had a rule, and none of it was VISIBLE to a page
   scan, which is exactly why it stayed missing: a probe that never reaches the state cannot report
   on it. `seeds/seed-design-fixtures.php` creates the states so these can be looked at. */

/* The «calculate shipping» panel on the cart. Its own toggle link is a control, not a sentence. */
.ostz .shipping-calculator-form {
	margin-block-start: var(--ostz-space-3);
	padding: var(--ostz-space-3);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-sunken, #111318);
}

.ostz a.shipping-calculator-button {
	display: inline-flex;
	align-items: center;
	min-block-size: 24px;
	color: var(--ostz-accent, #00e0d5);
	font-size: var(--ostz-text-sm);
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* Checkout's own error region, printed OUTSIDE the notice wrapper when the cart is invalid — so
   the notice rules above never reached it and it rendered as a bare list on the money page. */
.ostz ul.woocommerce-error.cart-errors,
.ostz .woocommerce-NoticeGroup {
	display: block;
	margin-block-end: var(--ostz-space-6);
}

/* The pay-again form: the route back for a failed or bank-transfer order, which is the highest
   intent a customer ever has and the page most likely to have been left unstyled. */
.ostz form#order_review.woocommerce-checkout,
.ostz .woocommerce-order-pay {
	max-inline-size: 640px;
}

/* Downloads, saved cards, and the address form the account panel opens. */
.ostz table.woocommerce-table--order-downloads,
.ostz table.woocommerce-MyAccount-downloads,
.ostz table.woocommerce-PaymentMethods {
	inline-size: 100%;
	border-collapse: collapse;
	font-size: var(--ostz-text-sm);
}

/* uniform */
.ostz table.woocommerce-table--order-downloads th,
.ostz table.woocommerce-table--order-downloads td,
.ostz table.woocommerce-PaymentMethods th,
.ostz table.woocommerce-PaymentMethods td {
	padding: var(--ostz-space-3);
	border-block-end: 1px solid var(--ostz-line, #262a35);
	text-align: start;
}

.ostz .woocommerce-add-payment-method,
.ostz form.woocommerce-address-fields,
.ostz form.edit-address {
	min-inline-size: 0;
}

/* «سفارش دوباره» is a `.button`, so it already has the component's look; this only keeps it from
   filling the cell it sits in on a stacked phone row. */
.ostz a.order-again .button,
.ostz .order-again {
	display: inline-flex;
}

/* WooCommerce's payment list carries three classes and different templates lead with different
   ones, so the plural spelling is named alongside the `#payment`-scoped rule below. */
.ostz ul.wc_payment_methods {
	margin: 0 0 var(--ostz-space-4);
	padding: 0;
	list-style: none;
}

/* The register form's own class, beside `form.register` — the account page prints
   `woocommerce-form-register` and a plugin that re-renders it may print only that. */
.ostz form.woocommerce-form-register,
.ostz form.woocommerce-form-login {
	padding: var(--ostz-space-6);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius);
	background: var(--ostz-bg-raised, #15171e);
}

/* ================================================================== pagination
   ⚠ THE HOME OF THIS COMPONENT — `ostadeez-m-pager.css` is gone for the reason in rule 7. */

.ostz .woocommerce-pagination ul.page-numbers {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: center;
	gap: var(--ostz-space-2);
	margin-block: var(--ostz-space-8) 0;
	padding: 0;
	border: 0;
	list-style: none;
}

/* uniform */
.ostz .woocommerce-pagination ul.page-numbers li {
	margin: 0;
	border: 0;
}

/* ⚠ `@mirrors` ostadeez-b-wp.css `.ostz .navigation.pagination .page-numbers` — one pager, two
   platforms. A MODULE may not carry it: `.page-numbers` and `.woocommerce-pagination` are
   platform classes, and this check itself forbids a module naming one. So each bridge carries the
   full body, and the pair is a CONTRACT to stay byte-identical. Edit both or neither. */
.ostz .woocommerce-pagination .page-numbers {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-inline-size: 40px;
	min-block-size: 40px;
	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-dim, #aab0be);
	font-size: var(--ostz-text-base);
	font-weight: var(--ostz-weight-medium);
	line-height: 1;
	text-decoration: none;
	font-variant-numeric: tabular-nums;
	unicode-bidi: isolate;
	transition: border-color .15s ease, color .15s ease, background .15s ease;
}

.ostz .woocommerce-pagination .page-numbers.current,
.ostz .woocommerce-pagination a.page-numbers:hover {
	border-color: var(--ostz-accent, #00e0d5);
	background: color-mix(in srgb, var(--ostz-accent, #00e0d5) 14%, var(--ostz-bg-raised, #15171e));
	color: var(--ostz-text, #f2f4f8);
}

/* ================================================================== rating
   ⚠ THE HOME OF THIS COMPONENT — `ostadeez-m-rating.css` is gone for the reason in rule 7. */

/*
 * ⚠ This is a RE-IMPLEMENTATION, not a restyle. `.star-rating` draws its stars with
 * `content: '\e021'` in the `WooCommerce` icon font that ships inside `woocommerce.css` — dequeued
 * here — so with no rule the glyphs have no font and what renders is the screen-reader fallback
 * sentence «Rated 5.00 out of 5» in the browser's default type, under the product title.
 *
 * The mechanic is WooCommerce's own (a fixed track, a clipped overlay, the real sentence pushed
 * out of the box by padding) with the Unicode star instead of an icon font, so it needs no asset
 * and no request. This IS the component; there is no separate rating module.
 */

.ostz .star-rating {
	position: relative;
	display: inline-block;
	inline-size: 5.4em;
	block-size: 1.2em;
	overflow: hidden;
	font-family: var(--ostz-font);
	font-size: var(--ostz-text-base);
	line-height: 1.2;
	/* LTR on purpose: the overlay is clipped from the left, and stars are read left to right in
	   every locale that uses them. In an RTL track a 3-of-5 score would fill stars 3, 4 and 5. */
	direction: ltr;
	unicode-bidi: isolate;
	white-space: nowrap;
	vertical-align: middle;
}

.ostz .star-rating::before {
	content: '★★★★★';
	position: absolute;
	inset-block-start: 0;
	left: 0;
	color: var(--ostz-line-strong, #333846);
	letter-spacing: .08em;
}

/* uniform — WooCommerce prints exactly one span, and its inline width IS the score. */
.ostz .star-rating span {
	position: absolute;
	inset-block-start: 0;
	left: 0;
	block-size: 100%;
	padding-block-start: 1.5em;
	overflow: hidden;
	color: var(--ostz-amber, #ffb020);
	white-space: nowrap;
}

/* uniform */
.ostz .star-rating span::before {
	content: '★★★★★';
	position: absolute;
	inset-block-start: 0;
	left: 0;
	letter-spacing: .08em;
}

.ostz .woocommerce-product-rating {
	display: flex;
	align-items: center;
	gap: var(--ostz-space-2);
	margin-block: 0 var(--ostz-space-3);
}

/* The links WooCommerce prints inside its own tables and toggles: a product name in the order
   table, the order number, the «کد تخفیف دارید؟» toggle. All measured 20–22px — a row of short
   words in a sentence, each one a real destination, each under the tap floor. */
/* ⚠ `th` AS WELL AS `td`. WooCommerce renders the order-number cell of the account's orders table
   as a `<th>` — measured, `TH.woocommerce-orders-table__cell…` — so a `td`-only selector misses
   the one link in that table a customer actually clicks, and the rule looks correct in the file. */
.ostz td.product-name a,
.ostz td.woocommerce-orders-table__cell-order-number a,
.ostz th.woocommerce-orders-table__cell-order-number a,
.ostz td.woocommerce-grouped-product-list-item__label a,
.ostz .woocommerce-table--order-details td a,
.ostz a.showcoupon {
	display: inline-flex;
	align-items: center;
	min-block-size: 24px;
}

/* Measured 87.5×21.6 on the product page — the jump to the reviews tab, under the tap floor. */
.ostz .woocommerce-review-link {
	display: inline-flex;
	align-items: center;
	min-block-size: 24px;
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
	text-decoration: none;
}

/* WooCommerce's password reveal renders 16×6 — a control the customer has to hit on a phone while
   typing a password. Sized to the floor and given a hit area of its own. */
.ostz button.show-password-input,
.ostz .show-password-input {
	inline-size: 28px;
	block-size: 28px;
	background-position: center;
}

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

/* ================================================================== my account */

/* ⚠ COMPOUND, not descendant: `woocommerce-account` is a BODY class, so `.ostz .woocommerce-account`
   would ask for it inside itself. This rule never matched before it was compounded, and the
   account panel rendered as a full-width list with the content stacked under it. */
.ostz.woocommerce-account .woocommerce {
	display: grid;
	grid-template-columns: 220px minmax(0, 1fr);
	gap: var(--ostz-space-6);
	align-items: start;
}

@media (max-width: 820px) {
	.ostz.woocommerce-account .woocommerce {
		grid-template-columns: 1fr;
	}
}

.ostz .woocommerce-MyAccount-navigation {
	inline-size: auto;
	float: none;
	padding: var(--ostz-space-3);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-lg);
	background: var(--ostz-bg-raised, #15171e);
}

/* uniform */
.ostz .woocommerce-MyAccount-navigation ul {
	margin: 0;
	padding: 0;
	list-style: none;
}

/* ⚠ BOTH spellings. WooCommerce puts `woocommerce-MyAccount-navigation-link` on the `<li>` and the
   link inside it has no class of its own, so `li a` is what actually styles it — but a plugin that
   re-renders the menu may put the class on the anchor instead. Naming both is one line and closes
   a silent difference between two installs. */
.ostz .woocommerce-MyAccount-navigation li a,
.ostz a.woocommerce-MyAccount-navigation-link {
	display: block;
	min-block-size: 44px;
	padding: var(--ostz-space-2) var(--ostz-space-3);
	border-radius: var(--ostz-radius-sm);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
	line-height: 28px;
	text-decoration: none;
}

.ostz .woocommerce-MyAccount-navigation li a:hover {
	background: var(--ostz-bg-raised-2, #1b1e27);
	color: var(--ostz-text, #f2f4f8);
}

/* uniform */
.ostz .woocommerce-MyAccount-navigation li.is-active a {
	background: var(--ostz-bg-raised-2, #1b1e27);
	color: var(--ostz-text, #f2f4f8);
	font-weight: var(--ostz-weight-bold);
	box-shadow: inset 2px 0 0 var(--ostz-accent, #00e0d5);
}

.ostz .woocommerce-MyAccount-content {
	inline-size: auto;
	float: none;
	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 .woocommerce-Addresses {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--ostz-space-6);
}

.ostz .woocommerce-Address {
	min-inline-size: 0;
	padding: var(--ostz-space-4);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius-sm);
	background: var(--ostz-bg-sunken, #111318);
}

.ostz .woocommerce-Address-title {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--ostz-space-3);
	margin-block-end: var(--ostz-space-3);
}

/* uniform */
.ostz .woocommerce-Address-title h2,
.ostz .woocommerce-Address-title h3 {
	margin: 0;
	font-size: var(--ostz-text-md);
	font-weight: var(--ostz-weight-bold);
}

/* uniform — an address is data, printed by the platform with no classes. */
.ostz .woocommerce-Address address {
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
	font-style: normal;
	line-height: var(--ostz-leading-body);
}

.ostz .woocommerce-orders-table__cell-order-actions .button {
	min-block-size: 32px;
	padding: 0 var(--ostz-space-3);
	border-radius: var(--ostz-radius-xs);
	font-size: var(--ostz-text-xs);
}

/* ================================================================== catalogue surfaces

   ⚠ Everything in this block had no rule at all because the sample catalogue contains no product
   that renders it. They are designed against seeded fixtures, not guessed at — see
   `seeds/seed-design-fixtures.php`, which creates a sale, a rated product, a grouped product, an
   external product, stock states, attributes and a category image for exactly this reason. */

/* The sale flash. It rides the card's media box like the badge does, on the OPPOSITE side, because
   a product can be both «به‌زودی» and on sale and the two must not stack on each other. */
/*
 * ⚠ THE WORD IS TEXT-COLOURED, THE RING IS MAGENTA, and that split is a measured decision.
 *
 * The first version painted «تخفیف!» in `--ostz-accent-2` on the raised ground and measured
 * **4.23:1 in dark mode at 11px** — under the 4.5 floor, on a label whose whole job is to be read
 * across a grid. Every alternative that keeps magenta as the INK fails the same way in one scheme
 * or the other: white on #c026ff is 4.23, black on it is fine in dark but the light palette's
 * #8b1bbf inverts the problem. So the brand colour carries the identity as the border, where
 * contrast is not a legibility question, and the word uses the ink the rest of the site uses.
 * Colour was never the only signal here anyway — the badge says «تخفیف».
 */
.ostz span.onsale {
	position: absolute;
	inset-block-start: 10px;
	inset-inline-end: 10px;
	z-index: 2;
	padding: 3px 10px;
	border: 1px solid var(--ostz-accent-2, #c026ff);
	border-radius: var(--ostz-radius-pill);
	background: var(--ostz-bg-raised, #15171e);
	color: var(--ostz-text, #f2f4f8);
	font-size: var(--ostz-text-xs);
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-snug);
}

/* Stock state. Never colour alone — each state carries its own words from WooCommerce. */
.ostz p.stock {
	margin: 0 0 var(--ostz-space-3);
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-medium);
}

.ostz p.stock.in-stock {
	color: var(--ostz-accent, #00e0d5);
}

.ostz p.stock.available-on-backorder {
	color: var(--ostz-amber, #ffb020);
}

.ostz p.stock.out-of-stock {
	color: var(--ostz-danger, #e2564a);
}

/* The additional-information tab and the grouped-product table are the compact table variant. */
/* ⚠ Capped at the reading measure. MEASURED: at `inline-size: 100%` the table filled the 1160px
   tab panel, so «جنس» sat at the far right and «نخ پنبه ۲۴۰ گرم» in the middle of the screen with
   700px of nothing between them. A spec table is data to be read across, not a layout to fill. */
.ostz table.shop_attributes {
	inline-size: 100%;
	max-inline-size: var(--ostz-measure);
	border-collapse: collapse;
	font-size: var(--ostz-text-sm);
}

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

/* uniform */
.ostz table.shop_attributes th {
	inline-size: 30%;
	color: var(--ostz-text-dim, #aab0be);
	font-weight: var(--ostz-weight-medium);
}

/* uniform — WooCommerce wraps every attribute value in a bare <p>. */
.ostz table.shop_attributes td p {
	margin: 0;
}

/*
 * ⚠ `table-layout: fixed` is what makes `inline-size: 100%` binding. Without it a table is sized
 * by its CONTENT and `100%` is only a suggestion: MEASURED 2026-09-05 at 390px, the grouped
 * product's table rendered 386px inside a 358px form and pushed the whole document 12px wider —
 * an RTL page that drags sideways, on the one product type whose entire page IS this table. The
 * quantity column is given a fixed share so the product names take what is left and wrap.
 */
.ostz table.group_table {
	inline-size: 100%;
	table-layout: fixed;
	margin-block-end: var(--ostz-space-4);
	border-collapse: collapse;
}

/* uniform */
.ostz table.group_table td.woocommerce-grouped-product-list-item__label {
	overflow-wrap: anywhere;
}

/* ⚠ 150px, not 120: the stepper inside this cell is a three-part control and at 120 its buttons
   measured 21px wide — under the 24px tap floor, on the control that sets how many of each item a
   customer buys. `table-layout: fixed` means this number IS the column, so it has to fit the
   widest thing the cell can hold. */
/* uniform */
.ostz table.group_table td.woocommerce-grouped-product-list-item__quantity {
	inline-size: 150px;
}

/* uniform */
.ostz table.group_table td {
	padding: var(--ostz-space-3) var(--ostz-space-2);
	border-block-end: 1px solid var(--ostz-line, #262a35);
	vertical-align: middle;
}

/* uniform */
.ostz table.group_table td.woocommerce-grouped-product-list-item__label a {
	color: var(--ostz-text, #f2f4f8);
	font-weight: var(--ostz-weight-bold);
	text-decoration: none;
}

.ostz .woocommerce-product-details__short-description {
	margin-block-end: var(--ostz-space-4);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-md);
	line-height: var(--ostz-leading-body);
}

/* Category tiles — the `[product_categories]` shortcode and a shop page set to show categories.
   Same frame as a product card, because they sit in the same grid. */
.ostz li.product-category > a {
	display: block;
	overflow: hidden;
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius);
	background: var(--ostz-bg-raised, #15171e);
	color: var(--ostz-text, #f2f4f8);
	text-align: center;
	text-decoration: none;
	transition: border-color .18s ease;
}

.ostz li.product-category > a:hover {
	border-color: var(--ostz-accent, #00e0d5);
}

/* uniform */
.ostz li.product-category img {
	inline-size: 100%;
	block-size: auto;
	aspect-ratio: 1;
	object-fit: cover;
}

/* uniform */
.ostz li.product-category h2 {
	margin: 0;
	padding: var(--ostz-space-3);
	font-size: var(--ostz-text-base);
	font-weight: var(--ostz-weight-bold);
}

.ostz li.product-category mark.count {
	background: none;
	color: var(--ostz-text-faint, #828a9c);
	font-weight: var(--ostz-weight-normal);
	unicode-bidi: isolate;
}

/* The order-tracking form, the downloads table and «سفارش دوباره» — reachable only with a real
   order, which is why they are seeded rather than assumed. */
.ostz form.track_order {
	max-inline-size: 520px;
}

.ostz .woocommerce-order-downloads__title {
	margin: var(--ostz-space-8) 0 var(--ostz-space-4);
	font-size: var(--ostz-text-xl);
	font-weight: var(--ostz-weight-bold);
}

/* ================================================================== narrow screens

   ⚠ Not cosmetic. WooCommerce's smallscreen.css used to stack these tables and it is dequeued, so
   without this a six-column cart forces the whole PAGE to scroll sideways on a phone — a locked
   acceptance criterion. Each row becomes a card; each cell prints its own column name from
   `data-title`, which WooCommerce fills from the heading. */

@media (max-width: 768px) {

	.ostz .col2-set,
	.ostz .woocommerce-Addresses,
	.ostz .woocommerce-columns {
		grid-template-columns: 1fr;
	}

	/* uniform */
	.ostz table.shop_table_responsive thead,
	.ostz table.shop_table_responsive tbody th,
	.ostz table.woocommerce-orders-table thead {
		display: none;
	}

	/* uniform */
	.ostz table.shop_table_responsive tr,
	.ostz table.woocommerce-orders-table tr {
		display: block;
		margin-block-end: var(--ostz-space-4);
		border: 1px solid var(--ostz-line, #262a35);
		border-radius: var(--ostz-radius-sm);
		background: var(--ostz-bg-raised, #15171e);
	}

	/* uniform */
	.ostz table.shop_table_responsive tr td,
	.ostz table.woocommerce-orders-table tr td {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--ostz-space-3);
		inline-size: auto;
		text-align: start;
	}

	/* uniform */
	.ostz table.shop_table_responsive tr td::before,
	.ostz table.woocommerce-orders-table tr td::before {
		content: attr(data-title) ': ';
		color: var(--ostz-text-dim, #aab0be);
		font-size: var(--ostz-text-xs);
		font-weight: var(--ostz-weight-bold);
	}

	/* uniform — a cell whose content IS its own label prints no prefix.
	   ⚠ `product-thumbnail` belongs on this list and was missing: WooCommerce gives that cell an
	   EMPTY `data-title`, so the stacked row printed a bare «: » above the product image. Measured
	   on the phone cart. The empty-attribute selector catches any future cell with the same
	   omission rather than waiting for someone to notice it. */
	.ostz table.shop_table_responsive tr td.product-remove::before,
	.ostz table.shop_table_responsive tr td.product-thumbnail::before,
	.ostz table.shop_table_responsive tr td[data-title='']::before,
	.ostz table.shop_table_responsive tr td:not([data-title])::before,
	.ostz table.shop_table_responsive tr td.actions::before {
		content: '';
	}

	/* The shipping row of the totals card stacks: its label on one line, the choices under it.
	   As a side-by-side flex row (the uniform `td` rule above) the label took the start and the
	   choice list was squeezed into what was left — MEASURED 2026-09-27 at 375: the choice 88px
	   wide and its subtitle 58×130, a word per line (debt D9). */
	.ostz .cart_totals tr.woocommerce-shipping-totals td {
		flex-direction: column;
		align-items: stretch;
	}

	.ostz .actions .coupon input.input-text {
		max-inline-size: none;
	}

	/* On the stacked cart the actions cell is a flex row again (the uniform `td` rule above); it
	   must wrap, or the update button is squeezed to 99×76 beside the coupon. */
	.ostz table.shop_table_responsive tr td.actions {
		flex-wrap: wrap;
	}

	/*
	 * The CART row as a compact card: picture on the start side spanning the row, name + remove on
	 * top, price / quantity / subtotal stacked beside the picture. The generic one-cell-per-line
	 * stack above made every line 376–401px tall — four items were 1830px of table before the
	 * totals (پویش ظاهر 2026-09-19). Orders tables keep the generic stack.
	 */
	.ostz table.shop_table_responsive.cart tr.cart_item {
		display: grid;
		grid-template-columns: 60px minmax(0, 1fr) auto;
		grid-template-areas:
			"thumb name remove"
			"thumb price price"
			"thumb qty qty"
			"thumb sub sub";
		column-gap: var(--ostz-space-3);
		align-items: center;
		padding: var(--ostz-space-3);
	}

	.ostz table.shop_table_responsive.cart tr.cart_item td {
		min-inline-size: 0;
		padding: var(--ostz-space-1) 0;
		border: 0;
	}

	.ostz table.shop_table_responsive.cart tr.cart_item td.product-thumbnail {
		grid-area: thumb;
		align-self: start;
		inline-size: auto;
		padding: 0;
	}

	.ostz table.shop_table_responsive.cart tr.cart_item td.product-name {
		grid-area: name;
		justify-content: flex-start;
	}

	/* an empty `content:''` pseudo is still a flex item and costs one gap — it squeezed the
	   60px picture to 48. In this grid these cells print no label at all. */
	.ostz table.shop_table_responsive.cart tr.cart_item td.product-thumbnail::before,
	.ostz table.shop_table_responsive.cart tr.cart_item td.product-remove::before,
	.ostz table.shop_table_responsive.cart tr.cart_item td.product-name::before {
		content: none;
	}

	.ostz table.shop_table_responsive.cart tr.cart_item td.product-remove {
		grid-area: remove;
		align-self: start;
		inline-size: auto;
		padding: 0;
	}

	.ostz table.shop_table_responsive.cart tr.cart_item td.product-price {
		grid-area: price;
	}

	.ostz table.shop_table_responsive.cart tr.cart_item td.product-quantity {
		grid-area: qty;
	}

	.ostz table.shop_table_responsive.cart tr.cart_item td.product-subtotal {
		grid-area: sub;
	}
}

/* ==================================================== the plugin's one-page cart + checkout
 * Since 2026-09-23 the cart page is dm-smart-cart-modal's own template (`dm_scm_fast_checkout_
 * enabled`), which renders `.dm-scm-cp-*` markup and NONE of the WooCommerce table rules above can
 * reach it. What follows is the skin for it, and it is deliberately only four things: the ink that
 * failed contrast, the control that failed the tap floor, and nothing cosmetic.
 *
 * ⚠ This block SETS the plugin's published tokens and nothing else. The first version of it
 * re-declared the plugin's own internals (`--scm-muted`, `--scm-text`) from a 0-2-0 selector,
 * because at that moment there was no published name to set — and §5 of that plugin's doc forbids
 * a theme re-declaring paint on its classes, for the good reason that the next upstream release
 * would have silently taken it back. The answer was to publish the tokens there (2.38.0), not to
 * out-specify them here. If a name below stops existing upstream, this reverts silently and the
 * contrast failures come back with nothing red — which is what check-plugin-contract.php is for.
 */
.ostz .dm-scm-cart-page {
	/* ⚠ MEASURED 2026-09-23: the plugin's default muted ink scored 3.76–3.86 against the three
	   grounds it lands on (white, its own tint, and the page's own ground) on FIVE elements at
	   once — the item count, «جمع کالاها», «طبق روش انتخابی», the trust line and the shipping
	   note. One ink, five failures, which is why it is set at the token and not at each element.
	   Re-measured after the change, not calculated. */
	--dm-scm-ink-muted: var(--ostz-text-dim, #4f4a43);
	--dm-scm-ink: var(--ostz-text, #1a1a22);
	--dm-scm-line: var(--ostz-line, #ececf0);
	--dm-scm-surface-soft: var(--ostz-bg-sunken, #f6f6f9);
}

/* --- ostadeez-b-engagement.css --- */
/* OSTADEEZ — BRIDGE: the product-engagement plugin's controls.
   Split out of `ostadeez-scheme.css` on 2026-09-22 (theme 0.49.0).

   The waitlist button and its states. ⚠ On a «به‌زودی» product there is no add-to-cart, so this is
   the ONLY action on the page, and on a store where most products have no price yet it is the only
   conversion path there is — it is not decoration.

   ⚠ MEASURED before moving (waitlist + archive-action count, guest): home 7 · shop 14 ·
   product 4 · blog post 0. It renders wherever a product card or a product page does, which is
   exactly the audience of `ostadeez-shop` — so that is the dependency.

   ⚠ `.ostz-card .dm-waitlist-btn` is NOT here: a rule about the CARD belongs to the card, and it
   lives in `modules/ostadeez-m-product-card.css`. */

/* ---------------------------------------------------------------- product engagement
 *
 * The waitlist control and its dialog. ⚠ On a «به‌زودی» product there is no add-to-cart, so this
 * is the ONLY action on the page — and on a store where 20 of 26 products are priceless today,
 * it is the only conversion path there is. It rendered as a bare line of text: its markup carried
 * `btn btn-primary`, which are Bootstrap names, and this site ships no Bootstrap, so the control
 * matched zero rules in zero stylesheets. The fork now emits a real <button>; this gives it a look.
 */

.dm-waitlist-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	min-height: 40px;
	padding: 0 16px;
	border: 1px solid var(--ostz-accent);
	border-radius: var(--ostz-radius-sm);
	background: color-mix(in srgb, var(--ostz-accent) 12%, transparent);
	color: var(--ostz-accent);
	font-family: inherit;
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-bold);
	line-height: var(--ostz-leading-tight);
	cursor: pointer;
	transition: background .18s ease, color .18s ease, border-color .18s ease;
}

.dm-waitlist-btn:hover {
	background: var(--ostz-accent);
	color: var(--ostz-accent-ink);
}

.dm-waitlist-btn:focus-visible {
	outline: 2px solid var(--ostz-accent);
	outline-offset: 2px;
}

/* Already on the list: an outline rather than a fill, so the two states differ by more than a word
   and a returning visitor can see at a glance which products they are waiting on. */
.dm-waitlist-btn.active {
	border-color: var(--ostz-line-strong);
	background: transparent;
	color: var(--ostz-text-dim);
}

.dm-waitlist-btn .dm-icon {
	display: inline-flex;
	line-height: 0;
}

/* ⚠ `.ostz-card .dm-waitlist-btn` was declared here and moved to
   `modules/ostadeez-m-product-card.css` on 2026-09-22. A rule about the CARD belongs to the card:
   this sheet is the plugin skin and loads on templates that render no card at all. */

/* `.dm-modal-*` is dm-advanced-reviews' review dialog (0.25.1: the engagement plugin's waitlist
   modal that used to share these names is gone, and so are its `.dm-submit-btn` rules). */
.dm-modal-content {
	background: var(--ostz-bg-raised);
	color: var(--ostz-text);
	border: 1px solid var(--ostz-line-strong);
	border-radius: var(--ostz-radius);
}

.dm-modal-body input[type='tel'],
.dm-modal-body input[type='text'],
.dm-modal-body input[type='email'] {
	background: var(--ostz-bg-sunken);
	border: 1px solid var(--ostz-line);
	color: var(--ostz-text);
	font-family: inherit;
}

.dm-modal-body input::placeholder {
	color: var(--ostz-text-faint);
}

