/*
 * theme11 — Newsletter : the single custom stylesheet
 *
 * A port of the reference theme's styles/index.less. That file was compiled by
 * OJS with LESS variables; there is no LESS step here, so the palette lands as
 * CSS custom properties instead. The three brand shades are computed in PHP
 * from one admin setting and emitted by front/objects/header.php into a :root
 * block that overrides the fallbacks below — so the whole palette still derives
 * from a single colour, exactly as the reference did.
 *
 * Discipline carried over from the reference, and worth keeping:
 *   - 15 colours in the entire theme. Every rule goes through a variable;
 *     there is no stray hex anywhere below.
 *   - Logical properties throughout (margin-inline, inset-inline, text-align:
 *     start). RTL then needs only the handful of overrides in section 11 —
 *     grid column numbers are the one thing that is not logical.
 *   - Every selector is nl-prefixed so nothing collides with theme9, whose
 *     stylesheet still styles all interior pages.
 */

/* =====================================================================
   1. Tokens
   ===================================================================== */

:root {
	/* Brand — overridden by the header's computed block. */
	--nl-brand:       #00693e;
	--nl-brand-dark:  #004026;
	--nl-brand-tint:  #e5f0eb;

	--nl-gold:        #c8a24b;

	--nl-ink:         #16191d;
	--nl-ink-soft:    #2b2f36;
	--nl-muted:       #6b7280;

	--nl-surface:     #ffffff;
	--nl-surface-alt: #f6f7f8;
	--nl-border:      #e5e7eb;

	--nl-alert:       #c8102e;   /* ticker label ONLY */
	--nl-on-gold:     #1c1300;   /* text on the gold badge */
	--nl-on-brand:    #dfeae4;   /* footer text on brand-dark */

	--nl-radius:      12px;
	--nl-gap:         22px;
	--nl-thumb:       150px;   /* issue-block list thumbnail; 90px on mobile */
	--nl-rail-cols:   4;       /* cards visible across the sliding news rail */
	--nl-maxw:        1680px;

	/* Cool near-black, never pure black. */
	--nl-shadow:      0 1px 2px rgba(16, 24, 40, .06), 0 1px 3px rgba(16, 24, 40, .10);
	--nl-shadow-lg:   0 10px 30px rgba(16, 24, 40, .14);
	/* Spans the whole slide, not just the caption box, because the photo drifts
	   underneath it — the headline has to stay legible against whatever region
	   of the image arrives, not just the one that started there. */
	--nl-hero-scrim:  linear-gradient(to top,
	                    rgba(8, 12, 10, .88) 0%,
	                    rgba(8, 12, 10, .66) 18%,
	                    rgba(8, 12, 10, .30) 40%,
	                    rgba(8, 12, 10, 0) 64%);

	--nl-font-sans:   "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	--nl-font-serif:  "Source Serif 4", Georgia, "Times New Roman", serif;
	--nl-font-ar:     "IBM Plex Sans Arabic", "Inter", sans-serif;
}

/* =====================================================================
   2. Shell
   ===================================================================== */

.nl-home *,
.nl-header *,
.nl-footer * { box-sizing: border-box; }

.nl-shell {
	max-width: var(--nl-maxw);
	margin-inline: auto;
	padding-inline: 24px;
}

.nl-home img { display: block; max-width: 100%; }

.nl-home,
.nl-header,
.nl-footer { font-family: var(--nl-font-sans); }

/* Gold focus ring, global to every interactive element the theme owns. */
.nl-home a:focus-visible,
.nl-home button:focus-visible,
.nl-home input:focus-visible,
.nl-home [role="tab"]:focus-visible,
.nl-header a:focus-visible,
.nl-header button:focus-visible,
.nl-header input:focus-visible,
.nl-footer a:focus-visible {
	outline: 3px solid var(--nl-gold);
	outline-offset: 2px;
	border-radius: 6px;
}

/* Keyboard escape hatch past the masthead and nav. */
.nl-skip {
	position: absolute;
	inset-inline-start: -9999px;
	top: 0;
	z-index: 100;
	background: var(--nl-brand);
	color: #fff;
	padding: 10px 18px;
	border-radius: 0 0 8px 0;
	font-weight: 600;
	text-decoration: none;
}
.nl-skip:focus { inset-inline-start: 0; }

/* The homepage runs full width; interior pages keep theme9's own shell. */
.nl-main { display: block; }

/* =====================================================================
   3. Ticker
   ===================================================================== */

.nl-ticker {
	display: flex;
	align-items: stretch;
	background: var(--nl-surface-alt);
	border: 1px solid var(--nl-border);
	border-radius: 10px;
	overflow: hidden;
	margin-block: 18px;
}
.nl-ticker__label {
	background: var(--nl-alert);
	color: #fff;
	font-weight: 700;
	font-size: .82rem;
	letter-spacing: .03em;
	text-transform: uppercase;
	padding: 0 16px;
	display: flex;
	align-items: center;
	white-space: nowrap;
}
.nl-ticker__track { flex: 1; overflow: hidden; }
.nl-ticker__list {
	display: flex;
	gap: 48px;
	align-items: center;
	white-space: nowrap;
	padding-inline: 24px;
	height: 44px;
	margin: 0;
	animation: nl-marquee 38s linear infinite;
}
/* :focus-within so keyboard users get the pause too. */
.nl-ticker:hover .nl-ticker__list,
.nl-ticker:focus-within .nl-ticker__list { animation-play-state: paused; }

.nl-ticker__list a {
	font-size: .9rem;
	color: var(--nl-ink-soft);
	display: flex;
	align-items: center;
	gap: 10px;
	text-decoration: none;
}
.nl-ticker__list a:hover { color: var(--nl-brand); }
.nl-ticker__list a::before {
	content: "";
	width: 6px;
	height: 6px;
	border-radius: 50%;
	background: var(--nl-gold);
	flex: 0 0 auto;
}

/* -50% works because ticker.js duplicates the list. */
@keyframes nl-marquee {
	from { transform: translateX(0); }
	to   { transform: translateX(-50%); }
}

/* =====================================================================
   4. Hero
   ===================================================================== */

/*
 * Layering, bottom to top. Only layers 1 and 3 ever move; the caption, the
 * arrows and the dots are pinned.
 *
 *   1  .nl-hero__img            the photo, slow breathing zoom
 *   2  .nl-hero__slide::after   scrim, so the headline holds contrast as the
 *                               photo's brighter regions move underneath
 *   4  .nl-hero__caption        eyebrow + headline
 *   5  .nl-hero__btn/__dot      controls
 *
 * Layer 3 was a travelling light sweep; it was removed in favour of a single
 * motion language shared with the cards. `isolation` stays — it is what makes
 * the z-indexes resolve in one stacking context rather than by DOM accident.
 */
.nl-hero {
	position: relative;
	border-radius: 16px;
	overflow: hidden;
	isolation: isolate;
	aspect-ratio: 21 / 9;
	background: var(--nl-surface-alt);
	box-shadow: var(--nl-shadow-lg);
}
.nl-hero__slide {
	position: absolute;
	inset: 0;
	opacity: 0;
	visibility: hidden;
	transition: opacity .8s ease;
}
.nl-hero__slide.is-active { opacity: 1; visibility: visible; }

.nl-hero__img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
	/*
	 * The still frame, and the only frame under prefers-reduced-motion. It is
	 * the drift keyframes' own start scale, so turning motion off changes
	 * nothing about the crop or the composition.
	 *
	 * Scale only, no pan: the banner breathes the same way the feature cards do.
	 * With no translation and a base scale never below 1.05 there is no
	 * arithmetic to get wrong — the photo cannot expose an edge at any point in
	 * the cycle.
	 */
	transform: scale(1.04);
	will-change: transform;
	backface-visibility: hidden;
}

/* Layer 2 — the scrim, on its own layer rather than as the caption's
   background, so the sweep can pass between the photo and the text. */
.nl-hero__slide::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: 1;
	pointer-events: none;
	background: var(--nl-hero-scrim);
}

.nl-hero__caption {
	position: absolute;
	inset-inline: 0;
	bottom: 0;
	z-index: 3;
	padding: 34px 38px;
	color: #fff;
}
.nl-hero__eyebrow {
	display: inline-block;
	font-size: .72rem;
	font-weight: 700;
	letter-spacing: .12em;
	text-transform: uppercase;
	background: var(--nl-gold);
	color: var(--nl-on-gold);
	padding: 4px 10px;
	border-radius: 5px;
	margin-bottom: 12px;
}
/*
 * The colour is set explicitly rather than inherited from .nl-hero__caption.
 * The reference could rely on inheritance; here theme9's stylesheet carries a
 * bare `h1..h6 { color: var(--color-heading) }`, which beats inheritance no
 * matter what order the sheets load in, and rendered this title in theme9's
 * dark teal against the scrim — effectively illegible. Same reason
 * .nl-news__title below is explicit.
 */
.nl-hero__title {
	font-family: var(--nl-font-serif);
	font-weight: 700;
	font-size: 2rem;
	line-height: 1.2;
	margin: 0;
	max-width: 62ch;
	color: #fff;
	text-shadow: 0 1px 14px rgba(0, 0, 0, .35);
}
.nl-hero__title a { color: #fff; text-decoration: none; }

.nl-hero__btn {
	position: absolute;
	z-index: 4;              /* controls stay above the sweep */
	top: 50%;
	transform: translateY(-50%);
	width: 46px;
	height: 46px;
	border-radius: 50%;
	border: 0;
	background: rgba(255, 255, 255, .85);
	color: var(--nl-ink);
	cursor: pointer;
	display: grid;
	place-items: center;
	font-size: 1.2rem;
	line-height: 1;
	box-shadow: var(--nl-shadow);
}
.nl-hero__btn:hover { background: #fff; }
/* Logical, so prev/next swap under RTL for free. */
.nl-hero__btn--prev { inset-inline-start: 16px; }
.nl-hero__btn--next { inset-inline-end: 16px; }

.nl-hero__dots {
	position: absolute;
	z-index: 4;
	bottom: 18px;
	inset-inline-end: 24px;
	display: flex;
	gap: 9px;
}
.nl-hero__dot {
	width: 9px;
	height: 9px;
	border-radius: 50%;
	border: 0;
	background: rgba(255, 255, 255, .55);
	cursor: pointer;
	padding: 0;
	transition: width .2s, border-radius .2s;
}
/* The active dot stretches into a pill. */
.nl-hero__dot.is-active { background: #fff; width: 24px; border-radius: 6px; }

/* ---- Hero motion: a slow zoom in and out ----
 *
 * The effect animates `transform` and nothing else, so it stays on the
 * compositor and never triggers layout. It is gated twice:
 *
 *   - `prefers-reduced-motion: no-preference` — the rules do not exist at all
 *     for a reader who asked for less motion, rather than being declared and
 *     then switched off. The banner is then simply a still photo.
 *   - `.nl-hero.is-animating` — hero.js adds it only while the banner is in the
 *     viewport and the tab is visible, so nothing ticks off-screen.
 *
 * A continuous breathe: in and out, for ever, `alternate` so it eases back the
 * way it came instead of snapping at the loop end.
 *
 * Two earlier versions failed for opposite reasons, and both are worth keeping
 * in mind before touching the numbers:
 *
 *   - 0.14 of scale over 20s, in sync across every slide. Measured, it was
 *     animating; watched, it looked completely static. At ~10px of width per
 *     second, and with the autoplay replacing the photograph every 6s, there was
 *     never a stable reference to notice the movement against. Slowing it made
 *     it worse, not better.
 *   - The same range over 8s, restarted per slide with `ease-out both`. Visible
 *     at last, but one-way: the fill held the end scale until the slide changed
 *     and the next photo began again from the bottom, so it only ever zoomed
 *     IN. Never out.
 *
 * This runs on every slide, in sync, so a slide change never interrupts it: the
 * incoming photograph picks up at the scale the outgoing one had and keeps
 * travelling the same way. 0.14 over 9s is about 22px of width per second —
 * slow, but well clear of the threshold where it stops registering — and a full
 * there-and-back takes 18s, so both halves of the movement are on screen.
 */
@keyframes nl-hero-drift {
	from { transform: scale(1.04); }
	to   { transform: scale(1.18); }
}

@media (prefers-reduced-motion: no-preference) {
	.nl-hero.is-animating .nl-hero__img {
		animation: nl-hero-drift 9s ease-in-out infinite alternate;
	}
}

/* =====================================================================
   5. Subject row
   ===================================================================== */

.nl-block { margin-block: 54px; }

.nl-block__head {
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: 16px;
	border-bottom: 2px solid var(--nl-brand);
	padding-bottom: 10px;
	margin-bottom: 18px;
}
.nl-block__title {
	font-family: var(--nl-font-serif);
	font-weight: 700;
	font-size: 1.6rem;
	margin: 0;
	color: var(--nl-ink);
}
.nl-block__more {
	color: var(--nl-brand);
	font-weight: 600;
	font-size: .9rem;
	text-decoration: none;
	white-space: nowrap;
}
.nl-block__more:hover { color: var(--nl-brand-dark); }

/* ---- Sub-tabs ---- */

.nl-tabs {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin-bottom: 22px;
}
.nl-tab {
	border: 1px solid var(--nl-border);
	background: var(--nl-surface);
	border-radius: 999px;
	padding: 7px 18px;
	font-family: var(--nl-font-sans);
	font-size: .9rem;
	font-weight: 600;
	color: var(--nl-ink-soft);
	cursor: pointer;
	transition: background .15s, border-color .15s, color .15s;
}
.nl-tab:hover { border-color: var(--nl-brand); color: var(--nl-brand); }
/* The ARIA attribute *is* the state — no helper class. */
.nl-tab[aria-selected="true"] {
	background: var(--nl-brand);
	border-color: var(--nl-brand);
	color: #fff;
}
.nl-tabpanel[hidden] { display: none; }

/* =====================================================================
   6. Card grid
   ===================================================================== */

.nl-grid {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	gap: var(--nl-gap);
	/* Stated, not inherited: the equal-height mechanism below depends on the
	   grid stretching every card in a row to the row height. */
	align-items: stretch;
}
/* Back-fills around the big card so the grid never has holes. */
.nl-grid--mixed { grid-auto-flow: row dense; }

.nl-card {
	position: relative;      /* the feature tier positions media and body in here */
	background: var(--nl-surface);
	border: 1px solid var(--nl-border);
	border-radius: var(--nl-radius);
	overflow: hidden;
	box-shadow: var(--nl-shadow);
	display: flex;
	flex-direction: column;
	transition: box-shadow .2s, transform .2s;
}
.nl-card:hover,
.nl-card:focus-within { box-shadow: var(--nl-shadow-lg); transform: translateY(-2px); }

/* ---- Span classes, chosen by buildLayout() ---- */

.nl-card--featured     { grid-row: 1 / span 2; }
.nl-card--side-left    { grid-column: 1 / span 2; }
.nl-card--side-right   { grid-column: 3 / span 2; }
.nl-card--merged       { grid-column: span 2; }
.nl-card--merged-left  { grid-column: 1 / 3; }
.nl-card--merged-right { grid-column: 3 / 5; }
.nl-card--hero         { grid-column: 1 / -1; grid-row: 1; }

/* The hero is a full-bleed banner across all four columns; 16/9 at that width
   is over 600px tall, so it keeps a cinematic ratio. It is the one exception,
   and it is still a fixed ratio — never intrinsic. */
.nl-card--hero .nl-card__media { aspect-ratio: 21 / 9; }

/* Entrance for the prominent cards only, CSS-only, runs on load. */
@keyframes nl-card-in {
	from { opacity: 0; transform: translateY(12px); }
	to   { opacity: 1; transform: none; }
}
.nl-card--featured,
.nl-card--hero { animation: nl-card-in .5s ease both; }

/* ---- Card internals ---- */

/*
 * The equal-height mechanism.
 *
 * The media box owns a FIXED ratio (flex:none + aspect-ratio) and the body is
 * flex:1, so a card's height is `ratio box + text`, and when the grid stretches
 * a row to a common height the slack goes into the BODY — the images stay
 * identical and the CTAs bottom-align via `margin-top:auto` below.
 *
 * It used to be the other way round (media flex:1, body flex:0), which meant
 * the media had no definite height, so its flex base size came from the
 * image's own intrinsic height: a portrait source produced a 683x1024 image
 * box, and two cards in one row whose bodies differed by a line got visibly
 * different image heights. Ratio-first is what keeps them equal.
 *
 * That, plus grid-auto-flow:row dense above, is the whole no-gaps guarantee.
 */
.nl-card__media {
	position: relative;      /* badge anchor */
	flex: none;
	aspect-ratio: 16 / 9;
	background: var(--nl-surface-alt);
	overflow: hidden;        /* crop + inherit the card's rounded corners */
}
.nl-card__media img {
	display: block;          /* no inline leading inside the ratio box */
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
	transform: scale(1);
	transition: transform .5s cubic-bezier(.22, 1, .36, 1);
	will-change: transform;
	backface-visibility: hidden;
}
/*
 * Standard cards zoom on hover only — no ambient animation. A grid of six
 * thumbnails breathing at once is noise, and it burns battery for nothing.
 *
 * Triggered from the CARD, so hovering the headline zooms the photo too, and
 * `:focus-within` gives keyboard users the same feedback. The growth crops
 * inside the media box (overflow:hidden, fixed ratio) so nothing reflows.
 */
.nl-card:hover .nl-card__media img,
.nl-card:focus-within .nl-card__media img { transform: scale(1.06); }

.nl-card__badge {
	position: absolute;
	inset-inline-start: 12px;
	top: 12px;
	background: var(--nl-gold);
	color: var(--nl-on-gold);
	font-size: .68rem;
	font-weight: 700;
	letter-spacing: .06em;
	text-transform: uppercase;
	padding: 4px 10px;
	border-radius: 5px;
}

.nl-card__body {
	padding: 15px 17px;
	display: flex;
	flex-direction: column;
	gap: 9px;
	flex: 1 1 auto;          /* takes the row's slack — see the note above */
}
.nl-card__chip {
	align-self: flex-start;
	font-size: .7rem;
	font-weight: 700;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--nl-brand);
	background: var(--nl-brand-tint);
	border-radius: 6px;
	padding: 3px 9px;
}
.nl-card__title {
	font-family: var(--nl-font-serif);
	font-weight: 600;
	font-size: 1.02rem;
	line-height: 1.32;
	margin: 0;
	color: var(--nl-ink);
	display: -webkit-box;
	-webkit-line-clamp: 3;
	line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.nl-card__title a { text-decoration: none; color: inherit; }
.nl-card__title a:hover { color: var(--nl-brand); }

/* Title size steps up with prominence. */
.nl-card--merged   .nl-card__title { font-size: 1.3rem;  line-height: 1.26; }
.nl-card--featured .nl-card__title { font-size: 1.55rem; line-height: 1.22; }
.nl-card--hero     .nl-card__title { font-size: 1.85rem; line-height: 1.2; }

/* ---- The excerpt, defined once ----
 *
 * One class for every variant — cards in both tiers, the issue block's hero
 * cards and its list rows. Only the colour differs, and only for the overlay
 * case; everything else about it is shared, so there is one place to change it.
 *
 * The height is RESERVED, not measured: a fixed line-height plus a min-height of
 * exactly the clamped line count means a 20-character excerpt and a 200-
 * character one occupy the same box, and an article with no abstract at all
 * still holds its place instead of collapsing and dragging the row out of
 * alignment. That is the whole reason it is min-height and not padding.
 */
.nl-excerpt {
	--nl-excerpt-lines: 2;
	color: var(--nl-muted);
	font-size: .88rem;
	font-weight: 400;          /* subordinate to the headline, never bold */
	line-height: 1.5;
	margin: 0;
	display: -webkit-box;
	-webkit-line-clamp: var(--nl-excerpt-lines);
	line-clamp: var(--nl-excerpt-lines);
	-webkit-box-orient: vertical;
	overflow: hidden;
	min-height: calc(var(--nl-excerpt-lines) * 1.5em);
}

.nl-card__more {
	margin-top: auto;
	align-self: flex-start;
	font-size: .82rem;
	font-weight: 700;
	color: var(--nl-brand);
	text-decoration: none;
}
.nl-card__more:hover { color: var(--nl-brand-dark); }

/* Source line: category badge and timestamp on one line, above the headline. */
.nl-card__source {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
}
/* Hidden by default and shown only by the feature tier below, which is also
   what makes the mobile fallback work: a feature-variant card keeps its class at
   narrow widths, so hiding the meta line per-tier would leave it visible there. */
.nl-card__meta {
	display: none;
	font-size: .78rem;
	color: var(--nl-muted);
}

/* ---- Engagement bar ----
   Pinned to the bottom of the body with margin-top:auto, which is what makes
   the bars line up across a row whatever the headlines do above them. */
.nl-card__reactions {
	margin-top: auto;
	padding-top: 4px;
	display: flex;
	align-items: center;
	gap: 16px;
	font-size: .78rem;
	color: var(--nl-muted);
}
.nl-card__reaction {
	display: inline-flex;
	align-items: center;
	gap: 6px;
}
.nl-card__reaction i { font-size: .82rem; opacity: .85; }
/* Visually hidden, still announced — the icons carry no accessible name. */
.nl-sr {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

/* ---- The feature row, and the rail beneath it ----
 *
 * A news row is split in two: the feature cards keep a grid, and the standard
 * cards move into a track that slides right and left (marquee.js). The feature
 * cards deliberately do not move — a large overlay headline drifting sideways is
 * hard to read — while the standard cards are interchangeable and of equal
 * weight, which is what makes them safe to put on a rail.
 *
 * The span classes are reset inside the feature grid. They exist to place cards
 * among their narrow neighbours (`--merged` spans 2 of 4 columns, `--featured`
 * spans 2 rows); with only feature cards left in the container those spans would
 * leave the row half empty. One equal column per card instead.
 */
.nl-grid--features {
	grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}
.nl-grid--features > .nl-card {
	grid-column: auto;
	grid-row: auto;
}

.nl-rail { margin-top: var(--nl-gap); }
.nl-rail__vp { overflow: hidden; }
/* The edge fade belongs to a track that actually travels — marquee.js sets
   `is-marquee` only when there is somewhere to travel to. On a row of exactly
   four cards it would just dim the first and last card for no reason. */
.nl-rail__vp.is-marquee {
	-webkit-mask-image: linear-gradient(90deg, transparent, #000 2%, #000 98%, transparent);
	mask-image: linear-gradient(90deg, transparent, #000 2%, #000 98%, transparent);
}
/*
 * Exactly --nl-rail-cols cards across the viewport, never a fraction of one.
 *
 * A fixed card width (300px) put 4.6 cards in a 1392px shell, so a sliced card
 * always hung off each edge and the row never lined up with the feature cards
 * above it. Sizing the columns as a share of the viewport instead keeps the rail
 * on the same rhythm as the rest of the page at every width.
 *
 * The percentage resolves against the track's own `width: 100%` — that is the
 * viewport's width, not the track's overflowing content width — which is what
 * makes the arithmetic work while the grid still overflows and has room to
 * travel.
 */
.nl-rail__track {
	display: grid;
	grid-auto-flow: column;
	grid-auto-columns: calc(
		(100% - (var(--nl-rail-cols) - 1) * var(--nl-gap)) / var(--nl-rail-cols)
	);
	align-items: stretch;    /* every card on the rail ends level */
	gap: var(--nl-gap);
	width: 100%;
	will-change: transform;
}

/* =====================================================================
   6b. Tier 1 — the feature card
   =====================================================================
   The photo fills the whole card and the text sits on it, bottom-left, over a
   scrim. There is no white body area and no gap between photo and text.

   Same markup as a standard card: `.nl-card__media` and `.nl-card__body` are
   simply positioned differently. That is deliberate — the mobile rule at the
   end of this block turns every card back into a standard card, which a
   separate feature template could not do, since the server does not know the
   viewport. Both the teaser and the meta line are always in the DOM and each
   tier shows one of them, for the same reason.

   Which cards land in this tier is decided in Theme11Newsletter::cell(): any
   card wider than one column. A card with no cover is never a feature card —
   the overlay text would have nothing to sit on.

   The whole tier is scoped to `min-width: 621px`, one pixel above the
   single-column breakpoint. Below that every card renders as a standard card:
   white text over a photo is hard to read at phone widths. Written as a
   min-width block rather than as overrides inside the mobile block so there is
   nothing to forget to undo. */

@media (min-width: 621px) {

.nl-card--feature {
	/*
	 * Predictable height whatever the source photo is; the grid may still
	 * stretch it taller, which is fine — the photo is inset:0 and follows.
	 *
	 * 16/9 rather than 4/3. At two columns wide, 4/3 made a 685x514 card whose
	 * overlay stack only ever fills the bottom ~176px — two thirds of the card
	 * was empty photograph, which read as dead space next to the standard cards
	 * beside it. The spec's own "or match whatever the grid row height requires"
	 * is the licence for this; 16/9 puts the text at ~46% of the card, the same
	 * proportion a standard card's body occupies.
	 */
	aspect-ratio: 16 / 9;
	/*
	 * The ratio must only ever drive the HEIGHT. A grid item with an
	 * aspect-ratio and a definite cross size is not stretched in the inline
	 * axis, so .nl-card--featured — whose height comes from spanning two grid
	 * rows — solved the ratio for its width instead and came out 969px wide in a
	 * 685px column, overflowing the grid. Pinning the width to the column span
	 * leaves the ratio as the natural height and nothing else.
	 */
	box-sizing: border-box;
	width: 100%;
	justify-self: stretch;
	/* Already on .nl-card, restated because this tier depends on it: the crop
	   and the rounded corners both come from here. */
	overflow: hidden;
}
/* The hero card spans all four columns. 4/3 at that width is over 1000px tall,
   so it keeps the banner ratio it already had. */
.nl-card--hero.nl-card--feature { aspect-ratio: 21 / 9; }

.nl-card--feature .nl-card__media {
	position: absolute;
	inset: 0;
	aspect-ratio: auto;      /* the card owns the ratio in this tier */
}

/* Bottom-anchored scrim, between photo and text. Full width, bottom ~66% —
   enough for the three-element overlay stack without darkening the whole
   photograph, which is what an 82% scrim was doing. */
.nl-card--feature::before {
	content: '';
	position: absolute;
	inset-inline: 0;
	top: 30%;
	bottom: 0;
	z-index: 1;
	pointer-events: none;
	/* One scrim for every feature card on the page, and it is sized for the
	   whole overlay stack — source line, headline AND excerpt. The earlier ramp
	   was cut for a headline alone and left the excerpt floating on bare
	   photograph; this one starts higher and holds its density longer. Verified
	   against the brightest image in the dataset by qa-feature-contrast.js. */
	background: linear-gradient(to top,
		rgba(0, 0, 0, .55) 0%,
		rgba(0, 0, 0, .42) 38%,
		rgba(0, 0, 0, .18) 68%,
		transparent 100%);
}

/*
 * The second half of the scrim, and the load-bearing one: a gradient on the TEXT
 * BLOCK itself.
 *
 * A percentage scrim on the card cannot know how tall the overlay stack is. Two
 * lines of headline plus two of excerpt cover a different slice of a 385px card
 * than of a 727px one, and when the card ratio changed from 4/3 to 16/9 the
 * headline moved up into the thin part of the ramp and contrast fell to 2.7:1
 * on the brightest photo. Anchoring a gradient to the body makes the darkest
 * part track the text automatically, whatever the card height or the text
 * length. The ::before above now only has to fade it into the photograph.
 */
.nl-card--feature .nl-card__body {
	/* Fades to nothing at the body's OWN top edge. Ending on a non-zero alpha
	   there put a hard horizontal seam across the photo, where the body's
	   background started and the card's gradient behind it was still thin. */
	background: linear-gradient(to top,
		rgba(0, 0, 0, .72) 0%,
		rgba(0, 0, 0, .55) 45%,
		rgba(0, 0, 0, 0) 100%);
}

.nl-card--feature .nl-card__body {
	position: absolute;
	inset-inline: 0;
	bottom: 0;
	z-index: 2;
	padding: 20px 22px;
	/* Bottom-left in LTR, bottom-right in RTL, without a direction rule. */
	align-items: flex-start;
	gap: 8px;
}
.nl-card--feature .nl-card__title {
	color: #fff;
	/* Fallback only — the scrim does the work. If a photo ever defeats it the
	   fix is a deeper scrim, not a heavier shadow. */
	text-shadow: 0 1px 10px rgba(0, 0, 0, .45);
	-webkit-line-clamp: 3;
	line-clamp: 3;
}
.nl-card--feature .nl-card__title a,
.nl-card--feature .nl-card__title a:hover { color: #fff; }
/* Solid fill, not the tinted one — the tint is a pale wash that disappears
   against the scrim. */
.nl-card--feature .nl-card__chip {
	background: var(--nl-gold);
	color: var(--nl-on-gold);
}
/* This tier shows the meta line; the excerpt and the CTA belong to the other. */
.nl-card--feature .nl-card__meta {
	display: block;
	color: rgba(255, 255, 255, .85);
}
.nl-card--feature .nl-card__more { display: none; }
/*
 * No excerpt on the overlay tier. These cards are mostly photograph, and a
 * headline on its own reads better against one than a headline plus two lines
 * of grey-white body copy — which also pushed the scrim far enough up the card
 * to darken the picture it was supposed to be showing.
 *
 * The element stays in the DOM: below 621px these cards render as standard
 * cards, and the excerpt comes back with them.
 */
.nl-card--feature .nl-excerpt { display: none; }

/* Engagement bar over a photograph: white, with the same shadow the headline
   carries so it survives a bright patch drifting under it. */
.nl-card--feature .nl-card__reactions {
	margin-top: 0;
	color: rgba(255, 255, 255, .92);
	text-shadow: 0 1px 6px rgba(0, 0, 0, .5);
}

/* ---- Ambient breathing zoom ----
 *
 * The quiet relative of the banner drift: scale only, no pan. These cards are a
 * third of the banner's width, and at that size a 2% horizontal drift stops
 * reading as movement and starts reading as a rendering glitch. Scale alone is
 * enough, and it cannot expose an edge — the base scale never drops below 1.06.
 *
 * Gated twice, exactly like the banner: `prefers-reduced-motion: no-preference`
 * so the rules do not exist for a reader who asked for less motion, and
 * `.is-animating`, which cards.js sets only while a card is on screen. That
 * matters more here than on the banner, since a page can hold several at once.
 *
 * No light sweep. Repeated across a grid, a travelling glint is noise; it stays
 * exclusive to the banner.
 */
@media (prefers-reduced-motion: no-preference) {
	@keyframes nl-card-breathe {
		from { transform: scale(1.06); }
		to   { transform: scale(1.14); }
	}
	.nl-card--feature.is-animating .nl-card__media img {
		animation: nl-card-breathe 20s ease-in-out infinite alternate;
	}
	/*
	 * Out of phase with each other. A negative delay starts a card partway into
	 * its own cycle, so cards that appear together do not pulse in lockstep —
	 * which looks mechanical. Keyed off the card's position among its siblings,
	 * cycling every four, since no block puts more than four cards in a row.
	 */
	.nl-card:nth-child(4n+2).nl-card--feature .nl-card__media img { animation-delay: -5s; }
	.nl-card:nth-child(4n+3).nl-card--feature .nl-card__media img { animation-delay: -10s; }
	.nl-card:nth-child(4n+4).nl-card--feature .nl-card__media img { animation-delay: -15s; }
	/* Second and later blocks offset again, so a feature card in one section is
	   never in phase with the card at the same position in the next. */
	.nl-block:nth-of-type(even) .nl-card--feature .nl-card__media img { animation-delay: -7s; }
	.nl-block:nth-of-type(even) .nl-card:nth-child(4n+2).nl-card--feature .nl-card__media img { animation-delay: -13s; }
}

}   /* /min-width: 621px — feature tier */

/* =====================================================================
   6bb. 3-up row — one wide feature card, two standard cards
   =====================================================================
   The only thing this layout owns is the grid template. The cards are the same
   two tiers as the card grid, so everything about the feature card — the
   full-bleed photo, the scrim, the bottom-left stack, the mobile fallback to a
   standard card — comes from section 6b and is not restated here.

   No fixed widths: 2fr/1fr/1fr, and align-items:stretch means the two standard
   cards take their height from the feature card beside them. Their bodies are
   flex:1 and the engagement bar is margin-top:auto, so all three bars sit on the
   same line whatever the headlines do above them. */

.nl-three {
	display: grid;
	grid-template-columns: 2fr 1fr 1fr;
	align-items: stretch;
	gap: var(--nl-gap);
}
/* The lead card's height IS the row height. 4/3 at two-thirds width would tower
   over two 16/9 cards, so this tier uses a wider ratio than the card grid's. */
.nl-three .nl-card--lead3 { aspect-ratio: 16 / 9; }

/* Both tiers in this row carry source line + headline + engagement bar and
   nothing else — no excerpt, no CTA. The elements stay in the DOM because the
   same component serves the other sections, which do use them. */
.nl-three .nl-card__more { display: none; }
.nl-three .nl-card__meta { display: block; }


/*
 * One padding for both tiers in this row. Elsewhere the feature body is padded
 * more generously than a standard body (20/22 against 15/17), which is right
 * when the two are not side by side — but here it put the lead card's
 * engagement bar 5px above its neighbours', and those bars are supposed to sit
 * on one line. Equal padding is what makes `margin-top:auto` land them level.
 */
.nl-three .nl-card__body { padding: 18px 20px; }

@media (max-width: 980px) {
	/* Feature across the top, the two standard cards side by side beneath it. */
	.nl-three { grid-template-columns: 1fr 1fr; }
	.nl-three .nl-card--lead3 { grid-column: 1 / -1; aspect-ratio: 21 / 9; }
}
@media (max-width: 620px) {
	/* Single column. The feature tier is already off below 621px, so all three
	   render as standard cards without anything further here. */
	.nl-three { grid-template-columns: 1fr; }
}

/* =====================================================================
   6c. Issue-articles block — alternating hero / list columns
   =====================================================================
   Four equal columns alternating hero, list, hero, list. A hero column is one
   large card: image, then a centred headline in the white area under it. A list
   column is three stacked rows, each a headline beside a small thumbnail,
   separated by hairline rules rather than by gaps.

   Every column ends on the same baseline: the section is a grid with
   align-items:stretch, each column is a flex column, and the list rows are
   flex:1 so they divide the column height evenly — which is what makes the third
   row's bottom edge land on the bottom of the hero cards beside it.

   Not one direction-specific rule in here. The grid's inline axis follows `dir`,
   so hero -> list -> hero -> list mirrors on its own under RTL (see the RTL note
   in section 11), and every offset below is a logical property, so the
   text-beside-thumbnail row flips with it. */

.nl-issue {
	display: grid;
	grid-template-columns: repeat(4, 1fr);
	align-items: stretch;
	gap: var(--nl-gap);
}
.nl-issue__col {
	display: flex;
	flex-direction: column;
	min-width: 0;
}

/* ---- Hero column ---- */

.nl-icard {
	display: flex;
	flex-direction: column;
	flex: 1;                 /* fills the column, so all four columns end level */
	background: var(--nl-surface);
	border: 1px solid var(--nl-border);
	border-radius: var(--nl-radius);
	overflow: hidden;
	box-shadow: var(--nl-shadow);
	transition: box-shadow .2s, transform .2s;
}
.nl-icard:hover { box-shadow: var(--nl-shadow-lg); transform: translateY(-3px); }
/*
 * The hero photo absorbs the column's spare height — `flex: 1 1 auto` with the
 * ratio as its base size, so it starts at 16/9 and grows from there.
 *
 * It used to be `flex: none` at 4/3, which made the hero card the tallest thing
 * in the row and left the list columns beside it stretching two text rows over a
 * height meant for three: ~75px of dead space inside every row. Slack has to go
 * somewhere, and a photograph that crops is a much better place to put it than
 * a gap under a headline. The taller 4/3 base became 16/9 for the same reason —
 * it lowers the floor the whole section has to clear.
 */
.nl-icard__media {
	flex: 1 1 auto;
	aspect-ratio: 16 / 9;
	min-height: 0;
	background: var(--nl-surface-alt);
	overflow: hidden;
}
.nl-icard__media img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
	transform: scale(1);
	/* Same hover treatment as a standard card — these are the same kind of
	   thing, so they should not move differently. */
	transition: transform .5s cubic-bezier(.22, 1, .36, 1);
}
.nl-icard:hover .nl-icard__media img,
.nl-icard:focus-within .nl-icard__media img { transform: scale(1.06); }
.nl-icard__body {
	flex: 0 0 auto;          /* natural height; the photo above takes the slack */
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 8px;
	padding: 16px 18px;
}
.nl-icard__excerpt { text-align: center; }
.nl-icard__title {
	font-family: var(--nl-font-serif);
	font-weight: 600;
	font-size: 1.12rem;
	line-height: 1.32;
	margin: 0;
	text-align: center;
	color: var(--nl-ink);
	display: -webkit-box;
	-webkit-line-clamp: 4;
	line-clamp: 4;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.nl-icard__title a { color: inherit; text-decoration: none; }
.nl-icard__title a:hover { color: var(--nl-brand); }

/* ---- List column ---- */

.nl-issue__col--list {
	background: var(--nl-surface);
	border: 1px solid var(--nl-border);
	border-radius: var(--nl-radius);
	box-shadow: var(--nl-shadow);
	overflow: hidden;
	padding-inline: 16px;
}
.nl-irow {
	flex: 1;                 /* the rows divide the column height evenly */
	display: flex;
	flex-direction: row;     /* logical: mirrors from `dir`, no RTL override */
	align-items: center;
	/*
	 * The ONLY thing separating text from thumbnail. Deliberately not a margin
	 * or padding on either child: `row-reverse` below flips the flex axis but
	 * not a child's own inline axis, so a margin-inline-* would keep its
	 * physical side and the two row variants would end up with different
	 * spacing. `gap` has no side.
	 */
	gap: 14px;
	padding-block: 14px;
	min-width: 0;
}
/* Hairline rule between rows, not a gap. Block-start, so it stays horizontal
   whatever the inline direction — and unaffected by the zigzag, since
   flex-direction cannot move a border. */
.nl-irow + .nl-irow { border-block-start: 1px solid var(--nl-border); }

/*
 * Which side the thumbnail sits on — the same side on every row.
 *
 * Done with flex-direction, never by reordering the DOM: source order stays
 * text-then-thumbnail, so the reading order a screen reader announces does not
 * depend on which side the picture is on.
 *
 * `row-reverse` reverses the flex container's main axis, which under RTL is
 * already reversed, so `start` resolves to the left in English and the right in
 * Persian with no direction-specific rule and no double flip. That is the whole
 * reason this is expressed as a logical side rather than as "left".
 */
[data-thumb-side="start"] .nl-irow { flex-direction: row-reverse; }
[data-thumb-side="end"] .nl-irow { flex-direction: row; }

.nl-irow__text {
	flex: 1 1 auto;
	min-width: 0;            /* lets the clamp work instead of overflowing */
}
.nl-irow__title {
	font-family: var(--nl-font-serif);
	font-weight: 600;
	font-size: .98rem;
	line-height: 1.34;
	margin: 0;
	color: var(--nl-ink);
	/* Fixed line count keeps every row the same height whatever the title
	   length, which is what lets flex:1 divide the column evenly. Cut from four
	   lines to three to make room for the excerpt below it — the excerpt stays
	   whatever else has to give. */
	display: -webkit-box;
	-webkit-line-clamp: 3;
	line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.nl-irow__text > * + * { margin-block-start: 6px; }
.nl-irow__title a { color: inherit; text-decoration: none; }
.nl-irow__title a:hover { color: var(--nl-brand); }

.nl-irow__thumb {
	flex: 0 0 var(--nl-thumb);
	width: var(--nl-thumb);
	aspect-ratio: 4 / 3;
	border-radius: 8px;
	background: var(--nl-surface-alt);
	overflow: hidden;
}
.nl-irow__thumb img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}

/* Empty tab. */
.nl-grid > .nl-empty {
	grid-column: 1 / -1;
	color: var(--nl-muted);
	font-size: .9rem;
	margin: 0;
	padding: 8px 0;
}

/* =====================================================================
   7. Header
   ===================================================================== */

.nl-utility {
	background: var(--nl-brand-dark);
	color: rgba(255, 255, 255, .92);
	font-size: .82rem;
}
.nl-utility__inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	min-height: 40px;
	gap: 16px;
	flex-wrap: wrap;
}
.nl-utility__brand { opacity: .9; }
.nl-usermenu {
	display: flex;
	gap: 16px;
	list-style: none;
	margin: 0;
	padding: 0;
	flex-wrap: wrap;
}
.nl-usermenu li { margin: 0; }
.nl-usermenu a { color: #fff; opacity: .9; text-decoration: none; }
.nl-usermenu a:hover { opacity: 1; text-decoration: underline; }

.nl-masthead {
	background: var(--nl-surface);
	border-bottom: 1px solid var(--nl-border);
}
.nl-masthead__inner {
	display: flex;
	align-items: center;
	gap: 24px;
	padding-block: 22px;
	flex-wrap: wrap;
}
.nl-brand {
	display: flex;
	align-items: center;
	gap: 16px;
	text-decoration: none;
	min-width: 0;
}
.nl-brand__logo { height: 56px; width: auto; max-width: 220px; display: block; }
.nl-brand__text {
	display: flex;
	flex-direction: column;
	line-height: 1.1;
	min-width: 0;
}
.nl-brand__kicker {
	font-size: .72rem;
	font-weight: 700;
	letter-spacing: .12em;
	text-transform: uppercase;
	color: var(--nl-brand);
	margin-bottom: 6px;
}
.nl-brand__title {
	font-family: var(--nl-font-serif);
	font-weight: 700;
	font-size: 1.95rem;
	letter-spacing: -.01em;
	color: var(--nl-ink);
}
.nl-masthead__aside {
	margin-inline-start: auto;
	display: flex;
	flex-direction: column;
	align-items: flex-end;
	gap: 12px;
}
.nl-badges { display: flex; gap: 8px; flex-wrap: wrap; }
.nl-badge {
	font-size: .68rem;
	font-weight: 700;
	letter-spacing: .04em;
	text-transform: uppercase;
	color: var(--nl-brand);
	background: var(--nl-brand-tint);
	border-radius: 999px;
	padding: 4px 11px;
}
.nl-search {
	display: flex;
	align-items: center;
	background: var(--nl-surface-alt);
	border: 1px solid var(--nl-border);
	border-radius: 999px;
	padding-block: 6px;
	padding-inline: 16px 6px;
	min-width: 300px;
}
.nl-search input {
	border: 0;
	background: transparent;
	outline: 0;
	flex: 1;
	font-family: var(--nl-font-sans);
	font-size: .95rem;
	color: var(--nl-ink);
	min-width: 0;
}
.nl-search input::placeholder { color: var(--nl-muted); }
.nl-search button {
	border: 0;
	background: var(--nl-brand);
	color: #fff;
	border-radius: 999px;
	width: 34px;
	height: 34px;
	display: grid;
	place-items: center;
	cursor: pointer;
	flex: 0 0 auto;
}
.nl-search button:hover { background: var(--nl-brand-dark); }

.nl-nav {
	background: var(--nl-surface);
	border-bottom: 2px solid var(--nl-brand);
	position: sticky;
	top: 0;
	z-index: 40;
}
.nl-primary-nav {
	display: flex;
	gap: 2px;
	flex-wrap: wrap;
	list-style: none;
	margin: 0;
	padding: 0;
}
.nl-primary-nav > li { position: relative; }
.nl-primary-nav a {
	display: block;
	padding: 14px 16px;
	font-weight: 600;
	font-size: .95rem;
	color: var(--nl-ink-soft);
	text-decoration: none;
}
.nl-primary-nav a:hover,
.nl-primary-nav a:focus { color: var(--nl-brand); }
/* Inset shadow as the underline, so the current item doesn't shift layout. */
.nl-primary-nav li.current > a,
.nl-primary-nav a[aria-current="page"] {
	color: var(--nl-brand);
	box-shadow: inset 0 -3px 0 var(--nl-brand);
}
/* Dropdown — CSS only, no JS. */
.nl-primary-nav li ul {
	position: absolute;
	inset-inline-start: 0;
	top: 100%;
	min-width: 220px;
	background: var(--nl-surface);
	border: 1px solid var(--nl-border);
	border-radius: 0 0 10px 10px;
	box-shadow: var(--nl-shadow-lg);
	list-style: none;
	margin: 0;
	padding: 6px 0;
	opacity: 0;
	visibility: hidden;
	transform: translateY(6px);
	transition: opacity .15s, visibility .15s, transform .15s;
	z-index: 50;
}
.nl-primary-nav li:hover > ul,
.nl-primary-nav li:focus-within > ul {
	opacity: 1;
	visibility: visible;
	transform: none;
}
.nl-primary-nav li ul a { padding: 9px 18px; font-weight: 500; }
.nl-primary-nav li ul a:hover { background: var(--nl-brand-tint); }

/* =====================================================================
   8. News & Announcements marquee (ping-pong)
   ===================================================================== */

.nl-news { padding: 3rem 0 1rem; }
.nl-news__head {
	text-align: center;
	font-family: var(--nl-font-serif);
	font-size: 1.7rem;
	font-weight: 700;
	color: var(--nl-ink);
	margin: 0 0 1.6rem;
}
/* Soft edge fade so cards don't hard-clip at the viewport edge. */
.nl-news__vp {
	overflow: hidden;
	-webkit-mask-image: linear-gradient(90deg, transparent, #000 3%, #000 97%, transparent);
	mask-image: linear-gradient(90deg, transparent, #000 3%, #000 97%, transparent);
}
.nl-news__track { display: flex; width: max-content; will-change: transform; }
.nl-news__card {
	flex: 0 0 300px;
	width: 300px;
	margin-inline-end: 24px;
	background: var(--nl-surface);
	border: 1px solid var(--nl-border);
	border-radius: var(--nl-radius);
	overflow: hidden;
	box-shadow: var(--nl-shadow);
}
.nl-news__img {
	display: block;
	/* Same ratio-first rule as the card grid, so no hardcoded height here
	   either. At the 300px card width this lands within 11px of the 158px it
	   replaced. */
	aspect-ratio: 16 / 9;
	background-size: cover;
	background-position: center;
	background-color: var(--nl-surface-alt);
}
.nl-news__body {
	border-inline-start: 3px solid var(--nl-gold);
	padding: .85rem 1.3rem 1.1rem;
}
.nl-news__title {
	font-family: var(--nl-font-serif);
	font-size: 1.05rem;
	line-height: 1.3;
	margin: 0 0 .4rem;
	color: var(--nl-ink);
}
.nl-news__title a { color: var(--nl-ink); text-decoration: none; }
.nl-news__title a:hover { color: var(--nl-brand); }
.nl-news__date { font-size: .72rem; color: var(--nl-muted); margin-bottom: .5rem; }
.nl-news__desc {
	font-size: .88rem;
	color: var(--nl-ink-soft);
	line-height: 1.5;
	margin: 0 0 .65rem;
	display: -webkit-box;
	-webkit-line-clamp: 3;
	line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}
.nl-news__more { font-size: .82rem; font-weight: 700; color: var(--nl-brand); text-decoration: none; }

/* =====================================================================
   9. Footer
   ===================================================================== */

.nl-footer {
	background: var(--nl-brand-dark);
	color: var(--nl-on-brand);
	margin-top: 64px;
	padding-block: 46px 0;
	font-size: .9rem;
}
.nl-footer__grid {
	display: grid;
	grid-template-columns: 2fr 1fr 1fr 1fr;
	gap: 32px;
}
/* Selector carries .nl-footer so it outranks the `.nl-footer a` rule below —
   the wordmark is itself an <a>, and would otherwise pick up the muted link
   colour meant for the link columns. */
.nl-footer .nl-footer__name {
	font-family: var(--nl-font-serif);
	font-weight: 700;
	font-size: 1.4rem;
	color: #fff;
	opacity: 1;
	text-decoration: none;
	display: inline-block;
	margin-bottom: 10px;
}
.nl-footer__meta { margin: 4px 0; opacity: .85; }
.nl-footer__col h4 { color: #fff; font-size: .95rem; margin: 0 0 12px; }
.nl-footer__col ul {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 8px;
}
.nl-footer a { color: var(--nl-on-brand); opacity: .85; text-decoration: none; }
.nl-footer a:hover { opacity: 1; color: #fff; text-decoration: underline; }
.nl-footer__bottom {
	border-top: 1px solid rgba(255, 255, 255, .15);
	margin-top: 34px;
	padding-block: 18px 22px;
	display: flex;
	justify-content: space-between;
	gap: 16px;
	flex-wrap: wrap;
	font-size: .82rem;
	opacity: .85;
}

/* =====================================================================
   10. Responsive
   ===================================================================== */

@media (max-width: 1200px) {
	.nl-shell { padding-inline: 18px; }
}

/* Two columns. Every span class collapses so the no-holes guarantee holds. */
@media (max-width: 980px) {
	.nl-grid { grid-template-columns: repeat(2, 1fr); }
	.nl-rail__track { --nl-rail-cols: 2; }
	.nl-card--side-left,
	.nl-card--side-right,
	.nl-card--merged,
	.nl-card--merged-left,
	.nl-card--merged-right { grid-column: 1 / -1; }
	.nl-card--featured { grid-row: auto; }
	.nl-card--featured .nl-card__title { font-size: 1.35rem; }
	.nl-card--hero .nl-card__title { font-size: 1.5rem; }
}

/*
 * The issue block drops to two columns far earlier than the card grid does, and
 * needs its own breakpoint for it. Its list rows carry a fixed ~150px thumbnail
 * beside the headline, so a quarter-width column stops working long before a
 * quarter-width card does: at 1100px four columns leave about 53px for the
 * headline text. Two columns: hero + list, then hero + list wrapping underneath,
 * which stay paired because the source order is hero, list, hero, list.
 */
@media (max-width: 1279px) {
	.nl-issue { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 860px) {
	.nl-nav { position: static; }
	.nl-primary-nav {
		flex-wrap: nowrap;
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
	}
	.nl-primary-nav > li { flex: 0 0 auto; }
	.nl-primary-nav a { white-space: nowrap; }
	/*
	 * The reference flattens submenus into static indented lists at this
	 * breakpoint. That works for its four-item menu; a real journal menu here
	 * carries six parents with up to nine children each, and flattening turned
	 * the mobile header into a clipped, screen-high wall of links before any
	 * content appeared.
	 *
	 * Top-level items stay in the horizontal scroller and submenus are hidden.
	 * Nothing becomes unreachable: every parent links to a section page that
	 * lists its children, and this is also closer to how the reference
	 * actually *looks* on a phone.
	 */
	.nl-primary-nav li ul { display: none; }
	.nl-masthead__aside { margin-inline-start: 0; align-items: stretch; width: 100%; }
	.nl-search { min-width: 0; width: 100%; }
	.nl-brand__title { font-size: 1.6rem; }
	.nl-footer__grid { grid-template-columns: 1fr 1fr; }
	.nl-block { margin-block: 40px; }
}

/* Single column. */
@media (max-width: 620px) {
	.nl-hero { aspect-ratio: 4 / 3; }
	.nl-hero__title { font-size: 1.3rem; }
	.nl-hero__caption { padding: 20px 22px; }
	/* Lift the prev/next buttons clear of the caption — at this aspect ratio a
	   vertically centred button lands on top of the gold eyebrow. */
	.nl-hero__btn { top: 34%; width: 38px; height: 38px; }
	.nl-hero__dots { bottom: 12px; inset-inline-end: 16px; }
	.nl-grid { grid-template-columns: 1fr; }
	.nl-card--side-left,
	.nl-card--side-right,
	.nl-card--merged,
	.nl-card--merged-left,
	.nl-card--merged-right,
	.nl-card--hero { grid-column: 1 / -1; }
	.nl-card--hero .nl-card__media { aspect-ratio: 16 / 9; }

	/* Single column. Hero cards are unchanged; list rows keep the
	   text-beside-thumbnail shape and just shrink the thumbnail. */
	:root { --nl-thumb: 90px; }
	/* One card at a time on a phone; four would be thumbnails. */
	.nl-rail__track { --nl-rail-cols: 1; }
	.nl-issue { grid-template-columns: 1fr; }
	.nl-irow__title { -webkit-line-clamp: 3; line-clamp: 3; }
	/* Two lines for every variant at phone widths — including the cards that
	   render as feature cards above this breakpoint and standard below it. */
	.nl-excerpt { --nl-excerpt-lines: 2; }

	.nl-block__title { font-size: 1.35rem; }
	.nl-tabs {
		flex-wrap: nowrap;
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
		padding-bottom: 4px;
	}
	.nl-tab { white-space: nowrap; }
}

@media (max-width: 520px) {
	.nl-footer__grid { grid-template-columns: 1fr; }
	.nl-shell { padding-inline: 14px; }
	.nl-news__card { flex-basis: 260px; width: 260px; }
}

/* Respect a reduced-motion preference: kill the marquees, the Ken-Burns zoom
   and the card entrance. The hero still advances, it just cuts instead of
   crossfading. */
@media (prefers-reduced-motion: reduce) {
	.nl-ticker__list { animation: none; }
	/* The hero drift and the light sweep are not switched off here — they are
	   never declared, see the `no-preference` block in section 4. All that is
	   left to kill is the crossfade between slides. */
	.nl-hero__slide { transition: none; }
	.nl-card--featured,
	.nl-card--hero { animation: none; }
	/*
	 * The ambient card zoom is not switched off here — like the hero drift it is
	 * never declared, see the `no-preference` block in section 6b. What is left
	 * is the hover transition, cut to near-instant rather than removed, so the
	 * card still acknowledges the pointer without anything gliding.
	 */
	.nl-card,
	.nl-card__media img,
	.nl-icard,
	.nl-icard__media img { transition-duration: .01ms; }
}

/* =====================================================================
   11. RTL
   =====================================================================
   Two rules, because everything above uses logical properties.

   Note on the grid, which is where the reference's own RTL block is
   misleading. STRUCTURE.md §8 states that grid-column line numbers are not
   logical and mirrors the side classes explicitly. That is not how CSS Grid
   behaves: the grid's inline axis follows the container's `direction`, so
   column line 1 is the inline-START — the RIGHT edge under RTL — and every
   numeric grid-column above therefore mirrors on its own.

   Carrying those overrides across cancels the mirroring rather than creating
   it: a .nl-card--merged-left came out on the visual left in RTL, which is
   exactly backwards. Verified in a headless browser before removing them, and
   re-verified after. tabs.js still mirrors the arrow keys, which genuinely is
   not automatic. */

[dir="rtl"] .nl-home,
[dir="rtl"] .nl-header,
[dir="rtl"] .nl-footer,
[dir="rtl"] .nl-block__title,
[dir="rtl"] .nl-card__title,
[dir="rtl"] .nl-icard__title,
[dir="rtl"] .nl-irow__title,
[dir="rtl"] .nl-hero__title,
[dir="rtl"] .nl-brand__title,
[dir="rtl"] .nl-news__title { font-family: var(--nl-font-ar); }

[dir="rtl"] .nl-ticker__list { animation-direction: reverse; }

/* ----------------------------------------------------------------------------
   The photo as a link.

   Fills its ratio box so the whole image is the target, not just the headline.
   `line-height:0` because an <a> is inline-level by default and its leading
   would otherwise add a sliver of background under the image inside the crop.
   ------------------------------------------------------------------------- */
.nl-media-link {
	display: block;
	width: 100%;
	height: 100%;
	line-height: 0;
	color: inherit;
	text-decoration: none;
}

/* ============================================================================
   OLDER-BROWSER FALLBACKS

   theme11 is built on aspect-ratio, CSS logical properties and flex `gap`, which
   together put the floor at Safari 15 / Chrome 88 / Firefox 89 (2021). Below it
   the page did not degrade, it broke: without aspect-ratio every card image
   collapses to zero height, because the ratio box is what gives `height:100%`
   on the <img> something to resolve against.

   Everything here is either inside `@supports not (...)` or is a physical
   declaration guarded by [dir], so a current engine resolves to exactly what it
   did before this block was added. Verified inert on Chromium; NOT verified on a
   real pre-2021 Safari, which does not exist on the build box.

   Direction matters here in a way it does not for the Arabic-only demo6: theme11
   serves both English and Arabic, so every inline-start/inline-end fallback is
   written twice under [dir] rather than mapped once.

   Not fixed here: Safari 14.0 shipped grid `gap` but not flex `gap`, and
   @supports cannot tell the two apart, so it reports support and any test lies.
   The cost is the gap collapsing to 0 in flex rows on that one version — spacing,
   not layout. A wrong guess would have cost more than the symptom.
   ========================================================================= */

/* ---- 1. keyboard focus without :focus-visible (Safari < 15.4) ------------ */
/*
 * The ring at the top of this file is defined on a list of :focus-visible
 * selectors only. An engine that does not know the selector discards the whole
 * rule, leaving no focus indicator anywhere on the page. The same ring is put
 * back on plain :focus here, then suppressed again wherever the selector IS
 * understood — so modern behaviour is unchanged and old browsers merely show
 * the ring on mouse clicks too, which is the right way round to be wrong.
 */
.nl-home a:focus,
.nl-home button:focus,
.nl-home input:focus,
.nl-home [role="tab"]:focus,
.nl-header a:focus,
.nl-header button:focus,
.nl-header input:focus,
.nl-footer a:focus {
	outline: 3px solid var(--nl-gold);
	outline-offset: 2px;
	border-radius: 6px;
}
@supports selector(:focus-visible) {
	.nl-home a:focus:not(:focus-visible),
	.nl-home button:focus:not(:focus-visible),
	.nl-home input:focus:not(:focus-visible),
	.nl-home [role="tab"]:focus:not(:focus-visible),
	.nl-header a:focus:not(:focus-visible),
	.nl-header button:focus:not(:focus-visible),
	.nl-header input:focus:not(:focus-visible),
	.nl-footer a:focus:not(:focus-visible) { outline: none; }
}

/* ---- 2. aspect-ratio -> percentage-padding ratio box --------------------- */
/*
 * Percentages, not pixels, so the boxes still scale with their column — with one
 * exception. Percentage padding resolves against the CONTAINING BLOCK's width,
 * not the element's own, so it is only correct where the two are equal.
 * .nl-irow__thumb is a fixed 150px inside a much wider row, where padding-top
 * would have measured the row; it derives its height from the same --nl-thumb
 * token instead, and so still follows the 90px mobile override for free.
 */
@supports not (aspect-ratio: 16 / 9) {
	.nl-hero                        { height: 0; padding-top: 42.857%; }
	.nl-card__media                 { height: 0; padding-top: 56.25%; }
	.nl-card--hero .nl-card__media  { padding-top: 42.857%; }
	.nl-card--feature               { height: 0; padding-top: 56.25%; }
	.nl-card--hero.nl-card--feature { padding-top: 42.857%; }
	.nl-three .nl-card--lead3       { height: 0; padding-top: 56.25%; }
	.nl-icard__media                { position: relative; flex: 0 0 auto; height: 0; padding-top: 56.25%; }
	.nl-news__img                   { height: 0; padding-top: 56.25%; }
	.nl-irow__thumb                 { position: relative; height: calc(var(--nl-thumb) * 3 / 4); }

	/* The feature tier puts the ratio on the CARD; its media is already
	   position:absolute inset:0, so it must not take a ratio box of its own. */
	.nl-card--feature .nl-card__media { height: auto; padding-top: 0; }

	/*
	 * height:100% on these resolves against the containing block's PADDING box,
	 * which includes the padding-top above — so absolute positioning is all that
	 * is needed and the existing width/height:100% still do the right thing.
	 * .nl-news__img has no child: it is a background-image box and needs none.
	 */
	.nl-hero__img,
	.nl-card__media img,
	.nl-icard__media img,
	.nl-irow__thumb img { position: absolute; top: 0; left: 0; }

	/*
	 * The media link too. Its height:100% resolves against a content box that
	 * the ratio-box trick has collapsed to zero, so left alone the anchor would
	 * be 0px tall — the image would still be visible (it is absolutely
	 * positioned) but nothing would be clickable.
	 */
	.nl-card__media .nl-media-link,
	.nl-icard__media .nl-media-link,
	.nl-irow__thumb .nl-media-link {
		position: absolute; top: 0; left: 0; width: 100%; height: 100%;
	}

	@media (max-width: 980px) { .nl-three .nl-card--lead3 { padding-top: 42.857%; } }
	@media (max-width: 620px) {
		.nl-hero                       { padding-top: 75%; }      /* 4/3 */
		.nl-card--hero .nl-card__media { padding-top: 56.25%; }
	}
}

/* ---- 3. logical properties (Safari < 14.1) ------------------------------ */
/* Symmetric and block-axis values first: these mean the same thing in both
   directions, so they need no [dir] guard. */
@supports not (margin-inline: 0) {
	.nl-shell                       { margin-left: auto; margin-right: auto;
	                                  padding-left: 24px; padding-right: 24px; }
	.nl-ticker                      { margin-top: 18px; margin-bottom: 18px; }
	.nl-ticker__list                { padding-left: 24px; padding-right: 24px; }
	.nl-hero__caption               { left: 0; right: 0; }
	.nl-block                       { margin-top: 54px; margin-bottom: 54px; }
	.nl-card--feature::before       { left: 0; right: 0; }
	.nl-card--feature .nl-card__body{ left: 0; right: 0; }
	.nl-issue__col--list            { padding-left: 16px; padding-right: 16px; }
	.nl-irow                        { padding-top: 14px; padding-bottom: 14px; }
	.nl-irow + .nl-irow             { border-top: 1px solid var(--nl-border); }
	.nl-irow__text > * + *          { margin-top: 6px; }
	.nl-masthead__inner             { padding-top: 22px; padding-bottom: 22px; }
	.nl-search                      { padding-top: 6px; padding-bottom: 6px; }
	.nl-footer                      { padding-top: 46px; padding-bottom: 0; }
	.nl-footer__bottom              { padding-top: 18px; padding-bottom: 22px; }

	@media (max-width: 1200px) { .nl-shell { padding-left: 18px; padding-right: 18px; } }
	@media (max-width: 860px)  { .nl-block { margin-top: 40px; margin-bottom: 40px; } }
	@media (max-width: 520px)  { .nl-shell { padding-left: 14px; padding-right: 14px; } }
}

/* Direction-dependent values, written out per [dir]. inline-start is left in
   LTR and right in RTL; inline-end is the mirror. */
@supports not (inset-inline-start: 0) {
	html:not([dir="rtl"]) .nl-skip            { left: -9999px; }
	html[dir="rtl"]       .nl-skip            { right: -9999px; }
	html:not([dir="rtl"]) .nl-skip:focus      { left: 0; }
	html[dir="rtl"]       .nl-skip:focus      { right: 0; }

	html:not([dir="rtl"]) .nl-hero__btn--prev { left: 16px; }
	html[dir="rtl"]       .nl-hero__btn--prev { right: 16px; }
	html:not([dir="rtl"]) .nl-hero__btn--next { right: 16px; }
	html[dir="rtl"]       .nl-hero__btn--next { left: 16px; }
	html:not([dir="rtl"]) .nl-hero__dots      { right: 24px; }
	html[dir="rtl"]       .nl-hero__dots      { left: 24px; }

	html:not([dir="rtl"]) .nl-card__badge     { left: 12px; }
	html[dir="rtl"]       .nl-card__badge     { right: 12px; }

	html:not([dir="rtl"]) .nl-masthead__aside { margin-left: auto; }
	html[dir="rtl"]       .nl-masthead__aside { margin-right: auto; }

	html:not([dir="rtl"]) .nl-search          { padding-left: 16px; padding-right: 6px; }
	html[dir="rtl"]       .nl-search          { padding-right: 16px; padding-left: 6px; }

	html:not([dir="rtl"]) .nl-primary-nav li ul { left: 0; }
	html[dir="rtl"]       .nl-primary-nav li ul { right: 0; }

	html:not([dir="rtl"]) .nl-news__card      { margin-right: 24px; }
	html[dir="rtl"]       .nl-news__card      { margin-left: 24px; }

	html:not([dir="rtl"]) .nl-news__body      { border-left: 3px solid var(--nl-gold); }
	html[dir="rtl"]       .nl-news__body      { border-right: 3px solid var(--nl-gold); }

	@media (max-width: 860px) {
		html:not([dir="rtl"]) .nl-masthead__aside { margin-left: 0; }
		html[dir="rtl"]       .nl-masthead__aside { margin-right: 0; }
	}
	@media (max-width: 620px) {
		html:not([dir="rtl"]) .nl-hero__dots { right: 16px; }
		html[dir="rtl"]       .nl-hero__dots { left: 16px; }
	}
}
