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

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

.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-article.css --- */
/* ARTICLE — the reading page: a post, a page, an update, an attachment, the projects intro.

   Migrated out of `ostadeez-main.css` on 2026-09-03. The prose rules there were correct and are
   carried over unchanged; what this file ADDS is everything the article was missing — the meta
   line, the author card, and the way onward at the bottom.

   ⚠ THE ARTICLE HAD NO ENDING. `single.php` printed the content and stopped: no terms, no
   next/previous, nothing. That is the DONE-ROUTES-TOO case in its purest form — the reader
   finished exactly what they came for, and the site's answer was a footer. The terms row lives in
   `ostadeez-m-tag.css` (it is a row of tags); the two links at the foot are here. */

/* ---------------------------------------------------------------- the column */

.ostz-single {
	max-inline-size: var(--ostz-measure);
	margin-inline: auto;
	padding-block: var(--ostz-space-8);
}

.ostz-single-media {
	margin: 0 0 var(--ostz-space-8);
	border-radius: var(--ostz-radius);
	overflow: hidden;
}

.ostz-attachment-img {
	display: block;
	inline-size: 100%;
	block-size: auto;
}

.ostz-attachment-back {
	margin-block-start: var(--ostz-space-8);
}

/* ---------------------------------------------------------------- the meta line */

.ostz-single-meta {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--ostz-space-2);
	margin-block: var(--ostz-space-3) var(--ostz-space-6);
	color: var(--ostz-text-faint, #828a9c);
	font-size: var(--ostz-text-xs);
}

.ostz-single-date {
	unicode-bidi: isolate;
}

.ostz-single-read {
	unicode-bidi: isolate;
}

/* Generated, so the markup stays two spans and no separator is stranded if one is absent. */
.ostz-single-read::before {
	content: '·';
	margin-inline-end: var(--ostz-space-2);
}

/* ---------------------------------------------------------------- prose

   Carried over unchanged from ostadeez-main.css. The measure is the point: the article templates
   were running to the full 1160px shell, measured at 85 characters per line where readable prose
   is about 70. */

.ostz-prose {
	max-inline-size: var(--ostz-measure);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-md);
	line-height: var(--ostz-leading-body);
}

/* uniform — editor output has no classes of its own, so every paragraph, list and quote inside a
   prose block is styled the same by definition. This is the sanctioned bare-element case. */
.ostz-prose > * {
	margin-block: 0 var(--ostz-space-4);
}

/* uniform */
.ostz-prose > :last-child {
	margin-block-end: 0;
}

/* uniform */
.ostz-prose h2,
.ostz-prose h3,
.ostz-prose h4 {
	margin-block: var(--ostz-space-8) var(--ostz-space-3);
	color: var(--ostz-text, #f2f4f8);
	line-height: var(--ostz-leading-heading);
}

/* uniform */
.ostz-prose h2 {
	font-size: var(--ostz-text-xl);
	font-weight: var(--ostz-weight-bold);
}

/* uniform */
.ostz-prose h3 {
	font-size: var(--ostz-text-lg);
	font-weight: var(--ostz-weight-bold);
}

/* uniform */
.ostz-prose h4 {
	font-size: var(--ostz-text-base);
	font-weight: var(--ostz-weight-bold);
}

/* uniform */
.ostz-prose a {
	color: var(--ostz-accent, #00e0d5);
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* uniform */
.ostz-prose ul,
.ostz-prose ol {
	padding-inline-start: var(--ostz-space-6);
}

/* uniform */
.ostz-prose li {
	margin-block-end: var(--ostz-space-2);
}

/* uniform */
.ostz-prose blockquote {
	margin-inline: 0;
	padding-inline-start: var(--ostz-space-4);
	border-inline-start: 3px solid var(--ostz-accent, #00e0d5);
	color: var(--ostz-text, #f2f4f8);
}

/* uniform */
.ostz-prose code {
	padding: 2px 6px;
	border-radius: var(--ostz-radius-xs);
	background: var(--ostz-bg-sunken, #111318);
	font-size: var(--ostz-text-sm);
	direction: ltr;
	unicode-bidi: isolate;
}

/* uniform */
.ostz-prose img {
	border-radius: var(--ostz-radius-sm);
}

/* uniform — a table pasted into an article is the compact table, without needing a class the
   editor cannot add. */
.ostz-prose table {
	inline-size: 100%;
	border-collapse: collapse;
	font-size: var(--ostz-text-sm);
}

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

/* ---------------------------------------------------------------- author card

   Only rendered where the author actually wrote a bio. An avatar with an empty paragraph beside it
   is worse than no card — it reads as content that failed to load. */

.ostz-authorcard {
	display: flex;
	align-items: flex-start;
	gap: var(--ostz-space-4);
	max-inline-size: var(--ostz-shell);
	margin-block-end: var(--ostz-space-8);
	padding: var(--ostz-space-4);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius);
	background: var(--ostz-bg-raised, #15171e);
}

.ostz-authorcard-avatar {
	flex: none;
	inline-size: 56px;
	block-size: 56px;
	border-radius: 50%;
	object-fit: cover;
}

.ostz-authorcard-body {
	min-inline-size: 0;
}

.ostz-authorcard-name {
	margin: 0 0 4px;
	font-size: var(--ostz-text-md);
	font-weight: var(--ostz-weight-bold);
}

.ostz-authorcard-bio {
	max-inline-size: var(--ostz-measure);
	margin: 0;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
	line-height: var(--ostz-leading-snug);
}

/* ---------------------------------------------------------------- next / previous

   Two panels, each carrying the direction in WORDS above the title. An arrow alone is the single
   most reliably misread control in an RTL document, and «قبلی» is unambiguous in both. */

.ostz-postnav {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--ostz-space-4);
	max-inline-size: var(--ostz-measure);
	margin: 0 auto var(--ostz-space-16);
}

.ostz-postnav-link {
	display: flex;
	flex-direction: column;
	gap: 4px;
	min-inline-size: 0;
	padding: var(--ostz-space-4);
	border: 1px solid var(--ostz-line, #262a35);
	border-radius: var(--ostz-radius);
	background: var(--ostz-bg-raised, #15171e);
	color: var(--ostz-text, #f2f4f8);
	text-decoration: none;
	transition: border-color .18s ease;
}

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

/* The two panels are placed by the grid, not by a float, so nothing needs mirroring — only the
   text alignment differs, and «prev» is declared explicitly rather than left to the default: a
   variant with no rule is indistinguishable from a variant someone forgot. */
.ostz-postnav-link--prev {
	text-align: start;
}

.ostz-postnav-link--next {
	text-align: end;
}

.ostz-postnav-dir {
	color: var(--ostz-accent, #00e0d5);
	font-size: var(--ostz-text-xs);
	font-weight: var(--ostz-weight-bold);
}

/* One line. A neighbouring article's title is a hint, not the content. */
.ostz-postnav-title {
	overflow: hidden;
	font-size: var(--ostz-text-sm);
	font-weight: var(--ostz-weight-medium);
	line-height: var(--ostz-leading-snug);
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* A post with only one neighbour must not leave half the row empty and half filled — the single
   panel takes the row. */
.ostz-postnav-link:only-child {
	grid-column: 1 / -1;
}

@media (max-width: 620px) {
	.ostz-postnav {
		grid-template-columns: 1fr;
	}

	.ostz-postnav-link--next {
		text-align: start;
	}
}

/* --- ostadeez-m-tag.css --- */
/* TAG — a pill that says WHAT SOMETHING IS. A category, a tag, a chapter, a module, an attribute.

   ⚠ A tag is not a chip. The chip (`ostadeez-m-chip.css`) is a control with an on state and a tap
   target sized for a thumb; a tag is a label that happens to be clickable. Keeping them apart is
   what stops a taxonomy row slowly turning into a filter bar that filters nothing — which is the
   drift that produced `.ostz-update-tag` as a third pill shape in the first place.

   This file unifies THREE pill families that were each declared separately: the update tags
   (`ostadeez-updates.css`), the article terms row (which had no rule at all — the markup did not
   exist yet because the article had no term row), and the attribute pills. Same shape, same
   radius, same text step; the meaning is carried by the variant. */

.ostz-tagrow {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--ostz-space-2);
	margin-block: var(--ostz-space-6) 0;
}

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

/* A label that is not the first in the row needs air before it, or two groups run together. */
.ostz-tagrow-label ~ .ostz-tagrow-label {
	margin-inline-start: var(--ostz-space-3);
}

.ostz-tag {
	display: inline-flex;
	align-items: center;
	min-block-size: 26px;
	padding: 3px 11px;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: var(--ostz-radius-pill);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-xs);
	line-height: var(--ostz-leading-snug);
	text-decoration: none;
	transition: border-color .15s ease, color .15s ease, background .15s ease;
}

.ostz-tag:hover,
.ostz-tag:focus-visible {
	border-color: var(--ostz-accent, #00e0d5);
	background: var(--ostz-bg-raised-2, #1b1e27);
	color: var(--ostz-accent, #00e0d5);
}

/* ---------------------------------------------------------------- variants

   The two accents mean two different KINDS of grouping, and that meaning is fixed across the site:
   cyan is the primary axis a reader navigates by (topic, chapter), magenta is the secondary one
   (label, module). A third colour would be a third axis, and there isn't one. */

.ostz-tag--topic,
.ostz-tag--chapter {
	border-color: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent, #00e0d5);
}

.ostz-tag--label,
.ostz-tag--module {
	border-color: var(--ostz-accent-2, #c026ff);
	color: var(--ostz-accent-2, #c026ff);
}

.ostz-tag--label:hover,
.ostz-tag--module:hover,
.ostz-tag--label:focus-visible,
.ostz-tag--module:focus-visible {
	border-color: var(--ostz-accent-2, #c026ff);
	color: var(--ostz-accent-2, #c026ff);
}

/* PLAIN — a value, not a destination: an attribute term, a size on a spec row. No hover, because
   it is not a link. */
.ostz-tag--plain {
	color: var(--ostz-text-dim, #aab0be);
	cursor: default;
}

/* ---------------------------------------------------------------- the update tags

   The names `ostadeez-updates.css` already prints, mapped onto this component instead of being a
   fourth definition. Kept as aliases rather than renamed in PHP: the class is written in two
   template files and a term-list helper, and retiring a name in one of three writers is how a pill
   silently loses its style. They go when the update surfaces are next opened for real work. */

.ostz-update-tags {
	display: flex;
	flex-wrap: wrap;
	gap: var(--ostz-space-2);
	margin: 0 0 var(--ostz-space-3);
}

.ostz-update-tag {
	display: inline-flex;
	align-items: center;
	min-block-size: 26px;
	padding: 3px 11px;
	border: 1px solid var(--ostz-line-strong, #333846);
	border-radius: var(--ostz-radius-pill);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-xs);
	line-height: var(--ostz-leading-snug);
	text-decoration: none;
	transition: border-color .15s ease, color .15s ease, background .15s ease;
}

.ostz-update-tag--chapter {
	border-color: var(--ostz-accent, #00e0d5);
	color: var(--ostz-accent, #00e0d5);
}

.ostz-update-tag--module {
	border-color: var(--ostz-accent-2, #c026ff);
	color: var(--ostz-accent-2, #c026ff);
}

.ostz-update-tag:hover,
.ostz-update-tag:focus-visible {
	background: var(--ostz-bg-raised-2, #1b1e27);
}

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

.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. */
.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);
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-sm);
	cursor: pointer;
}

.ostz .woocommerce-form__input-checkbox,
.ostz input[type='checkbox'].input-checkbox {
	inline-size: 18px;
	block-size: 18px;
	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);
}

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

.ostz td.product-thumbnail {
	inline-size: 76px;
}

/* 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. */
.ostz td.actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--ostz-space-3);
}

.ostz .actions .coupon {
	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);
}

.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. */
.ostz .dmship-rate-subtitle {
	flex-basis: 100%;
	color: var(--ostz-text-dim, #aab0be);
	font-size: var(--ostz-text-xs);
	line-height: var(--ostz-leading-body);
}

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

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

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