/* ==========================================================================
   RJL DESIGN SYSTEM — 03 LAYOUT

   Containers, section rhythm and the grid primitives. Mobile-first: every
   rule here is written for the small screen and only widened inside
   min-width queries. There is not a single max-width media query in this file.
   ========================================================================== */

/* --------------------------------------------------------------------------
   CONTAINER
   ----------------------------------------------------------------------- */
.rjl-container {
	width: 100%;
	max-width: var(--rjl-container);
	margin-inline: auto;
	padding-inline: var(--rjl-gutter);
}

/* --------------------------------------------------------------------------
   SECTION RHYTHM
   One class controls vertical spacing site-wide. Pages compose from these
   rather than each setting its own margins, which is what stops the
   "every page spaced differently" problem.
   ----------------------------------------------------------------------- */
.rjl-section {
	position: relative;
	padding-top: var(--rjl-section-y);
	padding-bottom: var(--rjl-section-y-bottom);
}

.rjl-section--tight {
	padding-top: var(--rjl-section-y-tight);
	padding-bottom: var(--rjl-section-y-tight);
}

.rjl-section--flush-top { padding-top: 0; }

/* Sunk band — a half-step of contrast within the same palette, used instead
   of dropping a hard dark block into a light page. */
.rjl-section--sunk { background-color: var(--rjl-bg-sunk); }

/* --------------------------------------------------------------------------
   SECTION HEADER
   Deliberately left-aligned and asymmetric rather than the centred
   title-plus-subtitle block. On wide screens the heading and the supporting
   text sit in two columns, which breaks the centred-everything default.
   ----------------------------------------------------------------------- */
.rjl-section-head {
	display: grid;
	gap: var(--rjl-space-sm);
	margin-bottom: var(--rjl-space-xl);
}

.rjl-section-head__title { margin: 0; }

.rjl-section-head__text {
	margin: 0;
	color: var(--rjl-text-muted);
	font-size: var(--rjl-text-md);
	line-height: var(--rjl-leading-loose);
	max-width: var(--rjl-measure);
}

.rjl-section-head__link {
	font-family: var(--rjl-font-mono);
	font-size: var(--rjl-text-xs);
	font-weight: var(--rjl-weight-bold);
	letter-spacing: var(--rjl-track-label);
	text-transform: uppercase;
	text-decoration: none;
	color: var(--rjl-accent-ink);
	white-space: nowrap;
}
.rjl-section-head__link:hover { text-decoration: underline; }

@media (min-width: 48em) {
	.rjl-section-head {
		grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
		align-items: end;
		column-gap: var(--rjl-space-2xl);
	}
	/* The eyebrow and title share the first column. */
	.rjl-section-head__label,
	.rjl-section-head__title { grid-column: 1; }
	.rjl-section-head__text  { grid-column: 2; grid-row: 1 / span 2; align-self: end; }
}

/* --------------------------------------------------------------------------
   GRID PRIMITIVES
   CSS Grid throughout. No flexbox percentage arithmetic.
   ----------------------------------------------------------------------- */
.rjl-grid {
	display: grid;
	gap: var(--rjl-space-md);
}

.rjl-grid--2 { grid-template-columns: 1fr; }
.rjl-grid--3 { grid-template-columns: 1fr; }

@media (min-width: 40em) {
	.rjl-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.rjl-grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 64em) {
	.rjl-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
	.rjl-grid { gap: var(--rjl-space-lg); }
}

/* --------------------------------------------------------------------------
   ZIG-ZAG ROW
   The replacement for the three-equal-cards feature row. Alternating
   media/text pairs, stacked on mobile, two columns from 48em with the media
   side swapping each row.
   ----------------------------------------------------------------------- */
.rjl-zig {
	display: grid;
	gap: var(--rjl-space-lg);
	align-items: center;
}

.rjl-zig + .rjl-zig { margin-top: var(--rjl-space-2xl); }

@media (min-width: 48em) {
	.rjl-zig {
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: var(--rjl-space-2xl);
	}
	.rjl-zig--flip > .rjl-zig__media { order: 2; }
	.rjl-zig--flip > .rjl-zig__body  { order: 1; }
}

.rjl-zig__media {
	position: relative;
	border-radius: var(--rjl-radius-lg);
	overflow: hidden;
	background: var(--rjl-bg-sunk);
}

.rjl-zig__media img {
	width: 100%;
	aspect-ratio: 4 / 3;
	object-fit: cover;
}

/* --------------------------------------------------------------------------
   CLUSTER — horizontal wrapping group (button rows, tag lists, meta).
   ----------------------------------------------------------------------- */
.rjl-cluster {
	display: flex;
	flex-wrap: wrap;
	gap: var(--rjl-space-sm);
	align-items: center;
}
.rjl-cluster--sm { gap: var(--rjl-space-2xs); }
.rjl-cluster--between { justify-content: space-between; }

/* --------------------------------------------------------------------------
   RAIL — a sticky secondary column.

   .rjl-with-rail and .rjl-with-rail--rail-first were removed in the 04b audit:
   nothing used them. The shop archive dropped its sidebar (the category strip
   replaced it) and the contact page builds its own two-column grid. .rjl-rail
   itself IS used, by the practical-details column on tpl-contact.php, so the
   sticky behaviour stays.
   ----------------------------------------------------------------------- */
@media (min-width: 64em) {
	.rjl-rail {
		position: sticky;
		top: calc(var(--rjl-header-h) + var(--rjl-space-md));
	}
}

/* --------------------------------------------------------------------------
   HORIZONTAL SCROLLER
   Mobile-native pattern used for product rails and the hub strip, so a row of
   items stays a row on a phone instead of collapsing to a tall stack.
   Snaps to item starts. Scrollbar hidden but scrolling preserved.
   ----------------------------------------------------------------------- */
.rjl-scroller,
.rjl-scroller.rjl-products {
	display: flex;
	gap: var(--rjl-space-sm);
	overflow-x: auto;
	overscroll-behavior-x: contain;
	/* touch-action:auto forces the browser to disambiguate horizontal
	   drag vs. page vertical scroll on every touch move, which is what
	   reads as the rail fighting/shaking mid-swipe on iOS Safari.
	   pan-x tells it up front this element only pans horizontally, so
	   vertical touches fall straight through to the page. */
	touch-action: pan-x;
	/* proximity, not mandatory - mandatory forces a hard snap while you are
	   still mid-drag on iOS Safari, which is what reads as the rail
	   "shaking" as you swipe (2026-09 QA round 3: "excessive shaking" on
	   the FROM THE CRATES rail). Proximity still snaps once you let go,
	   without fighting the drag itself. */
	scroll-snap-type: x proximity;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: none;
	/* Bleed to the screen edge so cards run off the side rather than stopping
	   awkwardly inside the gutter. */
	margin-inline: calc(var(--rjl-gutter) * -1);
	padding-inline: var(--rjl-gutter);
	padding-bottom: var(--rjl-space-2xs);
}
.rjl-scroller::-webkit-scrollbar { display: none; }
.rjl-scroller > * { flex: 0 0 72vw; scroll-snap-align: start; }

@media (min-width: 40em) {
	.rjl-scroller > * { flex-basis: 20rem; }
}

/* From the large breakpoint the scroller becomes a normal grid — a horizontal
   scroller with three items on a 1400px screen looks broken. */
@media (min-width: 64em) {
	.rjl-scroller,
	.rjl-scroller.rjl-products {
		display: grid;
		grid-auto-flow: row;
		grid-template-columns: repeat(var(--rjl-scroller-cols, 3), minmax(0, 1fr));
		overflow-x: visible;
		margin-inline: 0;
		padding-inline: 0;
		gap: var(--rjl-space-lg);
	}
}

/* --------------------------------------------------------------------------
   MAIN
   ----------------------------------------------------------------------- */
.rjl-main {
	display: block;
	/* width is load-bearing, not cosmetic.
	   This is a direct child of Astra's .ast-container, which Astra's dynamic
	   CSS turns into a flex row above its tablet breakpoint (default 921, so
	   the query lands at min-width:922px). A block-level flex item with no
	   width shrinks to its content instead of filling the row, which is what
	   left a black empty gap down the side of every page on staging.

	   The flex is also switched off at source in style.css. This stays as
	   defence: any future Astra wrapper that becomes a flex container cannot
	   collapse this element again.

	   width:100% ALONE IS NOT ENOUGH — measured. With two children in a flex
	   row, both resolve 100% against the container and then shrink equally,
	   giving two half-width columns side by side. flex-shrink:0 is what
	   actually holds the full width. */
	width: 100%;
	flex-shrink: 0;
	min-height: 40vh;
}


/* 2026-09 QA: "excessive shaking" / "shake" swiping through From The Crates
   on a phone, reported as unresolved across multiple rounds even after
   touch-action:pan-x, scroll-snap-type:proximity and -webkit-overflow-
   scrolling:touch above. Root cause was never the touch/snap handling -
   every crate card also carries .rjl-reveal (see rjl-ui.js, section 3:
   SCROLL-IN REVEAL), a fade-and-rise-18px animation meant for content
   entering the VERTICAL viewport as the page scrolls. Its
   IntersectionObserver has no custom root, so it also fires the moment a
   card first becomes visible by being scrolled INTO the horizontal rail -
   meaning a card can start fading up mid-swipe, animating vertically at
   the same time the user's finger is dragging it horizontally. That
   combined motion is what reads as the rail "shaking". Neutralising the
   reveal treatment inside the rail (cards are already meant to render
   fully visible immediately here, not fade in) removes the extra motion
   at its source. */
.rjl-scroller .rjl-reveal {
	opacity: 1;
	transform: none;
	transition: none;
}
