/* ============================================================================
   BENIS SHOES — LAYOUT
   ----------------------------------------------------------------------------
   Containers, grids and the primitives that position things. These classes
   carry no colour, no border and no typography — only box and flow. That
   separation is what stops layout and skin drifting apart the way they did
   across the old v1–v17 layers, where a "card" rule set both its grid column
   and its shadow, so changing one broke the other.
   ========================================================================== */

/* ── Container ───────────────────────────────────────────────────────────── */

.container {
	width: 100%;
	max-inline-size: var(--container);
	margin-inline: auto;
	padding-inline: var(--gutter);
}

.container--wide { max-inline-size: var(--container-wide); }
.container--text { max-inline-size: var(--container-text); }
.container--flush { padding-inline: 0; }

/* ── Section ─────────────────────────────────────────────────────────────── */

.section {
	padding-block: var(--section-gap);
}

.section--tight { padding-block: var(--section-gap-sm); }

/* A UTILITY BAND, not a content section.
   The service strip is one row of four short promises. It was carrying the same
   --section-gap-sm as a whole product rail, so on a desktop the run from the
   banner to the first products was 40px of air, 170px of strip, and 40px more
   air — the strip was costing 250px to say four things. Its own card already
   supplies internal padding; what it needs from the page is separation, not a
   section's worth of ceremony. */
.section--strip { padding-block: var(--space-6); }
.section--flush-top { padding-block-start: 0; }
.section--flush-bottom { padding-block-end: 0; }
/* The alternating band, now RETIRED. It was inverted to white when the canvas
   was still a cool off-white and a --surface-soft band read as a 1% step. On
   the warm canvas the inversion backfires: a white band under a tinted page is
   not a rhythm, it is a seam — the page visibly changes colour halfway down.
   The canvas runs edge to edge instead, and the rails still read as raised,
   because the white cards inside them are the thing lifting off it. The class
   stays because it also selects the roomier --section-gap padding. */
.section--soft { background-color: var(--surface-page); }

/* ── Stack — vertical rhythm without margin collapsing surprises ─────────── */

.stack {
	display: flex;
	flex-direction: column;
	gap: var(--space-4);
}

.stack--xs { gap: var(--space-1); }
.stack--sm { gap: var(--space-2); }
.stack--md { gap: var(--space-6); }
.stack--lg { gap: var(--space-8); }

/* ── Cluster — horizontal groups that wrap rather than overflow ──────────── */

.cluster {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-3);
}

.cluster--sm { gap: var(--space-2); }
.cluster--lg { gap: var(--space-5); }
.cluster--between { justify-content: space-between; }
.cluster--end { justify-content: flex-end; }
.cluster--nowrap { flex-wrap: nowrap; }

/* ── Grid ────────────────────────────────────────────────────────────────── */

.grid {
	display: grid;
	gap: var(--space-5);
}

/* Product grids size from their own container, not the viewport. This is the
   one genuinely correct idea in the old benis-v11 layer and it is kept: a
   grid inside a filtered PLP sits in a narrower column than the same grid on
   the homepage, and a viewport-based media query gets that wrong every time. */
.grid--auto {
	grid-template-columns: repeat(auto-fill, minmax(var(--grid-min, 15rem), 1fr));
}

.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

/* Mobile-first: the multi-column variants collapse below tablet rather than
   being written desktop-first and undone with max-width queries. */
@media (max-width: 1023px) {
	.grid--4 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 767px) {
	.grid--3,
	.grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 419px) {
	.grid--stack-xs { grid-template-columns: minmax(0, 1fr); }
}

/* ── Sidebar layout — PLP and account pages ──────────────────────────────── */

.with-sidebar {
	display: grid;
	gap: var(--space-8);
	grid-template-columns: minmax(0, 1fr);
}

/* The rail's visibility is tied to the same breakpoint as the two-column
   layout, not to a generic hide-mobile utility. Those disagreed: the utility
   revealed the rail at 768px while the sidebar column and the mobile filter
   button both switched at 1024px, so between those widths the page showed the
   filter rail AND the filter button — two ways to do the same thing. */
.with-sidebar__aside { display: none; }

@media (min-width: 1024px) {
	.with-sidebar__aside { display: block; }

	.with-sidebar {
		grid-template-columns: var(--sidebar-width, 16rem) minmax(0, 1fr);
		align-items: start;
	}

	/* The filter rail tracks the sticky header so it stays reachable while the
	   product grid scrolls past it. */
	.with-sidebar__aside {
		position: sticky;
		inset-block-start: calc(var(--header-height-lg) + var(--space-4));
		max-block-size: calc(100svh - var(--header-height-lg) - var(--space-8));
		overflow-y: auto;
		overscroll-behavior: contain;
	}
}

/* ── Rail — horizontally scrolling product/category strips ───────────────── */

.rail {
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: var(--rail-item, 60vw);
	gap: var(--space-4);
	overflow-x: auto;
	overscroll-behavior-inline: contain;
	/* PROXIMITY, not mandatory.
	   `mandatory` obliges the browser to land on a snap point, so a rail grabs
	   the gesture and pulls against a finger that was really trying to scroll
	   the page past it. On a homepage carrying five rails that reads as
	   scrolling being broken. Proximity still snaps a deliberate horizontal
	   swipe and lets go of everything else. */
	scroll-snap-type: inline proximity;
	padding-block-end: var(--space-2);
	/* Hidden bar, but the rail stays keyboard- and wheel-scrollable. */
	scrollbar-width: none;

	/* A rail spans the full page width so the next card peeks past the edge,
	   but its FIRST card must still line up with the container's content edge.
	   Below the container width that is just the gutter; above it, the gutter
	   plus however much the centred container is inset. One expression covers
	   both, which is why this needs no bleed modifier and no media query. */
	--rail-inset: max(
		var(--gutter),
		calc((100% - var(--container)) / 2 + var(--gutter))
	);
	padding-inline: var(--rail-inset);
	scroll-padding-inline-start: var(--rail-inset);
}

.rail::-webkit-scrollbar { display: none; }

.rail > * { scroll-snap-align: start; }

@media (min-width: 560px)  { .rail { --rail-item: 38vw; } }
@media (min-width: 768px)  { .rail { --rail-item: 28vw; } }
@media (min-width: 1024px) { .rail { --rail-item: 21vw; } }
@media (min-width: 1280px) { .rail { --rail-item: 17rem; } }

/* A rail of ARTICLES, not products. A post card is a 3:2 photograph over a
   title, an excerpt and a meta line, and at the catalogue's column width that
   excerpt wraps to six lines — the card stops reading as a card and starts
   reading as a paragraph with a picture on top. One step wider at every
   breakpoint is the whole fix, so the magazine needs no rail component of its
   own; it borrows this one and overrides the column.
   Must stay AFTER the .rail media queries above: same specificity, so order
   is what decides. */
.rail--posts { --rail-item: 80vw; }

@media (min-width: 560px)  { .rail--posts { --rail-item: 52vw; } }
@media (min-width: 768px)  { .rail--posts { --rail-item: 38vw; } }
@media (min-width: 1024px) { .rail--posts { --rail-item: 29vw; } }
@media (min-width: 1280px) { .rail--posts { --rail-item: 23rem; } }

/* There was a .rail--bleed modifier here that pulled the rail outward with a
   negative margin-inline. It assumed a padded parent; used inside
   .container--flush it made the rail 424px wide in a 390px viewport and
   overflowed the page by 34px. The --rail-inset calculation above removes the
   need for it entirely, so the modifier is gone rather than corrected. */

/* ── Media ratios — reserved space, so images cannot shift layout ────────── */

.ratio {
	position: relative;
	overflow: hidden;
	background-color: var(--surface-soft);
}

.ratio > img,
.ratio > video {
	position: absolute;
	inset: 0;
	inline-size: 100%;
	block-size: 100%;
	object-fit: cover;
}

.ratio--square   { aspect-ratio: 1 / 1; }
.ratio--product  { aspect-ratio: var(--ratio-product); }  /* the canonical product ratio */
.ratio--portrait { aspect-ratio: 4 / 5; }
.ratio--wide     { aspect-ratio: 16 / 9; }
.ratio--hero     { aspect-ratio: 4 / 5; }

@media (min-width: 768px) {
	.ratio--hero { aspect-ratio: 21 / 9; }
}

/* ── Utilities — deliberately few ────────────────────────────────────────── */

.full-bleed {
	margin-inline: calc(50% - 50vw);
	max-inline-size: 100vw;
}

.hide { display: none !important; }   /* state utility; must beat component display */

@media (max-width: 767px)  { .hide-mobile  { display: none !important; } }
@media (min-width: 768px)  { .hide-desktop { display: none !important; } }

/* Shown only when scripting is unavailable. `html.js` is set synchronously in
   <head> (see header.php), so this never flashes. Used for controls that exist
   purely as the no-script fallback for something a script does automatically —
   the cart's "update basket" button beside a stepper that already submits. */
html.js .no-js-only { display: none !important; }

.flow-grow { flex: 1 1 auto; min-inline-size: 0; }

/* min-width:0 on grid/flex children is what actually prevents long Persian
   product titles from forcing horizontal overflow. */
.grid > *,
.cluster > * { min-inline-size: 0; }
