/*
 * JMNS Theme — design system.
 *
 * Tokens come from theme.json as --wp--preset--* custom properties. This file
 * styles structure and components only; it never hardcodes a brand hex, so a
 * palette change is a one-file change.
 *
 * Layout uses CSS logical properties throughout (margin-inline, padding-block,
 * border-inline-start) so an Arabic RTL switch mirrors automatically without a
 * separate stylesheet — brief §4.2.
 */

/* ================================================================ tokens == */

/*
 * Spacing presets, pinned to design.md's scale.
 *
 * theme.json declares 4/8/12/16/24/32/40/48/64/96px, DESIGN_SYSTEM.md documents
 * it, and every measurement in this file was authored against it — but WordPress
 * merges its own generated spacingScale (x1.5 from 1.5rem) over the theme's
 * spacingSizes, and on WP 7.0.2 core's values win the collision. Five of the ten
 * steps were silently something else:
 *
 *     slug   declared   actually emitted
 *     20     0.5rem     0.44rem
 *     30     0.75rem    0.67rem
 *     60     2rem       2.25rem
 *     70     2.5rem     3.38rem
 *     80     3rem       5.06rem
 *
 * `spacingScale: { steps: 0 }` is the documented way to disable the generated
 * scale and does not take effect here, so the five are restated as custom
 * properties instead. This file loads after the global stylesheet, so these win
 * on source order without !important.
 *
 * The sizes stay declared in theme.json as well: that is what labels the editor's
 * spacing controls, and dropping them would leave editors choosing from core's
 * numbers instead of the design system's.
 */
:root {
	--wp--preset--spacing--20: 0.5rem;
	--wp--preset--spacing--30: 0.75rem;
	--wp--preset--spacing--60: 2rem;
	--wp--preset--spacing--70: 2.5rem;
	--wp--preset--spacing--80: 3rem;
}

/*
 * Colour channels, for the colours that are only ever used at reduced alpha.
 *
 * A hairline at 15% of a dark green cannot be written against a palette token:
 * `--wp--preset--color--primary` holds a whole colour and there is no way to
 * reach inside it for an alpha. So the base is stored as bare `r g b` channels
 * and composed at the point of use — `rgb(var(--jmns-hairline-rgb) /
 * var(--jmns-a-rule))` — which puts the fifty-odd dividers, hairlines, scrims
 * and shadows in this codebase behind a handful of declarations instead of
 * fifty literals.
 *
 * Channels rather than `color-mix()`. `color-mix(in srgb, var(--token) 15%,
 * transparent)` says the same thing more legibly, but it is a 2023 feature —
 * Chrome 111, Safari 16.2, Firefox 113 — and reaches 92.9% of visitors. A state
 * religious authority's portal does not get to leave 7% of the public with
 * missing borders to save a few characters. `rgb()` with space-separated
 * channels and a slash alpha is CSS Color 4 as well, but shipped in 2018 and is
 * effectively universal.
 *
 * These are deliberately not theme.json palette entries. A palette entry is a
 * whole colour and shows up as a swatch in the editor's colour picker; `38 51
 * 42` is not a colour and theme.json cannot hold it. Even if it could, burying
 * the eight colours an editor is meant to choose from under twenty hairline
 * shades would be a poor trade for one less file to look in.
 *
 * A colour scheme therefore overrides the handful declared below alongside the
 * palette, on `:root`, and every tint and every reversed-out label follows.
 */
:root {
	/*
	 * #26332A, and the ink every shadow on the page is cast in. Not a palette
	 * colour: it is never painted at full strength anywhere.
	 *
	 * Split from the hairline channel below, which held the same value and the
	 * same declaration until a dark scheme made them disagree. A shadow is the
	 * absence of light and stays black on any ground; a hairline is a drawn
	 * edge and has to invert to a light one, or every card, table rule and
	 * divider on the page loses its outline against the surface it sits on. One
	 * token cannot be both, and a light shadow on a dark ground is a glow.
	 */
	--jmns-shade-rgb: 38 51 42;

	/*
	 * #26332A. The same ink as the shade above today, and deliberately its own
	 * declaration: this is what the page's dividers, hairlines, table rules and
	 * card edges are cut from, and under `malam` it inverts to #DFE6E1 while
	 * the shade stays black.
	 */
	--jmns-hairline-rgb: 38 51 42;

	/* #245746 — `primary` in channel form, for the tints that quote the brand green. */
	--jmns-primary-rgb: 36 87 70;

	/* #173D32 — `primary-hover` in channel form, for the slider's caption ramp. */
	--jmns-primary-hover-rgb: 23 61 50;

	/*
	 * #0F1613. Darker and greener than the shade above, because a scrim has to
	 * carry a photograph rather than separate two rows of text.
	 */
	--jmns-scrim-rgb: 15 22 19;

	/*
	 * The ink for anything reversed out of a brand fill — a filled
	 * `interactive` button, the ticker's label, the gazette badge.
	 *
	 * Not `surface`, which is the same white today but means the colour a card
	 * is made of; a scheme is entitled to warm its surfaces without also
	 * warming reversed-out text. Not a palette entry either, for the same
	 * reason as the channels above: the picker already offers one white, and a
	 * second one an editor cannot tell apart is a trap rather than a choice.
	 *
	 * This and `--jmns-on-scrim` below were one token, `--jmns-on-dark`, and
	 * that token was wrong in a way no light scheme could show. It named two
	 * different dark things: a photo scrim, which is dark in every scheme, and
	 * an `interactive` fill, which under `malam` is #8FC0AB — light. Its ink
	 * therefore has to go dark exactly where the scrim's ink stays light, so
	 * the one token would have printed white on mint.
	 */
	--jmns-on-primary: #FFFFFF;

	/*
	 * The ink and the focus ring for anything sitting on the photo scrim — the
	 * lightbox controls, the search overlay, the slider's caption band. A scrim
	 * is dark under every scheme, including `malam`, so this stays light under
	 * every scheme.
	 */
	--jmns-on-scrim: #FFFFFF;

	/*
	 * The ink on the gold fill, which is a third case again and had no token at
	 * all. The utility badge stated `text-primary` on `gold`: 1.68:1 under
	 * `malam` and 3.01:1 under `kontras`, both of them unreadable, because the
	 * gold moves with the scheme and the ink did not follow it. With its own
	 * token the same badge measures 5.56 / 5.65 / 6.98 / 8.80 across the four
	 * schemes that are not `ehsan`, and 5.00 there.
	 */
	--jmns-on-gold: #1F2925;

	/*
	 * The three strengths a hairline is ever drawn at, stated once rather than
	 * as a literal alpha at each of the sites that draws one.
	 *
	 * An alpha typed into a rule is as unreachable by a scheme as a hex is, and
	 * `kontras` is the scheme that proves it: 20% black on white measures
	 * 1.61:1, which is a smudge rather than a boundary for the one visitor who
	 * has asked for the strongest contrast the portal can give. At `1` the same
	 * rule measures 21:1. So the strength is a token as well as the colour.
	 */
	--jmns-a-hair: .10;
	--jmns-a-rule: .15;
	--jmns-a-edge: .20;

	/*
	 * The statistics band, which is the one deep fill in the middle of the
	 * page. It used to compose itself out of `interactive` and `surface`, which
	 * says "a control's colour carrying the colour a card is made of" — two
	 * tokens that happen to be dark and light under every light scheme and are
	 * both light under `malam`. The band would have rendered #1F2A25 on #8FC0AB
	 * inverted, or rather it would have rendered dark ink on a dark ground.
	 *
	 * A band is a surface with its own ink and its own focus ring, so it says
	 * so, and a scheme states all three together or none of them.
	 */
	--jmns-band-bg: #245746;
	--jmns-band-ink: #FFFFFF;
	--jmns-band-ring: #FAF8F2;

	/*
	 * The closing band's own colour system, moved here from `.site-footer`.
	 *
	 * These are seven values that only mean anything on an ink band, which is
	 * why they are not palette entries — in the palette they would appear as
	 * seven more swatches in every colour picker in the editor. But declared on
	 * `.site-footer` they were also unreachable: a scheme writes to `:root`,
	 * and a custom property set on the element itself always beats one
	 * inherited from an ancestor, whatever the specificity says. The footer
	 * would have stayed green under all five schemes.
	 *
	 * `--footer-ring` is new. The band above and the footer shared one focus
	 * ring for as long as both were dark; under `kontras` the band goes white
	 * and the footer goes black, and no single ring colour can be seen on both.
	 */
	--footer-top: #173D38;
	--footer-bottom: #12322F;
	--footer-text: #F2F5F3;
	--footer-muted: #B7C7C0;
	--footer-accent: #B59C68;
	--footer-ring: #FAF8F2;

	/*
	 * The ground the crest is drawn for.
	 *
	 * The uploaded wordmark is one raster: the state crest in its heraldic
	 * colours beside `JABATAN MUFTI NEGERI SELANGOR` set in near-black. On a
	 * light masthead that ink measures 16.19:1 and nothing needs saying. On
	 * `malam` it measures **1.07:1** — the department's own name, invisible, on
	 * every page.
	 *
	 * No token can reach inside a raster, and no filter can help: 45% of the
	 * opaque pixels are that ink and the rest is the crest, so inverting to
	 * rescue the lettering would render the state arms in negative. That is the
	 * same refusal as the gazette scans — a crest in negative is not the crest.
	 *
	 * So the crest keeps its ground instead. Every scheme states the surface its
	 * masthead actually draws, which makes the plate invisible on the four light
	 * schemes by construction rather than by a rule that skips them; `malam`
	 * states a light one, and the crest sits on the ground it was designed for.
	 * Replace this with a light-lettering wordmark from the department and the
	 * plate can go.
	 */
	--jmns-logo-plate: #FFFFFF;

	/*
	 * The two washes the footer draws — a container edge and a rule. Pure white
	 * below full opacity over a ground the rule does not need to know, so they
	 * follow the band's own colour rather than a scheme's, and there is nothing
	 * for a scheme to say about them.
	 */
	--footer-divider: rgb(255 255 255 / 0.10);
	--footer-card: rgb(255 255 255 / 0.05);
}

:root {
	/* Motif opacity. Texture must never compete with text (§4.3). */
	--jmns-motif-opacity: 0.05;
	--jmns-rule: 2px;
	--jmns-focus-width: 3px;

	/*
	 * How much light a gazetted scan is shown at.
	 *
	 * A fatwa is published as a photograph of the gazette, and the paper in it
	 * is white. On the dark scheme a reader turning a page gets a full-width
	 * sheet of white against a #0F1512 ground, which is the glare the dark
	 * scheme was chosen to avoid. `brightness()` takes the sheet down to 85%
	 * there and leaves it alone everywhere else.
	 *
	 * Brightness, and only brightness. The scan is not inverted and its hue is
	 * not touched: it is the record as gazetted, and a reader comparing it with
	 * the paper copy has to be looking at the same document. It is also
	 * deliberately excluded from `@media print`, and it never reaches the
	 * download link, because the file a visitor receives is the file as
	 * gazetted.
	 */
	--jmns-page-dim: 1;

	/*
	 * The well the flipbook draws its pages on in fullscreen — a dark room, so
	 * the white edges of a scan read as page edges rather than bleeding into the
	 * chrome. It was `#1b2620` typed into flipbook.css and carried on the colour
	 * allowlist; it is a token now so nothing on the site states a colour a
	 * scheme cannot see.
	 *
	 * It is deliberately not in the scheme registry: a scheme that named it
	 * would have to name it in all five, and the measured palettes carry no
	 * value for it. A reading well is dark in every scheme anyway — that is what
	 * it is for — so holding one value here is the honest state until somebody
	 * measures four more.
	 */
	--jmns-reader-well: #1b2620;

	/*
	 * design.md v1.0 — radius, elevation and motion. These were absent from
	 * the theme entirely, which is why every surface met at a hard 90° corner
	 * and nothing on the page had any sense of depth.
	 */
	--r-card: 16px;
	--r-button: 12px;
	--r-input: 12px;
	--r-modal: 20px;
	--r-pill: 999px;

	--shadow-card: 0 8px 24px rgb(var(--jmns-shade-rgb) / 0.05);
	--shadow-hover: 0 12px 32px rgb(var(--jmns-shade-rgb) / 0.08);

	--t-fast: 150ms;
	--t-base: 200ms;
	--ease: cubic-bezier(0.2, 0, 0.2, 1);
}

/* ============================================================== baseline == */

/*
 * Border-box everywhere. Without it a 100%-wide element with a 1px border is
 * 2px wider than its container, which is exactly the residual overflow the
 * measurement showed after the ticker was fixed.
 */
*,
*::before,
*::after {
	box-sizing: border-box;
}

/*
 * The page descends rather than bookending its colour.
 *
 * Green used to open the page — a dark utility bar over a mid-green prayer bar —
 * and then vanish until the footer. It now arrives with depth: white chrome at
 * the top, the warm page ground through the middle, and the sage tint by the
 * last band, with the ink footer as the hard edge that makes the descent land.
 *
 * Ink stays legible at every stop, which is what makes a gradient safe here at
 * all: 13.21 on white, 12.55 on the ground, 11.62 on the tint. text-secondary
 * holds too, at 5.46 / 5.19 / 4.80. White text could not — it fails the light
 * end — so nothing over this gradient is ever reversed out.
 *
 * The gradient spans the document, not the viewport — scroll attachment, not
 * fixed. Fixed would anchor it to the screen, so every scroll position would
 * show white at the top of the viewport and tint at the bottom of it, which is
 * a vignette rather than a descent. Anchored to the document, the ground is
 * genuinely greener the further down the visitor gets.
 */
body {
	-webkit-font-smoothing: antialiased;
	text-wrap: pretty;
	background-color: var(--wp--preset--color--surface);
	background-image: linear-gradient(
		180deg,
		var(--wp--preset--color--surface) 0%,
		var(--wp--preset--color--background) 38%,
		var(--wp--preset--color--surface-tint) 100%
	);
	background-repeat: no-repeat;
}

/*
 * Replaced media can otherwise report its intrinsic width and widen the whole
 * document. The uploaded crest alone is 1925px.
 */
img,
svg,
video,
canvas,
iframe {
	max-inline-size: 100%;
	block-size: auto;
}

/*
 * Backstop. Individual overflows are fixed at their source above; this stops
 * any future one from turning into a horizontally-scrolling page on a phone.
 * `clip` rather than `hidden` so it does not create a scroll container and
 * break position:sticky on the header.
 */
html {
	overflow-x: clip;
}

/*
 * The [hidden] attribute is only `display: none` in the UA stylesheet, so any
 * class selector that sets display beats it. `.primary-navigation ul` did
 * exactly that and left every nav dropdown permanently open — the markup and
 * the JS were both correct, and the CSS quietly overruled them.
 *
 * !important is deliberate here. This has to win against any component rule,
 * present or future, or the bug returns the next time someone sets display on
 * a container whose children can be hidden.
 */
[hidden] {
	display: none !important;
}

/*
 * Long rulings need a comfortable measure; 42rem ≈ 68 characters at 16px —
 * and it sits at the left edge of the band, not in the middle of it.
 *
 * That second half was written in the plugin's taudhih stylesheet, which only
 * loads where Taudhih markup does. So a ruling's prose began where its heading
 * began and the same paragraph on an ordinary page floated 264px in, with the
 * title above it still at the edge. Whether the article had one left edge or
 * three depended on which stylesheet happened to be on the page.
 *
 * The reasoning is that stylesheet's and holds: a column of text floating in
 * the middle of a wider block reads as a mistake even when the line length is
 * right. It belongs here, where `.entry-content` is laid out, and applies to
 * everything the band holds rather than to a list of six element names that
 * the related block and the table wrapper were never added to.
 *
 * The wide components below are the full width of the band, so `0` and `auto`
 * put them in the same place.
 */
.entry-content > * {
	max-inline-size: var(--wp--style--global--content-size);
	margin-inline: 0;
}

.entry-content > .alignwide {
	max-inline-size: var(--wp--style--global--wide-size);
}

/*
 * A berita fills the band it is given.
 *
 * The measure and the shell disagreed on this one post type, and the
 * disagreement was visible: the h1 ran the full 1200px, every paragraph under it
 * stopped at 672px, and because the rule above pins the measure flush-left
 * rather than centring it, all 648px of the difference fell on the right. The
 * reader met a headline the width of the page and a body half of it, beside a
 * column of white space running the length of the article.
 *
 * News is also the one thing here that arrives with its own furniture —
 * galleries, tables, the occasional embed — and those were already opting up to
 * the band while the prose between them stayed behind, so the article stepped in
 * and out at every block. Releasing the cap gives the whole thing one left edge
 * and one right edge.
 *
 * The cost, stated rather than buried: at 1440 a line runs about 148 characters,
 * past the 45–85 a measure exists to hold and past the 80 of WCAG 1.4.8. That is
 * the department's call to make, and this block is where to unmake it — delete
 * it and the measure returns everywhere.
 *
 * Scoped to the post type rather than to `.entry-content`, so fatwa, kenyataan,
 * penerbitan and every ordinary page keep reading at 42rem.
 */
.single-jmns_berita .entry-content > * {
	max-inline-size: none;
}

/*
 * Replaced media is the exception, and has to be said again here.
 *
 * `img { max-inline-size: 100% }` up at the top of this file is a type selector,
 * so the rule above outranks it and an image dropped straight into the content
 * went back to reporting its intrinsic width: two berita carry a bare 1024px
 * `<img>` and pushed a 390px screen 658px sideways.
 *
 * Stated as five type selectors rather than a `:where()` list so it wins on
 * specificity rather than on being written second — a later edit that moves
 * these blocks around should not be able to bring the overflow back.
 */
.single-jmns_berita .entry-content > img,
.single-jmns_berita .entry-content > svg,
.single-jmns_berita .entry-content > video,
.single-jmns_berita .entry-content > canvas,
.single-jmns_berita .entry-content > iframe {
	max-inline-size: 100%;
}

/*
 * Headings need room above more than below to group with their own section.
 *
 * In :where(), per ADR-052. This is the rhythm of prose, and it was reaching
 * headings inside components dropped into the content — the staff directory's
 * officer names carry `margin-block: 0`, lost to this at element-plus-class, and
 * sat 32px below the top of their own card while the photograph beside them
 * started at zero. A component that states its own spacing should win by saying
 * so; a bare `h2` in an article still gets this.
 */
:where( .entry-content h2, .entry-content h3 ) {
	margin-block-start: var(--wp--preset--spacing--60);
	margin-block-end: var(--wp--preset--spacing--30);
}

/* ============================================================ a11y core == */

.skip-link {
	position: absolute;
	inset-inline-start: -9999px;
	z-index: 999;
}

.skip-link:focus {
	inset-inline-start: var(--wp--preset--spacing--30);
	inset-block-start: var(--wp--preset--spacing--20);
	padding: 0.75rem 1rem;
	background: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--interactive);
	border: var(--jmns-rule) solid var(--wp--preset--color--interactive);
	font-weight: 600;
}

/*
 * The skip link targets <main>, which is not focusable by default — the
 * viewport jumps but the focus ring stays in the header, so the next Tab
 * returns to the navigation the visitor just skipped (SC 2.4.1 / 2.4.3).
 * tabindex="-1" makes it focusable; this suppresses the ring, because the
 * landmark itself is not an interactive control and outlining the whole page
 * body is noise.
 */
main#primary:focus {
	outline: none;
}

.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.screen-reader-text:focus {
	width: auto;
	height: auto;
	clip-path: none;
}

/*
 * Focus is never removed, only restyled. `interactive` measures 7.83:1 on the
 * page background and 8.31:1 on a card — comfortably past the 3:1 that WCAG
 * 2.2 SC 1.4.11 asks of a non-text indicator.
 *
 * One rule covers the whole site, because every surface in this palette is
 * light. The dark-band override the previous palettes needed is gone.
 */
:focus-visible {
	outline: var(--jmns-focus-width) solid var(--wp--preset--color--interactive);
	outline-offset: 2px;
}

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

/*
 * Surfaces are named by temperature, not by value. The palette has no dark
 * band to contrast against a light one, so the page is structured the way the
 * source image is: warm at the top, cool at the foot, everything within one
 * narrow lightness range.
 */
.surface-cool {
	background-color: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--text-primary);
}

/*
 * Girih motif. Applied via mask-image so one SVG tints to any palette colour,
 * and only ever on decorative bands — never behind body copy (§4.3).
 */
.has-motif {
	position: relative;
	isolation: isolate;
}

.has-motif::before {
	content: "";
	position: absolute;
	inset: 0;
	z-index: -1;
	background-color: currentColor;
	opacity: var(--jmns-motif-opacity);
	-webkit-mask-image: url("../img/girih.svg");
	mask-image: url("../img/girih.svg");
	-webkit-mask-repeat: repeat;
	mask-repeat: repeat;
	-webkit-mask-size: 120px 120px;
	mask-size: 120px 120px;
	pointer-events: none;
}


/* ============================================================ page shell == */

/*
 * Every top-level band on the home page shares one container and one vertical
 * rhythm. Without this the sections sat flush against the viewport edge and
 * against each other, which is most of what made the page look unaligned.
 */
.site-main > * {
	--shell: min(100% - 2 * var(--wp--preset--spacing--50), var(--wp--style--global--wide-size));
}

/*
 * Room at both ends of an inner page.
 *
 * Inner templates put content straight into <main> with no space around it, so
 * a fatwa's final paragraph, an archive's last card and the staff directory all
 * ended flush against the footer's top edge — measured at exactly 0px on
 * /fatwa/ and /direktori-pegawai/, and 16px on a page only because whatever
 * happened to be last carried its own margin.
 *
 * The top had the same defect and it did not show until the breadcrumb arrived.
 * The heading used to stand 47px clear of the prayer bar on its own margin; the
 * trail above it has none, so it sat at exactly 0 from the bar's bottom edge —
 * a row of small capitals hard against a dense strip of prayer times. Equal
 * padding at both ends, so the page has the same air below the bar as it has
 * above the footer.
 *
 * The two ends are not equal, because what they are separating is not equal.
 * Above is the prayer bar, a dense strip of the same ground; 48px clears it.
 * Below is the footer, which is a hard edge into deep green — the page does not
 * end so much as stop — and 48px there arrived too fast, most of all on a short
 * page like the vision, where the whole document is barely two screens. 80px at
 * the narrow end, 96 by 1440.
 *
 * The floor is 5rem and not 3 so the value meets the phone rule below without a
 * step: under 48rem the launcher reserves exactly 5rem at the end of <main>, and
 * a clamp that fell to 3rem would have taken 80px back down to 72 at the first
 * pixel past the breakpoint.
 *
 * Excluded on the front page deliberately: its last band is the deep green
 * statistics strip, and the colour descent depends on that meeting the ink
 * footer with nothing between them (see the .site-footer gradient).
 */
body:not(.home) .site-main {
	padding-block: var(--wp--preset--spacing--80) clamp(5rem, 2.75rem + 3.6vw, 6rem);
}

/*
 * Templates other than the front page put their content straight into <main>,
 * and nothing was applying the shell to it — so a ruling's title, its meta and
 * its body all ran the full 1281px while the footer below them sat in the
 * 1200px shell. On a phone that is text touching the screen edge.
 *
 * Named rather than applied to `.site-main > *`, because the homepage's bands
 * must stay full-bleed: their background colours are the band, and constraining
 * them would stop the sage and the ink at 1200px with white either side.
 *
 * `article` rather than `.entry`: post_class() emits post-1965, page,
 * type-page, status-publish and hentry — there is no `entry` class to hook, and
 * assuming one is why the first attempt at this fixed the archives and left
 * every single ruling still running edge to edge.
 *
 * The cost of naming them is that a new one starts full-bleed and nobody
 * notices until it is on screen. `.search-facets` did exactly that: on a 1920px
 * display the type filter began at the left edge while the heading above it and
 * the results below it both sat in the 1200px shell. It is the only child of
 * `.site-main` on any search page that was missing — checked against all of
 * them, not just the one that was reported.
 */
.site-main > article,
.site-main > .page-header,
.site-main > .entry-card,
.site-main > .fatwa-finder,
.site-main > .fatwa-list,
.site-main > .tender-list,
.site-main > .fatwa-more,
.site-main > .jmns-penerbitan,
.site-main > .no-results,
.site-main > .search-facets,
.site-main > .pagination {
	inline-size: var(--shell, min(100% - 3rem, 75rem));
	margin-inline: auto;
}

/*
 * The poster on a post that has nothing else.
 *
 * Only printed where the stored content is empty — see content-single.php —
 * which on this site means a publication whose graphic is the publication.
 *
 * Held to the reading measure and to the page's left edge rather than run to
 * the full shell: the originals are around 800px square, so a 1200px frame
 * upscales them by half and turns a poster into a hero on a page whose only
 * other element is its title.
 */
.entry-thumbnail {
	max-inline-size: 42rem;
	margin: 0 0 var(--wp--preset--spacing--50);
}

.entry-thumbnail img {
	display: block;
	inline-size: 100%;
	block-size: auto;
	border-radius: var(--r-card);
}

/* ---------------------------------------------------------- breadcrumb --- */

/*
 * Where the visitor is, above the page title.
 *
 * Set in the portal's own idiom for small text rather than as a row of links:
 * the section labels on the vision page, the field labels on a ruling and the
 * column heads in the footer are all uppercase, letterspaced and green, and a
 * trail of underlined links above the title read as something a plugin had
 * added. Uppercase at 12px it reads as an eyebrow — part of the masthead of the
 * page, which is what it is.
 *
 * The last crumb is exempt. It is the page's own title, the h1 beneath repeats
 * it in sentence case, and a news report's title set in capitals across two
 * lines would shout the same words twice.
 *
 * It lives inside the header rather than above it, so it takes the shell those
 * headers already sit in and there is one thing keeping the trail, the title
 * and the body on the same left edge instead of three.
 */
.jmns-crumbs {
	margin-block-end: var(--wp--preset--spacing--30);
}

.jmns-crumbs__list {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	/* The chevron needs the same air on both sides, and the space before it falls
	   between two list items rather than inside either one. Wide enough that the
	   focus ring on a crumb — 3px at 2px offset — clears the mark beside it. */
	gap: 0.25rem 0.9ch;
	margin: 0;
	padding: 0;
	list-style: none;
	color: var(--wp--preset--color--text-secondary);
	font-size: 0.75rem;
	line-height: 1.5;
}

.jmns-crumbs__item {
	display: flex;
	align-items: baseline;
	gap: 0.9ch;
	/* Lets the last crumb shrink rather than widen the row past the shell. */
	min-inline-size: 0;
}

/*
 * Everything but the page's own name: uppercase, spaced, and weighted enough to
 * hold at 12px. Letterspacing is what makes small capitals legible; without it
 * this size in capitals is a smear.
 */
.jmns-crumbs__link,
.jmns-crumbs__section {
	font-weight: 600;
	letter-spacing: 0.07em;
	text-transform: uppercase;
}

.jmns-crumbs__sep {
	/* A direction, not a division — the trail goes one way. Lighter than either
	   side of it, because it is punctuation and not a step. */
	opacity: 0.45;
}

/*
 * Green, and underlined at a third of it. A trail is a row of words in which
 * some lead somewhere and some are section labels that lead nowhere, and colour
 * is the one thing that must not be carrying that difference alone — but a solid
 * underline under every link at this size put four heavy rules above the title.
 * The hairline says "link" on inspection and disappears at a glance; hover and
 * focus bring it up to full.
 */
.jmns-crumbs__link {
	color: var(--wp--preset--color--interactive);
	text-decoration: underline;
	text-decoration-color: rgb(var(--jmns-primary-rgb) / 0.35);
	text-decoration-thickness: 1px;
	text-underline-offset: 0.3em;
	/*
	 * 18px of 12px type is a 49x18 target, under the 24x24 that SC 2.5.8 asks
	 * for. The padding is vertical only and the trail takes it back with a
	 * negative margin, so the link grows to 30px tall and nothing moves.
	 *
	 * Not 44: the trail is furniture above the heading, and a 44px-tall row of
	 * small capitals would take more of the masthead than the title it
	 * introduces. 24 is the requirement; 44 is the aspiration, and here the two
	 * genuinely conflict.
	 *
	 * The negative margin is what keeps this free. Padding alone grew the trail
	 * to 30px and pushed the heading down with it — trimming the trail's own
	 * bottom margin did not give it back, because that margin was already being
	 * collapsed into the heading's larger one and was buying nothing. Cancelling
	 * the padding on the link instead leaves a 30px target inside an 18px row.
	 */
	padding-block: 0.375rem;
	margin-block: -0.375rem;
}

.jmns-crumbs__link:hover,
.jmns-crumbs__link:focus-visible {
	color: var(--wp--preset--color--interactive-hover);
	text-decoration-color: currentColor;
}

/*
 * The page's own title, in the case the department wrote it. A report's title
 * runs long: at 360px "Karnival Falak Peringkat Negeri Selangor 2025" is three
 * lines of trail above a heading that says the same words again. One line, and
 * the h1 directly beneath carries it in full.
 */
.jmns-crumbs__here {
	overflow: hidden;
	white-space: nowrap;
	text-overflow: ellipsis;
}

/* Section rhythm. Losing this padding is what made the bands collapse into
   one another. `.home-latest` was listed twice in this selector, once above the
   comment and once below it.

   4rem down to 3rem — a quarter off, the top of the range the review asked for.
   Five bands at 64px top and bottom spent 640px of the page on gaps between
   things; at 48px the bands still read as separate, which is what this padding
   is for, and the homepage gives back about 160px. Below that the alternating
   grounds start doing the separating on their own and the rhythm goes. */
.home-section,
.jmns-quick-links,
.home-latest {
	padding-block: var(--wp--preset--spacing--80);
}

.jmns-quick-links {
	background-color: var(--wp--preset--color--surface);
}

.home-section > *,
.home-latest > *,
.jmns-quick-links > * {
	inline-size: var(--shell, min(100% - 3rem, 75rem));
	margin-inline: auto;
}

/*
 * Section rhythm, per the client brief §19: warm ivory, white, soft sage,
 * white, warm ivory, very light neutral, deep green, dark green — a temperature
 * progression down the page rather than one white block after another.
 *
 * These steps are tiny by luminance and meant to be. Every one of these grounds
 * sits between 1.02 and 1.15 against its neighbours, so what actually separates
 * them is **hue**, not value: sage is green-tinted, ivory is warm, the neutral
 * is neither. Trying to make the difference visible as a value step would mean
 * grounds dark enough to fight the content, which is the opposite of the brief.
 *
 * Fatwa and Dari Meja Mufti stay unfilled, because the body's own descent
 * gradient shows through there — the bare ground is already the warm-ivory and
 * greening end of that descent, so filling them would flatten it.
 */
/*
 * Grounds reassigned when Taudhih moved to second on 12 August.
 *
 * The order is now fatwa, taudhih, berita, meja. Leaving the old assignment
 * put sage against the light neutral — 1.02 apart, so two sections read as one
 * continuous band and §19's progression disappeared exactly where the page is
 * densest. Taudhih takes white and Berita takes the neutral, which restores
 * the alternation: white, sage, white, neutral, ivory, green.
 */
.home-section--taudhih,
.home-latest {
	background-color: var(--wp--preset--color--surface);
}

.home-section--berita {
	background-color: var(--wp--preset--color--surface-muted);
}

/* §19: soft sage, and the one green-tinted band above the statistics. */
.home-section--fatwa {
	background-color: var(--wp--preset--color--surface-tint);
}

/*
 * §17 names this band's ground explicitly (#FAF8F2, `background`). It was
 * already showing close to that, because the body's descent gradient happens
 * to be around ivory by this point in the page — but "happens to be" is not a
 * specification, and the gradient moves whenever a section above it changes
 * height. Stated, so it stays what the brief asked for.
 */
.home-section--artikel {
	background-color: var(--wp--preset--color--background);
}

.home-section__title,
.home-latest > h2,
.jmns-quick-links__heading {
	margin-block: 0 var(--wp--preset--spacing--60);
	font-family: var(--wp--preset--font-family--heading);
	/*
	 * 28-32px. The V2 review put a section heading at 28-34 and `title-lg` runs
	 * exactly that; the final review's §23 narrows it to 28-32, so the top end
	 * is capped. Everything below 32 is the preset's own fluid step, unchanged.
	 */
	font-size: min( var(--wp--preset--font-size--title-lg), 2rem );
	font-weight: 700;
	line-height: 1.25;
	/*
	 * Left, per brief §3: "avoid making every section centre-aligned".
	 *
	 * Six identical centred headings down a page is most of what makes it read
	 * as a template — a centred title has no relationship to the grid beneath
	 * it, so every section announces itself the same way and the eye gets no
	 * edge to travel down. The statistics band keeps its centre, because that
	 * one genuinely is a display panel rather than an editorial section.
	 */
	text-align: start;
	color: var(--wp--preset--color--text-primary);
}

.home-stats__title {
	text-align: center;
}

/*
 * Three columns on desktop, one on a phone. auto-fit rather than a fixed
 * repeat(3): the row holds three cards today, and a section that later renders
 * four should not need a second grid declaration.
 */
.home-section__grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
	gap: var(--wp--preset--spacing--50);
	/*
	 * margin-block only. A flat `margin: 0` here reset the margin-inline:auto
	 * that .home-section > * uses to centre the shell, so every card grid on the
	 * page sat at the band's left edge — L=0 against L=41 for the heading above
	 * it. Headings looked centred and their cards looked shoved left, because
	 * they were.
	 */
	margin-block: 0;
	padding: 0;
	list-style: none;
}

/* The card is the grid item's whole height, so a longer title in one column
   does not leave its neighbours floating at different depths. */
.home-section__item {
	display: flex;
}

.home-section__item > * {
	flex: 1 1 auto;
}

/*
 * §25: an editorial text link, not a centred outline button.
 *
 * Four identical pill buttons down the page were, in the review's words,
 * "slightly generic" — they read as four calls to action when they are really
 * four ways of saying "there is more of this". Aligned to the end of the
 * section instead, which puts them where the eye finishes rather than in the
 * middle of the next gap.
 */
.home-section__more {
	margin-block: var(--wp--preset--spacing--50) 0;
	text-align: end;
}

.home-section__more a {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	/* 44px of target without looking like a button. */
	min-block-size: 44px;
	border: 0;
	padding: 0;
	background: none;
	color: var(--wp--preset--color--interactive);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	text-decoration: none;
}

.home-section__more a:hover {
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

/*
 * V3 §23: the link sits level with the heading on desktop and stays below the
 * content on a phone — and it stays at the end of the section in the markup
 * either way.
 *
 * That last part is why it is lifted rather than moved. Read in source order,
 * "Lihat semua fatwa" belongs after the four rulings it offers more of;
 * hoisting it into the heading block would put it before them for a screen
 * reader, and rendering it twice would put one destination in the tab order
 * twice.
 *
 * Taken out of flow rather than placed in a grid. Making the section a grid was
 * the first attempt and it collapsed the sections: every child carries
 * `inline-size: min(100% - 3rem, 75rem)`, and inside a grid column that
 * percentage resolves against a track the content is also sizing — circular, so
 * Berita's lead came out 0px wide and 4175px tall with its excerpt wrapped one
 * character per line. Absolute positioning changes nothing about how the
 * remaining children are laid out.
 *
 * The paragraph keeps the shell width and the auto margins it already had, so
 * with both insets zeroed it centres on the shell and its end-aligned text
 * lands on the shell's right edge. The top offset is the section's own padding,
 * which is where the heading starts.
 *
 * Its box therefore covers the heading, which is what `pointer-events` is for:
 * the paragraph passes clicks through and only the anchor takes them.
 */
@media (min-width: 48rem) {
	.home-section:has( > .home-section__more ) {
		position: relative;
	}

	.home-section > .home-section__more {
		position: absolute;
		inset-block-start: var(--wp--preset--spacing--80);
		inset-inline: 0;
		margin-block: 0;
		pointer-events: none;
	}

	.home-section > .home-section__more a {
		pointer-events: auto;
	}
}

.home-section__more-arrow {
	transition: transform var(--t-fast) var(--ease);
}

.home-section__more a:hover .home-section__more-arrow {
	transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
	.home-section__more a:hover .home-section__more-arrow {
		transform: none;
	}
}

/* On a phone the link sits under the content, centred with it. */
@media (max-width: 47.999rem) {
	.home-section__more {
		text-align: center;
	}
}

/* ------------------------------------------------------------- news --- */

/*
 * The photograph, when the whole row has one — see home-berita.php for why it
 * is all or none. 16:9 rather than the source ratio: these are event
 * photographs of wildly different shapes and a row of them at their own
 * proportions is a ragged edge under three titles.
 */
.news-card__figure {
	display: block;
	margin-block-end: var(--wp--preset--spacing--40);
	overflow: hidden;
	border-radius: var(--r-card, 16px);
	background-color: var(--wp--preset--color--surface-tint);
}

@media (prefers-reduced-motion: reduce) {
	.news-card__image {
		transition: none;
	}

	.news-card--illustrated:hover .news-card__image,
	.news-card--illustrated:focus-within .news-card__image {
		transform: none;
	}
}

/*
 * Heading and standfirst as one block, V3 §22.
 *
 * The spacing here used to be a negative top margin on the standfirst, pulling
 * it back up through the heading's own bottom margin. That worked while both
 * were siblings; inside the block they are simply a heading and a paragraph,
 * and the gap between them is stated rather than cancelled out.
 */
.home-section__header {
	margin-block-end: var(--wp--preset--spacing--60);
}

.home-section__header .home-section__title {
	margin-block-end: var(--wp--preset--spacing--30);
}

/* A standfirst under a section title (§17), on the heading's own grid line. */
.home-section__standfirst {
	max-inline-size: 52ch;
	margin-block: 0;
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--large);
	line-height: 1.55;
}

/* ----------------------------------------------------------- taudhih --- */

/*
 * Taudhih reads as a journal, not as a stack of cards.
 *
 * It used to be `.jmns-card` with a 2px gold rule across the top, which gave it
 * the same white panel, radius and shadow as Fatwa two sections above — V3 §15
 * is explicit that this "makes it too similar to Fatwa" and asks for borderless
 * editorial entries carried on typography, the article number, the series, a
 * thin gold line and spacing.
 *
 * So there is no panel here at all. The section band behind these entries is
 * already `surface`, so removing the card's own white leaves the type sitting
 * directly on the page, which is the whole point: a scholarly explanation reads
 * as a passage, and a passage does not come in a box.
 *
 * What is left doing the work is the rule under the eyebrow. 1px and `gold`
 * rather than the old 2px — V3 §16 names the weight, and V2 §24 said not to
 * make the gold stronger. It is a boundary and not a word, so it carries no
 * contrast requirement; the number above it is information and uses `gold-text`
 * for that reason.
 */
.taudhih-entry {
	display: flex;
	flex-direction: column;
	align-items: start;
	block-size: 100%;
}


/*
 * The series label, lifted out of the title so the subject can start the line.
 * Small, quiet and above the heading: it identifies the card rather than
 * competing with it, and it carries a real distinction — Al-Hukmi explains a
 * ruling, Al-Falak is astronomy.
 */
.taudhih-entry__eyebrow {
	display: flex;
	align-items: baseline;
	gap: 0.4rem;
	/* The hairline belongs to the entry, so it runs the column's full width. */
	align-self: stretch;
	margin-block: 0 var(--wp--preset--spacing--30);
	padding-block-end: var(--wp--preset--spacing--30);
	border-block-end: 1px solid var(--wp--preset--color--gold);
	/* §16 sets this series' three sizes: 12-13 / 17-19 / 14-15. */
	font-size: var(--wp--preset--font-size--caption);
	font-weight: 600;
	letter-spacing: 0.02em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}

/*
 * The number reads as an identifier, so it is set as one — tabular figures keep
 * #9 and #119 the same width down a column.
 *
 * Brief §18 asks for it in muted gold, and gold is where that section wants its
 * one note of ornament. `gold-text`, not `gold`: the brief's accent is 2.69:1
 * on white and cannot be read, and an article number is information, not
 * decoration. The darker relative carries the same hue at 4.81:1.
 *
 * The tint behind it goes with the colour change — a gold figure on a sage pill
 * is two treatments arguing, and §18 asks for restraint.
 */
.taudhih-entry__number {
	color: var(--wp--preset--color--gold-text);
	font-variant-numeric: tabular-nums;
	font-weight: 700;
}

.taudhih-entry__title {
	margin-block: 0 var(--wp--preset--spacing--20);
	/* §24: 17-18px for this series. */
	font-size: var(--wp--preset--font-size--large);
	line-height: 1.4;
}

.taudhih-entry__title a {
	color: var(--wp--preset--color--text-primary);
	text-decoration: none;
}

.taudhih-entry__title a:hover {
	color: var(--wp--preset--color--interactive);
	text-decoration: underline;
}

.taudhih-entry__excerpt {
	margin-block: var(--wp--preset--spacing--20) 0;
	/* §24: 14-15px, a step under body copy so the summary stays secondary to
	   the title without becoming the "excessively small" text §28 warns of. */
	font-size: 0.9375rem;
	line-height: 1.6;
	color: var(--wp--preset--color--text-secondary);
}

/*
 * The dot between the number and the series inherits the eyebrow's colour.
 *
 * It was set to `border-strong` to sit back from the two labels it separates,
 * which measures 3.64:1 on white. Punctuation hidden from assistive technology
 * has a fair claim to being decoration, but it is still visible text and the
 * claim is an argument rather than a fact — and the dot is a period-sized glyph
 * that reads as quiet at any colour. Inheriting costs nothing and settles it.
 */

/*
 * The same affordance the news rows carry, for the same reason: the title is
 * the link, this is the sign that says so. Pushed to the foot of the entry so
 * three of unequal length still end on one line.
 */
.taudhih-entry__go {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	margin-block-start: auto;
	padding-block-start: var(--wp--preset--spacing--30);
	color: var(--wp--preset--color--interactive);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

.taudhih-entry__arrow {
	transition: transform var(--t-fast) var(--ease);
}

.taudhih-entry:hover .taudhih-entry__arrow {
	transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
	.taudhih-entry:hover .taudhih-entry__arrow {
		transform: none;
	}
}

/* ----------------------------------------------------------- posters --- */

/*
 * Dari Meja Mufti. The poster is the entry — see home-artikel.php — so the
 * card is the image, and the surrounding chrome gets out of its way: no fill,
 * no border, no padding. A .jmns-card shell here would put a white box and a
 * shadow around a graphic that already has its own margins and background,
 * which reads as a picture of a card rather than a card.
 */
/*
 * A column, not a row. This was `display: flex` with the default row direction
 * back when the card held one child — the link — and a row of one looks like a
 * column. Adding the date under it put the date *beside* the poster instead,
 * wrapped over two lines in the gutter. One child hid the bug; two showed it.
 */
.poster-card {
	display: flex;
	flex-direction: column;
	block-size: 100%;
}

/*
 * §17: "do not let artwork touch text too tightly". The gap was one step and
 * the title sat close enough to the poster to read as a caption printed on it
 * rather than as the article's own name.
 */
.poster-card__link {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--40);
	inline-size: 100%;
	text-decoration: none;
	color: var(--wp--preset--color--text-primary);
}

/*
 * Square, because every poster is 1080×1080 and the grid must not shuffle if
 * one ever is not. object-fit keeps an odd size cropped rather than letterboxed
 * with a stripe of card behind it.
 *
 * The border is not decoration: these posters are near-white at the edges — a
 * wood-grain background — so against the page they float without one, and a
 * boundary at 3:1 is what SC 1.4.11 asks of a control's edge. It doubles as
 * the hover cue's anchor.
 */
.poster-card__image {
	display: block;
	inline-size: 100%;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--r-card);
	background-color: var(--wp--preset--color--surface);
	transition: box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}

/*
 * Left, with the rest of the page since §3, and because §17 asks for this to
 * read as editorial rather than as a caption. A centred line under a centred
 * square is the caption arrangement; an article's name starts where its
 * column starts.
 *
 * `balance` stays. It stops a two-line title leaving one word stranded, which
 * is if anything more visible left-aligned than centred.
 */
.poster-card__title {
	margin-block: 0;
	/* §28: card title 16-19px. */
	font-size: var(--wp--preset--font-size--large);
	line-height: 1.3;
	text-align: start;
	text-wrap: balance;
	/*
	 * Two lines reserved whether or not the second is used, so every date in
	 * the row sits on one baseline. One of the three titles wraps and two do
	 * not, and with each block sized to its own text the dates stepped up and
	 * down across the row — the same fix `.jmns-tile__label` carries, for the
	 * same reason.
	 */
	min-block-size: calc(2 * 1.3em);
	min-block-size: 2lh;
}

/*
 * The date under the title, centred with it. Quiet — it qualifies the entry
 * rather than competing with it — but present, which is the point: it used to
 * exist only as whatever the poster artwork happened to print.
 */
/*
 * Under the title and left with it. Not reordered above it, though the berita
 * rows put the date first: the meta is a sibling of the link rather than a
 * child, so moving it visually would need `order` on a different flex parent
 * and would leave the reading order saying one thing and the page another.
 */
.poster-card__meta {
	margin-block: var(--wp--preset--spacing--20) 0;
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--caption);
	text-align: start;
}

/*
 * Lift the image, underline the title. The whole card is one link, so both
 * halves have to answer — a hover that only moves the picture leaves the words
 * looking inert, and one that only underlines the words looks like the picture
 * is not part of the target.
 */
.poster-card__link:hover .poster-card__image,
.poster-card__link:focus-visible .poster-card__image {
	box-shadow: var(--shadow-hover);
	transform: translateY(-2px);
}

.poster-card__link:hover .poster-card__title {
	color: var(--wp--preset--color--interactive);
	text-decoration: underline;
}

/*
 * The ring goes round the whole card, not the image alone. Default focus would
 * outline the link's box, which after the image lifts 2px sits slightly off it.
 */
.poster-card__link:focus-visible {
	outline: 3px solid var(--wp--preset--color--interactive);
	outline-offset: 4px;
	border-radius: var(--r-card);
}

@media (prefers-reduced-motion: reduce) {
	.poster-card__image {
		transition: none;
	}

	.poster-card__link:hover .poster-card__image,
	.poster-card__link:focus-visible .poster-card__image {
		transform: none;
	}
}

/* ------------------------------------------------------- statistics --- */

/*
 * The one deep band on the page, and where the colour descent lands before the
 * ink footer.
 *
 * On its own three tokens, not on `interactive` and `surface`. Those two are a
 * control's colour and the colour a card is made of, and reading them as "the
 * dark one and the light one" only works while every scheme is light. Under
 * `malam` `interactive` is #8FC0AB and `surface` is #1F2A25, so the band would
 * have painted dark ink on a light fill it was never designed to sit on — a
 * silent failure, because both declarations still name a real token and the
 * rule still applies. The band states its own ground, its own ink and its own
 * focus ring, and a scheme supplies all three together or none.
 *
 * Under `rasmi` the three hold #245746, #FFFFFF and #FAF8F2 — the same values
 * the two palette tokens resolved to, which is why this renders unchanged.
 */
.home-stats {
	background-color: var(--jmns-band-bg);
	color: var(--jmns-band-ink);
}

.home-stats__title {
	color: var(--jmns-band-ink);
}

/*
 * Four across when four fit, wrapping to whatever the width allows, and a short
 * last row centred rather than left under column one.
 *
 * Neither grid arrangement this has had can do that. `repeat(auto-fit,
 * minmax(11rem, 1fr))` let the count follow the width, but a `1fr` track fills
 * the row: a wrapped row holding one figure still holds it in column one with
 * two empty tracks beside it, and there is nothing left over to centre. It also
 * carried the 830–1050 fault, because a grid track is allowed to be narrower
 * than what stands in it — auto-fit handed out four tracks from 830px while the
 * visitor total needed 233px and a quarter of the shell was 178px, so the
 * number sat outside its own column and over the divider. `repeat(3, minmax(0,
 * 1fr))` cured the overflow by never asking for a fourth column, and bought the
 * centring with a span rule that could only be written because three per row
 * was known in advance. Under wrapping it is not known.
 *
 * Flex wrap, and every figure `flex: 1 1 <one share of the row>` — the row
 * divided by the number of figures in the band, less the gaps between them.
 * That basis is the whole mechanism, and three things follow from it:
 *
 * - **A line holds four only when four *equal* columns each fit what stands in
 *   them.** Flex breaks a line on each item's hypothetical main size, which is
 *   the basis raised to the item's own automatic minimum — its min-content. Four
 *   quarter-shares plus three gaps come to exactly the row, so the line holds
 *   all four when nothing was raised, and overflows the row the moment one
 *   figure needs more than its share. The question "do four fit" is therefore
 *   asked of the figures as they are actually rendering, at the size the clamp
 *   is actually giving them, and it is asked of columns that are already equal.
 *   This was `flex: 1 1 0%`, which asked it of each figure's min-content
 *   separately: at 768 that is 73.3 + 73.3 + 73.3 + 199.6 + gaps = 455 in a
 *   720px row, so four went on the line and then the visitor total kept its
 *   199.6 while the other three split what was left, 161.5 each. Four columns,
 *   one of them 38px wider than its neighbours, at every width from 768 to
 *   1064.
 *
 * - **Every line fills the row**, because free space is shared by grow. A line
 *   holding one figure gives that figure the whole width, and the item centres
 *   its own contents, so the orphan in a 3+1 lands in the middle of the band.
 *   `flex: 0 1` with `justify-content: center` would centre the boxes instead
 *   and put the same figure in the same place, but it would leave a three-wide
 *   row three quarter-shares wide inside a full-width shell — 253px of band
 *   used out of 342 at 390 — and a row would no longer start at a known x,
 *   which is what the divider below is clipped against.
 *
 * - **Figures in a line come out equal**, because equal bases and equal grow
 *   share the free space equally. Nothing can be raised above its share and
 *   spoil that, because an item that needed more than its share is exactly the
 *   item that broke the line.
 *
 * Where four begin: the visitor total is 4.918em of digits (see
 * .home-stats__value) and the clamp is at its 3rem ceiling by this point, so it
 * needs 236.06px; four of those and three 24px gaps need 1016.26px of shell,
 * which is a 1064.26px viewport. Measured, the band goes 3+1 at 1064 and 4
 * across at 1065.
 */
.home-stats__grid {
	/*
	 * The gutter is stated once and then used twice: as the gap, and as the
	 * part of the row the shares do not get. `--stat-count` is written onto the
	 * element by the template, because the band renders three figures or four
	 * depending on whether there is a visitor total to show, and a share has to
	 * know how many ways the row is being divided.
	 *
	 * `--stat-label-target` is the touch target every caption stands in — see
	 * .home-stats__label, which explains why it is here rather than on the link.
	 */
	--stat-gap: var(--wp--preset--spacing--50);
	--stat-label-target: 24px;
	display: flex;
	flex-wrap: wrap;
	gap: var(--stat-gap);
	/* margin-block only — see .home-section__grid. */
	margin-block: 0;
}

/*
 * Value above label, but dd after dt in the markup: reversing here keeps the
 * term/description pairing intact for assistive technology while the number
 * reads first, which is the point of the band.
 *
 * `justify-content: flex-end` is the *block-start* edge under `column-reverse`,
 * and it is what holds the four values on one line. This used to be left at the
 * default, which packs to main-start — the bottom — and that made every value's
 * position a function of how tall the label under it happened to be. The labels
 * are not the same height: three of them link to an archive and carry the
 * SC 2.5.8 touch target from `.home-stats__label a`, and the visitor total has
 * no archive to link to, so at 768 it was a bare 16.89px line box against their
 * 44px. Items on a flex line stretch to a common height — 90.17px there — so
 * the whole 27.11px difference was charged to the top of the shortest one:
 * 563.03 − 16.89 − 5.6 − 40.58 puts the visitor total's value at 499.97 while
 * the other three sat at 472.86. That is the ragged row the department saw, and
 * it is a label difference showing up as a value difference. Packed to the
 * block-start the value starts at the item's own top edge and cannot be moved
 * by anything below it; the label box is equalised as well, just below, so the
 * captions line up too.
 */
.home-stats__item {
	/*
	 * One share of the row. Both the wrap point and the equal widths come out
	 * of this — see .home-stats__grid, which owns the arithmetic.
	 */
	flex: 1 1 calc(
		( 100% - ( var(--stat-count, 4) - 1 ) * var(--stat-gap) ) / var(--stat-count, 4)
	);
	display: flex;
	flex-direction: column-reverse;
	justify-content: flex-end;
	align-items: center;
	gap: 0.35rem;
	text-align: center;
}

/*
 * §21's thin divider, drawn down the middle of the gap before each figure but
 * the first, with the band clipped just short of its own edge so that the one
 * belonging to a wrapped row is never painted.
 *
 * **Why it is in the gap and not on the item.** It used to be a
 * `border-inline-start`, and the figures were held off it by 12px of padding on
 * every item. That padding is charged to the column, and it is charged at the
 * moment the 48rem query turns it on: 24px per figure plus the gap going from
 * 12 to 24 takes 108px out of the row while the shell gains 2px. At 137.5% text
 * zoom, where those rem values are half as large again, it took 148px — enough
 * that the band fitted four figures at 768 and only three from 770 to 820,
 * which is a window getting wider and a column disappearing. Drawn in the gap
 * the clearance is the same 12px and it is paid for out of space the row was
 * already spending, so nothing changes across the breakpoint but the gap.
 *
 * **Why it is clipped and not selected against.** The arithmetic that used to
 * suppress the wrong stroke, `:not(:nth-child(3n + 1))`, knew where a row began
 * only because the grid was three wide. Wrapping takes that away: the fourth
 * figure opens no row at four across and opens the second at 3+1, and no
 * selector can tell those apart. What is still true is that every flex line
 * starts at the container's own inline-start edge — grow leaves no free space
 * for `justify-content` to move a line with — so the stroke that opens a row is
 * always the one 12px to the left of that edge, whichever figure it belongs to
 * and however many rows there are. Clipping the band there removes all of them
 * and nothing else.
 *
 * `overflow-clip-margin` is what keeps a focus ring out of it: the ring is 3px
 * wide at a 2px offset, so it stands 5px outside a label that has been given
 * the full width of its column, and the stroke stands 12px outside. 0.5rem
 * passes the first and still cuts the second, at every step of the text-size
 * control.
 *
 * That 12px is why the offset is written as half of `spacing--50` rather than
 * half of `--stat-gap`, which would otherwise be the tidier way to say it. 48rem
 * matches `min-width` and `max-width` both, so at exactly 768 the gap is still
 * the phone's 12px and half of it is 6px — inside the 8px clip margin, which
 * would paint the opening stroke of the wrapped row rather than cut it. The
 * clip margin cannot come down to meet it either: it has to stay above the
 * focus ring's 5px, and 5 to 6 is not a window to build on. Stated as a fixed
 * 12px the stroke sits in the middle of the 24px gap everywhere above the
 * breakpoint and flush against the previous figure's edge at 768 itself, where
 * the columns are 232px wide and nothing is near it.
 */
@media (min-width: 48rem) {
	.home-stats__grid {
		overflow-x: clip;
		overflow-clip-margin: 0.5rem;
	}

	.home-stats__item {
		position: relative;
	}

	/*
	 * Every figure carries a stroke, and the clip above removes the ones that
	 * would open a row.
	 *
	 * `+ .home-stats__item` looks like the right selector and is not: it skips
	 * the first figure in the *markup*, when what has to be skipped is the first
	 * figure in each *rendered row*, and where the row breaks is decided by the
	 * flex algorithm at a width CSS cannot ask about. Between 770 and 1060 the
	 * band wraps to three-and-one, and under that selector the visitor total
	 * opened its row with a stroke hanging to its left against nothing.
	 *
	 * So the stroke is drawn for all four, half a gap outside the figure it
	 * belongs to, and a figure that begins a row puts it half a gap outside the
	 * grid — where `overflow-x: clip` removes it. `overflow-clip-margin` is the
	 * reason this does not cost a focus ring: at 8px the ring around a caption
	 * link still shows, while a stroke at -12px does not.
	 */
	.home-stats__item::before {
		content: "";
		position: absolute;
		inset-block: 0;
		inset-inline-start: calc( var(--wp--preset--spacing--50) / -2 );
		inline-size: 1px;
		background-color: rgb(255 255 255 / 0.18);
	}
}

/*
 * Every caption occupies the same box, whether or not it holds a link.
 *
 * Three of the four link to an archive, and a link standing on its own carries
 * a touch target — 24px from the base rule and 44px under 48rem, both on
 * `.home-stats__label a`. The visitor total has no archive to link to, so its
 * caption was a bare line box: 16.89px against the others' 44px at 768, and
 * 23.8px against their 24px above it. That difference is what the row read as
 * ragged, because it moved the value above it as well — see .home-stats__item.
 *
 * The minimum goes on the caption itself and the text is centred in it, so the
 * unlinked one occupies exactly the box the linked ones do and its words sit
 * where theirs sit. The link keeps its own minimum: the target has to be the
 * thing a finger lands on, not the box around it. `--stat-label-target` is
 * declared on the grid and re-stated in the phone block so the two numbers stay
 * with the band's other measurements; they have to track the shared touch
 * target rules, which are ~3,600 lines away and shared with four other
 * components.
 */
.home-stats__label {
	display: flex;
	align-items: center;
	justify-content: center;
	min-block-size: var(--stat-label-target);
	opacity: 0.88;
}

/*
 * §21: "numbers should be visually prominent". They were set at the same size
 * as a section heading, which made them read as four more headings rather than
 * as the point of the band.
 *
 * Held back from the hero's scale on purpose — §21 also says not to look like
 * a marketing statistics section, and the difference between authority and
 * advertising here is largely how big the digits are.
 */
.home-stats__value {
	margin: 0;
	font-family: var(--wp--preset--font-family--heading);
	font-size: clamp(2.25rem, 3.2vw + 1rem, 3rem);
	font-weight: 700;
	line-height: 1;
	font-variant-numeric: tabular-nums;
	/* 3,011,460 next to 431 — the digits must not shuffle between columns. */
	letter-spacing: -0.01em;
	/*
	 * The figure states the width it needs, and the column is sized from it.
	 *
	 * This is what the wrap point above is computed from, so it has to be the
	 * width of the *final* figure and not of whatever is on screen: the value is
	 * a shrink-to-fit box, and left to itself it would be 22px wide while the
	 * count-up is passing through single digits and 177px when it lands. A row
	 * that fits four figures at "0" and three at "3,011,871" would re-wrap
	 * mid-count.
	 *
	 * Counted rather than measured, because CSS has no way to ask. PHP puts the
	 * composition of the rendered figure on the element — seven digits and two
	 * group separators for the visitor total — and the em ratios are this face's
	 * advances at this weight, taken at their widest across the whole size range
	 * the clamp above produces: a tabular digit is 0.6011em at 36px and 0.5944em
	 * at 48px, a comma 0.3511em and 0.3444em, both including the -0.01em
	 * tracking. So 4.918em for the visitor total, against a measured 176.77px at
	 * 36px (+0.3px), 232.69px at 48px (+3px) and 241.55px at the 49.5px that
	 * 137.5% text zoom produces (+1.9px). Never short, never more than 7px long.
	 *
	 * `em`, so it follows the clamp and the text-size control without being told
	 * about either. The floor is what the item can never be narrower than, so a
	 * face swap that widens the digits pushes the box out rather than spilling
	 * the number over the edge of it.
	 */
	min-inline-size: calc(
		( var(--stat-digits, 0) * 0.602em ) + ( var(--stat-marks, 0) * 0.352em )
	);
}

/*
 * The count-up's own width reservation, stood down.
 *
 * reveal.js wraps the animating figure in a span and holds it at
 * `<characters>ch`, on the stated ground that `ch` is exact under
 * `tabular-nums`. It is not: `ch` is the advance of the *proportional* zero —
 * 25px where the tabular digits being counted render at 21.64px — so the
 * visitor total reserved 225px for a number that measures 177. That was free
 * while the tracks were fixed and nothing was measured from the figure. It is
 * not free now: a 27% over-reservation appearing for the 900ms of the count
 * would widen the last column and re-wrap the row while it ran, and at 137.5%
 * text zoom 315px of reservation does not fit the 254px shell of a 320px phone
 * at all. The floor above already holds the box at the width the figure lands
 * at, before the script runs and after it has finished, so the span only has to
 * be as wide as the digits currently in it. `!important` because the script
 * sets it inline, which is the one case the keyword is for.
 */
.home-stats__value > [aria-hidden="true"] {
	min-inline-size: 0 !important;
}

.home-stats__label {
	font-size: var(--wp--preset--font-size--small);
}

/* Underlined, not colour-shifted: there is no lighter value available on this
   fill that still clears AA, so the link states itself with an underline. */
.home-stats__label a {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 0.2em;
}

.home-stats__label a:hover {
	text-decoration-thickness: 2px;
}

/*
 * A ring the deep bands can carry.
 *
 * The global ring is `interactive`, chosen for light surfaces where it measures
 * 5.57:1. On the footer's ink it measures 2.37:1 — under the 3:1 SC 1.4.11 asks
 * of a focus indicator — and that was thirteen tab stops: the phone, the email,
 * four link-list items, five social icons, the survey link and the partner logo.
 *
 * The footer's own focus rules only ever touched text-decoration and border
 * colour, so nothing was overriding the outline itself.
 *
 * One selector list, and now two, because the two bands stopped agreeing about
 * what a light ring is. They were both dark under every scheme this site had;
 * under `kontras` the statistics band is white and the footer is black, and no
 * single colour is visible on both. Each band therefore names its own ring, and
 * both are measured against their own ground by the contrast guard. Under
 * `rasmi` the two tokens hold `background`, which is what this rule said
 * before, at 7.83:1 on the band and 11.23:1 on the footer.
 */
.home-stats :focus-visible {
	outline-color: var(--jmns-band-ring);
}

.site-footer :focus-visible {
	outline-color: var(--footer-ring);
}

/* The latest-entry card, so the section is not raw text on a flat ground. */
.home-latest .entry-card {
	background-color: var(--wp--preset--color--surface);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--r-card);
	box-shadow: var(--shadow-card);
	padding: var(--wp--preset--spacing--50);
}

/* ================================================================ header == */

/*
 * The uploaded crest is 1925×318, and WordPress prints it at intrinsic size —
 * which is why the masthead was ~380px tall and pushed everything below the
 * fold. design.md sets an 80px navbar, so the mark is capped to fit inside it.
 *
 * Height is capped and width left auto so the aspect ratio is never distorted,
 * whatever the department uploads next.
 */
.custom-logo,
.wp-block-site-logo img {
	block-size: auto;
	/*
	 * 64px, up from 56 — V3 §25's "increase logo by approximately 10-15%", and
	 * this is 14.
	 *
	 * The note that used to sit here said it could not be done: that WordPress
	 * serves the logo as a 300x63 rendition, that the image was therefore
	 * already at natural size, and that the fix was a larger upload. The first
	 * part was a symptom and the conclusion was wrong. The uploaded crest is
	 * **1516x319**, with renditions all the way up; 300x63 is what the browser
	 * picks because `jmns_logo_sizes()` tells it the slot is 270px wide, and at
	 * 270px on a 1x screen that choice is correct.
	 *
	 * So the cap was never the only thing to raise — the hint had to move with
	 * it, or the browser would go on serving a 300px file into a 305px slot and
	 * the crest really would have gone soft. Both moved. See setup.php.
	 */
	max-block-size: 64px;
	inline-size: auto;
	max-inline-size: min(100%, 420px);
	object-fit: contain;

	/*
	 * Spread rather than padding, and a shadow rather than an outline: padding
	 * would grow the masthead on all five schemes to fix one, and the box has
	 * to stay the size it is. A spread-only shadow paints outside the box
	 * without occupying any, so the four light schemes render to the same
	 * pixels they did before this rule existed.
	 */
	background-color: var(--jmns-logo-plate);
	border-radius: 4px;
	box-shadow: 0 0 0 8px var(--jmns-logo-plate);
}

.site-header.is-stuck .custom-logo,
.site-header.is-stuck .wp-block-site-logo img {
	/* Up with the resting size, keeping the same proportion of it. */
	max-block-size: 44px;
	transition: max-block-size var(--t-fast) var(--ease);
}

/*
 * Masthead — brand, search, menu toggle. Two rows on desktop: identity and
 * search above, navigation below. B2 crams all three into one 80px strip,
 * which works with a small wordmark but not with a 420px government crest
 * carrying three separate coats of arms.
 */
.site-header__masthead {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--40);
	align-items: center;
	justify-content: space-between;
	inline-size: min(100% - 2 * var(--wp--preset--spacing--50), var(--wp--style--global--wide-size));
	margin-inline: auto;
	min-block-size: 80px;
	padding-block: var(--wp--preset--spacing--40);
}

.site-header__brand {
	display: flex;
	align-items: center;
	min-inline-size: 0;
}

/*
 * §9: "increase vertical breathing room in the main navigation row".
 *
 * The row was the height of its own link boxes and nothing more, so the
 * navigation read as a rule with words on it rather than as a band. A little
 * padding is the whole change — §9 also says "do not make the header
 * oversized", and the header still has to sit under a 520px hero without
 * competing with it.
 */
.site-header__nav-row {
	padding-block: var(--wp--preset--spacing--20);
	border-block-start: 1px solid var(--wp--preset--color--divider);
}

.site-header__nav-row > .primary-navigation {
	inline-size: min(100% - 2 * var(--wp--preset--spacing--50), var(--wp--style--global--wide-size));
	margin-inline: auto;
}

/*
 * Four widget areas, three zones — at the client's direction, and without
 * moving anybody's widgets.
 *
 * Four equal columns gave every area a full-height track whatever it held. The
 * social row holds one 44px line of icons, so its column was a heading and then
 * 250px of empty green, which is most of what made the band feel unfinished.
 *
 * The client's grouping is department / navigation + social / feedback. That is
 * three tracks with the links and the icons stacked in the middle one, which
 * grid can do by placement alone: no wrapper, no regrouped widget areas, and
 * nothing about which widget belongs where written into the markup.
 *
 * Scoped to four active areas. footer.php only emits the ones an editor has
 * filled, so with three or fewer the placement below would put content in
 * tracks that do not exist — `data-columns` is already published for exactly
 * this kind of decision.
 */
@media (min-width: 64.0625rem) {
	.site-footer__columns[data-columns="4"] {
		grid-template-columns: 1.5fr 1.2fr 1.15fr;
		row-gap: var(--wp--preset--spacing--60);
	}

	.site-footer__columns[data-columns="4"] > :nth-child(1) {
		grid-area: 1 / 1 / span 2 / auto;
	}

	.site-footer__columns[data-columns="4"] > :nth-child(2) {
		grid-area: 1 / 2 / auto / auto;
	}

	/* Social sits under the links it belongs with, not beside them. */
	.site-footer__columns[data-columns="4"] > :nth-child(3) {
		grid-area: 2 / 2 / auto / auto;
	}

	.site-footer__columns[data-columns="4"] > :nth-child(4) {
		grid-area: 1 / 3 / span 2 / auto;
	}
}

/* ------------------------------------------------------------- search --- */

.site-search {
	flex: 1 1 22rem;
	min-inline-size: 0;
	max-inline-size: 34rem;
}

/*
 * Inline variant — the 404 page and the no-results empty state. It carries a
 * VISIBLE label, unlike the masthead: someone who has just failed a search
 * needs to be told what the box does, not reminded by a placeholder that
 * disappears the moment they type.
 */
.site-search--inline {
	display: block;
	max-inline-size: 34rem;
	margin-block: var(--wp--preset--spacing--50);
}

.site-search--inline .site-search__form {
	display: block;
	/*
	 * The 3px insets the control inside the masthead's outer pill. Here there is
	 * no outer pill to inset from, so it was left over — and it put the label and
	 * the search box 3px right of the heading and the paragraph above them, on a
	 * page whose whole job is one column of three things.
	 */
	padding: 0;
	border: 0;
	border-radius: 0;
	background: transparent;
	overflow: visible;
}

.site-search--inline .site-search__form:focus-within {
	outline: none;
	box-shadow: none;
}

.site-search__label {
	display: block;
	margin-block-end: var(--wp--preset--spacing--20);
	font-weight: 600;
	color: var(--wp--preset--color--text-primary);
}

/* The inline variant is the same control, so it is built the same way. */
.site-search__control {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--20);
	/* Same 3px gutter as the masthead control; see `.site-search__form`. */
	padding: 3px;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--r-pill);
	background-color: var(--wp--preset--color--surface);
	transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}

/* Same ring as the masthead control — see the note there. */
.site-search__control:focus-within {
	border-color: var(--wp--preset--color--interactive);
	outline: 3px solid var(--wp--preset--color--interactive);
	outline-offset: 2px;
	box-shadow: none;
}

/*
 * One control, not two stuck together.
 *
 * The field was a bordered box and the button a filled block sitting flush
 * inside it — no gutter, no radius of its own — so the join read as a seam
 * between two components rather than as an action belonging to a field. This is
 * among the first things anyone sees on the page.
 *
 * The container now carries 4px of padding, which is what the button sits in:
 * the white gutter is what makes the pair read as one object with something
 * inside it. The ring stays on the wrapper, because focusing the input should
 * outline the whole control and not half of it.
 */
.site-search__form {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--20);
	/*
	 * 3px, with the field and the button inside it stated at 40 — §4 asks for a
	 * 40-42px search field and this measured 54 around a 44px one. The white
	 * gutter the note below describes survives at 3px; the pair sits in 50.
	 *
	 * `center` rather than `stretch`, now that both children carry a height of
	 * their own; stretch is what pushed the field to match the button.
	 */
	padding: 3px;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--r-pill);
	background-color: var(--wp--preset--color--surface);
	transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}

/*
 * A real ring, at the same weight every other control gets.
 *
 * The field suppresses its own outline so the pair reads as one control, and
 * the compensation used to be a border-colour change plus a 22%-alpha glow.
 * Measured, that was a 1.75:1 state change on a 1px border and a glow
 * compositing to 1.28:1 against white — an indicator present in the markup and
 * close to invisible in use, on the control this site's primary task runs
 * through. The submit button sitting inside the same wrapper got a 3px 8.31:1
 * ring, so the two halves disagreed with each other.
 *
 * Now the wrapper takes that same ring. The border still darkens underneath it,
 * which costs nothing and helps at high zoom.
 */
.site-search__form:focus-within {
	border-color: var(--wp--preset--color--interactive);
	outline: 3px solid var(--wp--preset--color--interactive);
	outline-offset: 2px;
	box-shadow: none;
}

/*
 * The field is transparent and borderless: the wrapper carries the boundary, so
 * a second one inside it drew a rounded outline around the text and the control
 * read as three concentric shapes.
 *
 * This used to need `.site-search` in front of it to outrank the form defaults.
 * Those are in :where() now, so a single class is enough.
 */
.site-search__input {
	flex: 1 1 auto;
	min-inline-size: 0;
	/*
	 * 40 inside a 48px control: the container's 3px padding supplies the rest.
	 *
	 * Both properties, deliberately. The base `:where(input[type="search"], …)`
	 * rule sets `min-block-size: 48px` at zero specificity — a floor, not a
	 * height — so stating only `block-size: 40px` here leaves that floor in
	 * force and the field renders 48. The floor has to be lowered by name, and
	 * the height stated as well, because the submit beside it would otherwise
	 * stretch the row back to 44.
	 */
	min-block-size: 40px;
	block-size: 40px;
	padding-block: 0;
	padding-inline: var(--wp--preset--spacing--40) var(--wp--preset--spacing--20);
	border: 0;
	border-radius: var(--r-pill);
	background: transparent;
	color: var(--wp--preset--color--text-primary);
	/* Under 16px iOS zooms the page when the field takes focus. */
	font-size: max(1rem, 16px);
}

/* The wrapper already shows focus; a second ring inside it is noise. */
.site-search__input:focus-visible {
	outline: none;
}

.site-search__input::placeholder {
	color: var(--wp--preset--color--text-secondary);
	opacity: 1;
}

.site-search__submit {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.45rem;
	/*
	 * 40px, inset in the container's padding rather than filling its full
	 * height. A button that reaches both edges of the field it sits in is a
	 * second component; one that floats inside it is the field's own action.
	 *
	 * Both properties again — `:where(button, …)` carries the same 48px floor
	 * as the field, and a height alone does not lower it.
	 */
	min-block-size: 40px;
	block-size: 40px;
	min-inline-size: 40px;
	padding-inline: var(--wp--preset--spacing--40);
	border: 0;
	border-radius: var(--r-pill);
	background-color: var(--wp--preset--color--interactive);
	color: var(--jmns-on-primary);
	font-size: 0.9375rem;
	font-weight: 600;
	letter-spacing: 0.01em;
	cursor: pointer;
	transition: background-color var(--t-fast) var(--ease);
}

.site-search__submit:hover {
	background-color: var(--wp--preset--color--interactive-hover);
}

.site-search__submit svg {
	inline-size: 20px;
	block-size: 20px;
	flex: none;
}

@media (max-width: 30rem) {
	/* Icon only, so it is a circle rather than a pill with dead space in it. */
	.site-search__submit {
		padding-inline: 0;
		aspect-ratio: 1;
	}

	/* At 360 the word costs more than it earns; the icon plus the field's own
	   label carry it. */
	.site-search__submit span {
		position: absolute;
		inline-size: 1px;
		block-size: 1px;
		overflow: hidden;
		clip-path: inset(50%);
		white-space: nowrap;
	}

	.site-search__submit {
		padding-inline: var(--wp--preset--spacing--30);
	}
}


.site-title {
	margin: 0;
	/* Scales down before the nav runs out of room at 1280. */
	font-size: clamp(1.125rem, 1.6vw + 0.5rem, var(--wp--preset--font-size--title-sm));
	font-weight: 700;
}

.site-title a {
	color: var(--wp--preset--color--text-primary);
	text-decoration: none;
}

/* ---------------------------------------------------------- navigation --- */

/*
 * Six items plus a long site title overflowed the viewport at 1280 —
 * DIREKTORI sat past the right edge. The nav is allowed to shrink and its
 * items to wrap onto a second row rather than push past the edge; min-inline-
 * size:0 is what actually permits a flex child to shrink below its content.
 */
.primary-navigation {
	flex: 1 1 auto;
	min-inline-size: 0;
}

/*
 * Split in two on purpose.
 *
 * The reset half — stripping the list's own margin, padding and bullets — is
 * element-plus-class and so outranked every `.sub-menu` rule that tried to set
 * its own box. The panel lost its vertical padding and the third level lost its
 * indent, both silently. In :where() it weighs nothing and a component sets its
 * own box by saying so.
 *
 * The layout half stays at normal weight: it is this nav's row, not a default,
 * and a panel overrides it deliberately rather than by accident.
 */
:where(.primary-navigation ul) {
	margin: 0;
	padding: 0;
	list-style: none;
}

.primary-navigation ul {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-start;
	column-gap: var(--wp--preset--spacing--40);
	row-gap: 0;
}

/*
 * A submenu item is a link plus its disclosure button. The link is display:flex
 * for the 44px target, which made it fill the row and pushed the toggle onto a
 * line of its own — the stray caret under PERKHIDMATAN.
 */
.primary-navigation .has-submenu {
	display: flex;
	/*
	 * Wrapping, so a submenu that is in flow drops to its own line instead of
	 * joining the row. The row exists to hold the link and its caret together;
	 * a `position: static` list became a third item in it and was laid out
	 * beside the caret, outside its own parent panel — which is what happened
	 * the moment the third level stopped floating, and is what a phone had been
	 * doing all along.
	 */
	flex-wrap: wrap;
	align-items: center;
	gap: 0.125rem;
}

/*
 * A group parent is a label with a list under it, so it stacks. There is no
 * caret to sit beside any more — only the top level discloses — which is what
 * removed the flex-row conflict this rule used to work around.
 */
.sub-menu .is-group {
	display: block;
}

.primary-navigation a {
	display: block;
	/* §25: more vertical breathing room in the nav row. 12px, up from 8. */
	padding-block: 0.75rem;
	color: var(--wp--preset--color--text-primary);
	/*
	 * Level one of three, and the smallest of them, at the client's direction.
	 *
	 * 13px uppercase and letterspaced reads as a bar you orient by; the panel
	 * beneath it is where you actually read, so it is larger. Inverting that —
	 * a big bar over small panels — is what made every level look the same
	 * weight and none of them look like a hierarchy.
	 *
	 * It also bought room back, once: six uppercase items plus a 420px crest did
	 * not fit 1280px at body size and DIREKTORI fell off the edge. That was when
	 * the crest and the menu shared a row. They have not since the masthead was
	 * split, and the nav row now measures 754px of items in a 1280px band —
	 * 486px of headroom. V3 §25 asks for slightly larger navigation text, and
	 * the reason not to has expired.
	 *
	 * 14px, not 16: one step, and the three-level hierarchy above still holds.
	 */
	font-size: 0.875rem;
	font-weight: 700;
	letter-spacing: 0.045em;
	text-decoration: none;
	/* Reserve the underline's space so hover does not shift layout. */
	border-block-end: var(--jmns-rule) solid transparent;
}

.primary-navigation a:hover,
.primary-navigation a:focus-visible,
.primary-navigation [aria-current] {
	border-block-end-color: var(--wp--preset--color--primary-light);
	color: var(--wp--preset--color--interactive);
}

/* Touch targets: 44px minimum per §4.4. */
.primary-navigation a,
.utility-bar a {
	min-block-size: 44px;
	display: flex;
	align-items: center;
}

/* =========================================================== prayer bar == */

/*
 * Prayer bar — one quiet line.
 *
 * It had grown to four stacked rows: a date, a "WAKTU SOLAT SGR01" heading, a
 * SETERUSNYA banner, and the times. Three of those said the same thing in
 * different words. What is left is the date, the seven times with the next one
 * chipped, and the zone control that names the zone.
 *
 * primary-light carries text-primary at 6.28:1 and still cannot carry white
 * (2.39:1). Full-strength primary now can — 8.31:1 under the 2026-08 palette,
 * where it could not at 3.88:1 before — so the band is the light tint with dark
 * text by choice rather than by constraint. It stays that way: a green bar
 * directly under a green-accented header would put the page's two heaviest
 * marks side by side.
 */
/*
 * The band is the sage tint, not full primary-light.
 *
 * Green opened the page and then disappeared until the footer; it now arrives
 * with depth instead. What the band loses in colour it keeps in structure: the
 * tint is an 1.14 step against white, which is quiet on its own, so hairlines
 * top and bottom draw the edges the fill no longer states on its own.
 *
 * Ink on the tint is 13.05:1, against 6.28:1 on primary-light, and
 * text-secondary clears AA here too at 4.76:1 — so secondary copy in this band
 * is not restricted to an opacity trick.
 */
.prayer-bar {
	background-color: var(--wp--preset--color--surface-tint);
	color: var(--wp--preset--color--text-primary);
	font-size: var(--wp--preset--font-size--small);
	border-block: 1px solid rgb(var(--jmns-hairline-rgb) / var(--jmns-a-hair));
}

/*
 * One grid for the whole bar, so both halves share baselines.
 *
 * Stacking each time as label-over-number is what made the row fit at all:
 * inline "IMSAK 05:50" pairs needed ~1430px of a 1193px container, while the
 * same seven stacked need ~620px, and folding the zone select behind a pencil
 * returned another 200px.
 *
 * What that left was two independent columns, each spacing itself — so the
 * date's baseline landed 1px off the prayer labels', the clock 2-5px off the
 * times, and the zone chip 1px off the districts. Near-misses read as sloppy in
 * a way that being plainly unaligned does not.
 *
 * Rows are declared here once. Column 1 places the date, the clock line and the
 * zone block explicitly; the times list spans rows 1-2 and subscribes to them
 * with subgrid, so prayer labels sit on the date's row and the times themselves
 * sit on the clock's. Where subgrid is unsupported the declaration drops and the
 * cells fall back to stacking on their own — the same layout, unlocked.
 */
.prayer-bar__inner {
	inline-size: min(100% - 2 * var(--wp--preset--spacing--50), var(--wp--style--global--wide-size));
	margin-inline: auto;
	display: grid;
	grid-template-columns: auto minmax(0, 1fr) auto;
	grid-template-rows: auto auto;
	align-items: end;
	column-gap: var(--wp--preset--spacing--50);
	row-gap: 0.1rem;
	padding-block: var(--wp--preset--spacing--30);
}


/* The end column, centred across both rows: "where" qualifies the whole bar. */
.prayer-bar__zone--compact,
.prayer-bar__zone-static {
	grid-column: 3;
	grid-row: 1 / span 2;
	align-self: center;
	margin: 0;
}

.prayer-bar__stale {
	grid-column: 1 / -1;
}

/*
 * Both calendars at one weight, divided by a rule that is quieter than either.
 * They are the same day stated twice; making one of them the loud half turns a
 * pair into a headline and a footnote.
 */
.prayer-bar__date {
	grid-column: 1;
	grid-row: 1;
	margin: 0;
	font-weight: 600;
}

/*
 * 0.62, not 0.4. It is aria-hidden and arguably exempt as decoration, but it is
 * rendered text and at 0.4 it measured 2.21:1 on the bar's tint — below the
 * 4.5:1 SC 1.4.3 asks of text, and the exemption is not worth leaning on for a
 * glyph nobody will notice getting darker. 3.82:1 now, and still clearly
 * subordinate to the dates either side of it.
 */
.prayer-bar__date-sep {
	opacity: 0.62;
}

.prayer-bar__hijri {
	opacity: 0.85;
}

/* ---------------------------------------------- live clock and countdown --- */

/*
 * The clock reads the site's wall time, so it is the one number on the page
 * that is true for Selangor rather than for the visitor's device. It is set in
 * tabular figures because it changes every second: proportional digits would
 * make the whole line twitch as 1s and 4s swap through.
 */
/* The status line, which is now just the countdown. */
.prayer-bar__now {
	grid-column: 1;
	grid-row: 2;
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.2rem var(--wp--preset--spacing--30);
	margin: 0;
}

/*
 * The bar's dominant element, and now its only status line. It spent a version
 * as a 12px aside next to an 18px clock, which had the hierarchy inverted.
 *
 * Tabular figures: it reticks every second, and proportional digits make the
 * whole line twitch as 1s and 4s swap through.
 */
.prayer-bar__next {
	font-size: var(--wp--preset--font-size--large);
	font-weight: 700;
	line-height: 1.15;
	font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------- the times --- */

/*
 * Label above, number below. Seven of these read as one row of columns, which
 * is how a prayer timetable is read everywhere else — and it halves the width
 * of the inline form it replaces.
 *
 * Seven equal grid tracks rather than a flex row: flex packed the times to the
 * left and left 380px of the 904px band empty, and on a phone it wrapped 5 + 2.
 * Equal tracks fill the band, and every column lands on the same rhythm whether
 * its label is ASAR or SYURUK.
 */
/*
 * Spans the bar's first two rows and inherits them, so the labels sit on the
 * date's baseline and the times on the clock's. The hairline separates "when and
 * where" from the times without putting a box around either.
 */
.prayer-bar__times {
	grid-column: 2;
	grid-row: 1 / span 2;
	display: grid;
	grid-template-columns: repeat(7, minmax(0, 1fr));
	grid-template-rows: subgrid;
	column-gap: var(--wp--preset--spacing--20);
	margin: 0;
	padding: 0;
	padding-inline-start: var(--wp--preset--spacing--50);
	/*
	 * 0.14 rather than `--jmns-a-rule`. The named strengths are .10/.15/.20 and
	 * this rule was authored at .14; snapping it to .15 would move two channels
	 * of a rendered pixel, which is a visual change smuggled into a refactor.
	 * It reads the hairline channel, so it still inverts under a dark scheme —
	 * what it cannot do is go to full strength under `kontras`, where it
	 * measures 1.30:1. It divides two halves of the prayer bar rather than
	 * bounding a control, so nothing in 1.4.11 turns on it.
	 */
	border-inline-start: 1px solid rgb(var(--jmns-hairline-rgb) / 0.14);
	list-style: none;
}

/*
 * Each cell takes the same two rows as the bar, so every label shares one
 * baseline and every time shares another. Padding is identical on all seven:
 * only .is-next carried it before, which made that one cell 55px against ~44px
 * and lifted the highlighted time off the row it belongs to. The fill is the
 * only thing that distinguishes it.
 */
.prayer-bar__time {
	grid-row: 1 / span 2;
	display: grid;
	grid-template-rows: subgrid;
	justify-items: center;
	/* Hug the content: the highlight is a chip around a time, not a filled
	   column. The track stays 1fr; only the ink inside it shrinks. */
	justify-self: center;
	padding: 0.25rem var(--wp--preset--spacing--20);
	/*
	 * The hairline is reserved on all seven and painted on one. Giving the
	 * border to `.is-next` alone would grow that cell by 2px and lift its time
	 * off the row the other six share — the same near-miss the equalised
	 * padding above exists to prevent. `transparent` survives forced-colors
	 * mode, so the six do not sprout boxes there either.
	 */
	border: 1px solid transparent;
	border-radius: var(--r-button);
	/* The one authored moment in the bar: when a prayer passes, the chip hands
	   over to the next column instead of jumping. The hairline is part of that
	   handover now, and the text no longer changes colour, so it leaves the
	   list. Global reduced-motion shortens this to nothing. */
	transition: background-color var(--t-base) var(--ease), border-color var(--t-base) var(--ease);
}

/*
 * The next prayer: a white chip on the tint, bounded by a green hairline.
 *
 * This has been walked down twice. It began as a `text-primary` fill, which is
 * 13.05:1 against the band — the largest value step anywhere on the page. It
 * out-contrasted the masthead, the navigation and the first heading, so a
 * utility strip led the page and the department said so. An `interactive` fill
 * reduced that to 7.24:1. The department wanted quieter still, and this is as
 * far as it goes while remaining a state indicator at all.
 *
 * The fill can no longer carry the signal: white is a 1.14 step against the
 * tint, nowhere near the 3:1 SC 1.4.11 asks of anything conveying state. So the
 * **hairline** carries it, and it has two neighbours to clear rather than one —
 * the white inside and the tint outside. `interactive` is 8.31:1 against white
 * and 7.24:1 against the band, so both sides pass with room.
 *
 * The **time** stays ink, at 14.98:1 on the white — a shade above the six
 * around it, which is the right direction for emphasis even if nobody
 * consciously sees it. Putting it in green would have tied chip, border and
 * text together but left the one time worth reading less legible than its
 * neighbours, and the highlighted item cannot be the hardest to read.
 *
 * The **label** takes the green instead. A 1px line of `interactive` does not
 * read as green at all — hue needs area, and at a hairline's width the eye
 * files it as "dark edge". Without that the chip was a plain outlined box, and
 * a plain outlined box on this band already means the zone control. One green
 * word restores the hue the border cannot carry, at 8.31:1 on white, clear of
 * AA at 13px.
 *
 * **The thing to be careful about here.** White surface plus hairline is
 * already the zone toggle's recipe, three cells to the right, and that one is
 * a control. Four things keep them apart: this is `--r-button` against the
 * toggle's `--r-pill`, the hairline is brand green against its neutral
 * `border-strong`, there is no pointer cursor and no hover state, and it sits
 * mid-row among six cells shaped exactly like it rather than alone at the end.
 * If any of those go, the chip starts inviting a click it cannot answer.
 */
.prayer-bar__time.is-next {
	border-color: var(--wp--preset--color--interactive);
	background-color: var(--wp--preset--color--surface);
}

.prayer-bar__label {
	font-size: var(--wp--preset--font-size--caption);
	text-transform: uppercase;
	letter-spacing: 0.04em;
	opacity: 0.85;
}

.prayer-bar__time.is-next .prayer-bar__label {
	color: var(--wp--preset--color--interactive);
	/* Full strength: 0.85 on a colour this size would cost contrast the 13px
	   caps cannot spare. */
	opacity: 1;
}

.prayer-bar__value {
	font-weight: 600;
	/* Digits must align when times sit in a row of columns. */
	font-variant-numeric: tabular-nums;
}

.prayer-bar__stale,
.prayer-bar__unavailable {
	flex: 1 1 100%;
	margin: 0;
	font-style: italic;
	opacity: 0.9;
}

/* ------------------------------------------------------- zone switcher --- */

/*
 * The zone is a code and a pencil, not a 16rem select sitting open in the band.
 * The select spelled out six district names to say "SGR01", ellipsised to
 * "SGR01 — Gombak, Petali…", and took a fifth of the bar to do it. The code
 * names the zone, the districts are footnoted under the times, and the control
 * to change it is one glyph — a <details>, so it opens without JavaScript.
 */
.prayer-bar__zone--compact {
	position: relative;
}

.prayer-bar__zone-toggle,
.prayer-bar__zone-static {
	display: inline-flex;
	align-items: center;
	gap: 0.3rem;
	margin: 0;
	padding: 0.25rem var(--wp--preset--spacing--20);
	font-size: var(--wp--preset--font-size--caption);
	font-weight: 700;
	letter-spacing: 0.06em;
}

.prayer-bar__zone-toggle {
	/*
	 * No negative margin. It was here to pull the code back under the date when
	 * the chip was bare text; once the chip gained a border and a fill it
	 * over-corrected, putting the border box at x=33 against the date's x=41 —
	 * the one element breaking the bar's left edge.
	 */
	border-radius: var(--r-pill);
	cursor: pointer;
	list-style: none;
	/*
	 * A resting surface, not a hover-only one. This is the only way to change
	 * zone, and a control that looks like text until you touch it is a control
	 * most visitors never find.
	 *
	 * White at low alpha did that job while the band was mid-green. On the sage
	 * tint a white wash is a 1.14 step and effectively invisible, so the chip is
	 * now a solid surface bounded by border-strong — 3.64:1 against white, which
	 * is what SC 1.4.11 asks of a control's boundary.
	 */
	border: 1px solid var(--wp--preset--color--border-strong);
	background-color: var(--wp--preset--color--surface);
	transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}

/* Both engines need telling; ::marker alone leaves the triangle in Safari. */
.prayer-bar__zone-toggle::-webkit-details-marker {
	display: none;
}

.prayer-bar__zone-toggle::marker {
	content: "";
}

/* Hover and open both deepen the boundary rather than the fill: the fill is
   already the lightest value on the band, so it has nowhere brighter to go. */
.prayer-bar__zone-toggle:hover,
.prayer-bar__zone--compact[open] > .prayer-bar__zone-toggle {
	border-color: var(--wp--preset--color--interactive);
	background-color: var(--wp--preset--color--surface);
}

.prayer-bar__zone-pencil {
	inline-size: 0.95rem;
	block-size: 0.95rem;
	flex: 0 0 auto;
	opacity: 0.75;
}

.prayer-bar__zone-toggle:hover .prayer-bar__zone-pencil,
.prayer-bar__zone--compact[open] .prayer-bar__zone-pencil {
	opacity: 1;
}

/*
 * The panel floats over the page rather than opening inline: a disclosure that
 * grows the band would push the whole homepage down by 60px on every use.
 */
.prayer-bar__zone--compact > .prayer-bar__zone-form {
	position: absolute;
	inset-block-start: calc(100% + 0.35rem);
	/*
	 * Opens leftward, from the right edge of its button.
	 *
	 * Anchored `inset-inline-start: 0` it opened rightward — and the zone button
	 * sits at the right end of the prayer bar, at x=1139 on a 1280 screen, so a
	 * 528px panel ran 387px off the page. The cap above was doing its job; the
	 * anchor was the problem, and it only showed once larger text made the panel
	 * wide enough to reach the edge.
	 */
	inset-inline-start: auto;
	inset-inline-end: 0;
	z-index: 30;
	inline-size: max-content;
	/* Room for the longest district list, but never wider than the phone it
	   opens on. */
	max-inline-size: min(24rem, calc(100vw - 2 * var(--wp--preset--spacing--50)));
	padding: var(--wp--preset--spacing--30);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--r-input);
	background-color: var(--wp--preset--color--surface);
	box-shadow: var(--shadow-hover);
	/* The radio group owns the width; the no-JS submit sits under it rather
	   than beside it, where it would squeeze the district lists. */
	flex-direction: column;
	align-items: stretch;
}

/*
 * The zone picker cannot be wider than the page.
 *
 * The compact variant caps itself; this one never did, because at the default
 * text size the three district lists happen to fit. They stop fitting the
 * moment anything widens the text — the reading-preferences panel at its
 * largest step with wide spacing pushed this form to 1,667px and gave every
 * page 28px of real horizontal scroll.
 *
 * A layout that only holds at one text size is a layout that fails WCAG 1.4.4
 * and 1.4.12, both of which say content must survive the reader resizing it.
 * Found by turning the site's own accessibility switches all the way up.
 */
.prayer-bar__zone-form {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--20);
	align-items: center;
	max-inline-size: 100%;
	min-inline-size: 0;
}

.prayer-bar__zone-set,
.prayer-bar__zone-option {
	min-inline-size: 0;
	max-inline-size: 100%;
}

/* The district list is the part that grows; let it wrap rather than push. */
.prayer-bar__zone-option-districts {
	overflow-wrap: anywhere;
}

/* ------------------------------------------------------- zone radios --- */

.prayer-bar__zone-set {
	display: flex;
	flex-direction: column;
	gap: var(--wp--preset--spacing--10);
	margin: 0;
	padding: 0;
	border: 0;
}

.prayer-bar__zone-legend {
	padding: 0;
	margin-block-end: var(--wp--preset--spacing--10);
	font-size: var(--wp--preset--font-size--caption);
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--wp--preset--color--text-secondary);
}

/*
 * The whole row is the target, not just the 16px dot — a 44px band the width of
 * the panel, so choosing a zone on a phone does not require aiming.
 */
.prayer-bar__zone-option {
	display: grid;
	grid-template-columns: auto auto 1fr;
	align-items: center;
	gap: 0 var(--wp--preset--spacing--20);
	/*
	 * Stated, not assumed. The comment above has always claimed a 44px band,
	 * and padding alone produced 40px for the two options whose district list
	 * fits on one line — close enough to pass SC 2.5.8's 24px, short of the 44
	 * the brief asks for and short of what this file said it did.
	 */
	min-block-size: 44px;
	padding: var(--wp--preset--spacing--20);
	border-radius: var(--r-button);
	cursor: pointer;
	transition: background-color var(--t-fast) var(--ease);
}

.prayer-bar__zone-option:hover {
	background-color: var(--wp--preset--color--divider);
}

.prayer-bar__zone-option input {
	/* Baseline-aligning a radio against text is what makes the row look level;
	   the control's own box sits slightly high otherwise. */
	margin: 0;
	transform: translateY(0.15em);
}

.prayer-bar__zone-option-code {
	font-weight: 700;
	font-size: var(--wp--preset--font-size--small);
	letter-spacing: 0.04em;
}

.prayer-bar__zone-option-districts {
	font-size: var(--wp--preset--font-size--caption);
	color: var(--wp--preset--color--text-secondary);
}

.prayer-bar__zone-form select {
	inline-size: auto;
	/*
	 * A flat 16rem, NOT min(100%, 16rem). The percentage resolves against the
	 * form, and the form is shrink-to-fit — sized by this select. That makes
	 * the percentage indefinite, the min() falls back to no cap at all, and
	 * the control took its longest option's width: "SGR01 — Gombak, Petaling,
	 * Sepang, Hulu Langat, Hulu Selangor, Shah Alam" at 633px.
	 */
	max-inline-size: 16rem;
	min-block-size: 44px;
	padding-block: 0.35rem;
	background-color: var(--wp--preset--color--surface);
	text-overflow: ellipsis;
}

.prayer-bar__zone-submit {
	min-block-size: 44px;
	padding-inline: var(--wp--preset--spacing--40);
}

@media (max-width: 48rem) {
	/*
	 * One column. The shared-baseline grid is a desktop idea: stacked, the times
	 * sit below the clock rather than beside it, so there are no cross-column
	 * baselines left to lock and the subgrid has to be released or the seven
	 * cells would try to fit two rows meant for the date and the clock.
	 */
	.prayer-bar__inner {
		grid-template-columns: minmax(0, 1fr);
		grid-template-rows: none;
		row-gap: var(--wp--preset--spacing--20);
	}

	.prayer-bar__date,
	.prayer-bar__now,
	.prayer-bar__times,
	.prayer-bar__zone--compact,
	.prayer-bar__zone-static {
		grid-column: 1;
		grid-row: auto;
		align-self: start;
	}

	/*
	 * The date and the zone share a line.
	 *
	 * Stacked, this bar was four blocks tall — date, countdown, zone chip, times
	 * — and took 190px of an 844px screen, more than the banner below it. The
	 * date is a short line and the zone chip is a chip; putting them on one row
	 * costs nothing and gives the row back.
	 *
	 * The tracks themselves are declared further down, with the rule that lets
	 * the times row shrink — see the note there.
	 */
	.prayer-bar__date {
		grid-column: 1;
		grid-row: 1;
		align-self: center;
		/* Sharing the row with the zone chip leaves it about 250px, and the full
		   line — weekday, date, and the Hijri date after it — needs every one of
		   them to stay on one line. */
		font-size: 0.8125rem;
		line-height: 1.35;
	}

	.prayer-bar__zone--compact,
	.prayer-bar__zone-static {
		grid-column: 2;
		grid-row: 1;
		justify-self: end;
		align-self: center;
	}

	.prayer-bar__now,
	.prayer-bar__times {
		grid-column: 1 / -1;
	}

	/*
	 * Release the cells from the two-row band the desktop bar locks them into.
	 *
	 * `.prayer-bar__times` had its template reset for mobile but each cell kept
	 * `grid-row: 1 / span 2` from the desktop rule, which pinned all seven onto
	 * the same band: they laid out in one row 579px wide inside a 342px box, so
	 * Imsak, Subuh and Syuruk were simply off the side of the screen. The bar
	 * showed four prayers and gave no sign the other three existed.
	 */
	.prayer-bar__time {
		grid-row: auto;
		grid-template-rows: none;
		padding-block: 0.2rem;
	}

	/*
	 * Four columns, so the seven times break 4 + 3 with both rows on the same
	 * grid. Seven equal tracks would put SYURUK in 45px.
	 *
	 * The divider follows the stack: a rule down the side of a full-width block
	 * would sit in the margin pointing at nothing.
	 */
	.prayer-bar__times {
		grid-template-columns: repeat(4, minmax(0, 1fr));
		grid-template-rows: none;
		gap: var(--wp--preset--spacing--20) var(--wp--preset--spacing--10);
		padding-inline-start: 0;
		padding-block-start: var(--wp--preset--spacing--30);
		border-inline-start: 0;
		border-block-start: 1px solid rgb(var(--jmns-hairline-rgb) / 0.14);
	}

	.prayer-bar__time {
		grid-row: auto;
		grid-template-rows: auto auto;
	}
}

/* ================================================================= cards == */

/*
 * Card — design.md: white surface, 16px radius, 24px padding, soft elevation
 * that lifts on hover over 150ms.
 *
 * The border is kept underneath the shadow rather than replaced by it: a
 * shadow alone disappears in forced-colours mode and in print, and the card
 * would lose its boundary entirely.
 */
.jmns-card {
	background-color: var(--wp--preset--color--surface);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--r-card);
	box-shadow: var(--shadow-card);
	padding: var(--wp--preset--spacing--50);
	block-size: 100%;
	transition: box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}

.jmns-card:hover {
	box-shadow: var(--shadow-hover);
	transform: translateY(-2px);
}

.jmns-card__title {
	margin-block: 0 var(--wp--preset--spacing--20);
	font-size: var(--wp--preset--font-size--title-sm);
}

/*
 * Secondary ink, from the token that names secondary ink.
 *
 * This was `rgb(var(--jmns-shade-rgb) / 0.75)`, which is 75% of the shadow ink
 * composited against whatever is behind the card — a way of writing "grey" that
 * only produces grey while the card is white. Under `malam` the card is #1F2A25
 * and the shade is black, so the same declaration renders near-black text on a
 * near-black card. `text-secondary` is the colour this line was always after,
 * it is 5.28:1 here under `rasmi`, and it follows the scheme.
 */
.jmns-card__meta {
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--text-secondary);
}

.jmns-card-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
	gap: var(--wp--preset--spacing--40);
}

/* ================================================================ badges == */

/*
 * Gazette status. Colour is a secondary cue only: the badge always carries its
 * own text, so meaning survives colour-blindness, greyscale print and
 * high-contrast modes (§4.4 — nothing by colour alone).
 *
 * The two variants used to be the same badge. Both set
 * `color: var(--wp--preset--color--interactive)` and nothing else, so
 * "Diwartakan" and "Tidak Diwartakan" were pixel-identical apart from the word
 * — on an archive row that reads as a decoration rather than as a status, and
 * a scanning eye had to read six syllables to tell a gazetted ruling from one
 * that is not.
 *
 * They now differ in weight, not in hue: filled against outline. That survives
 * greyscale, and it survives being glanced at.
 */
/*
 * The tender badge is the same badge (ADR-057). "Terbuka" against "Tutup" is the
 * same kind of statement as "Diwartakan" against "Tidak Diwartakan" — a status
 * a reader must be able to take in without reading it, that a supplier will act
 * on, and that must survive greyscale — so it takes the same filled-against-
 * outline treatment rather than a second badge invented beside it.
 *
 * There is deliberately no third variant for "no closing date". A tender with no
 * stated deadline prints no badge at all; a grey chip reading "Tidak diketahui"
 * would occupy the same slot and say the department had considered the question.
 */
.warta-badge,
.tender-badge {
	display: inline-flex;
	align-items: center;
	padding: 0.15em 0.7em;
	border: 1px solid transparent;
	border-radius: var(--r-button);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.4;
}

/* Reversed-out ink on `interactive` is 8.31:1 here and no worse than 8.31:1
   under any of the five — see `--jmns-on-primary`, which is what makes that
   true when the fill stops being dark. */
.warta-badge--yes,
.tender-badge--open {
	background-color: var(--wp--preset--color--interactive);
	border-color: var(--wp--preset--color--interactive);
	color: var(--jmns-on-primary);
}

/*
 * Quiet, but not faint. This is the department's own classification carried by
 * 241 of the 433 rulings, not a missing value — a badge that looked provisional
 * would misrepresent it. `text-secondary` on white is 5.46:1 and the outline is
 * 3.64:1.
 */
.warta-badge--no,
.tender-badge--closed {
	background-color: var(--wp--preset--color--surface);
	border-color: var(--wp--preset--color--border-strong);
	color: var(--wp--preset--color--text-secondary);
}

/*
 * The gazette citation, beside the badge that asserts it.
 *
 * Inline, so it sits on the badge's line and wraps under it at 320px rather
 * than forcing the badge into its own row at every width. Not a second badge:
 * the pill is a status and this is a reference — giving it a border would make
 * the page look like it carried two statuses.
 *
 * The margin hangs off the adjacent-sibling selector rather than off `.warta-ref`
 * itself, because the citation can outlive the badge: a fatwa whose gazette
 * status nobody has recorded prints no pill (jmns_warta_badge()'s third state)
 * and would otherwise start the line one indent in from nothing.
 */
.warta-ref {
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--small);
	line-height: 1.4;
}

.warta-badge + .warta-ref {
	margin-inline-start: 0.5rem;
}

.warta-ref__sep {
	/* The dot is a separator and not a word; keep it off the reading weight. */
	color: var(--wp--preset--color--border-strong);
}

/* ================================================================ tables == */

.wp-block-table table {
	inline-size: 100%;
	border-collapse: collapse;
}

.wp-block-table th,
.wp-block-table td {
	padding: 0.6rem 0.75rem;
	border: 1px solid rgb(var(--jmns-hairline-rgb) / var(--jmns-a-rule));
	text-align: start;
}

.wp-block-table thead th {
	background-color: var(--wp--preset--color--text-primary);
	color: var(--wp--preset--color--background);
}

.wp-block-table tbody tr:nth-child(even) {
	background-color: var(--wp--preset--color--background);
}

/* Wide tables scroll inside their own container rather than the page. */
.wp-block-table {
	overflow-x: auto;
}

/*
 * The same table, when it came from the old site.
 *
 * Every table on this portal is a bare `<table>` — fifteen of them, none from
 * the block editor — so none of them matched the rules above and none of them
 * had a border, a header row or anywhere to scroll. The plugin puts a
 * `.jmns-table` container round each one (markup is its side of ADR-001); the
 * look is set here, beside the block editor's, because they should not differ.
 */
.jmns-table {
	overflow-x: auto;
	margin-block: var(--wp--preset--spacing--40);
	/*
	 * The prose measure by default; the wide one only when earned.
	 *
	 * Restated rather than inherited from `.entry-content > *`, because the
	 * wrapper the plugin adds is what that rule now caps, and a table that opts
	 * into `--wide` below has to be able to override it.
	 *
	 * An earlier cut sized the wrapper `fit-content` so every table took exactly
	 * the room it needed. It never shrank: the inner table's percentage width
	 * resolves against the wrapper, whose width was resolving against the table,
	 * and a cyclic dependency like that falls back to the full column.
	 */
	max-inline-size: var(--wp--style--global--content-size);
	/*
	 * The article's left edge, like everything else in the band. This was
	 * `auto`, which centred a three-column table 264px in while the paragraph
	 * introducing it began at the edge — the floating column the measure rule
	 * near the top of this file exists to prevent. A `--wide` table is the full
	 * width of the band and lands in the same place either way.
	 */
	margin-inline: 0;
	/*
	 * The container is focusable so it can be scrolled from a keyboard, which
	 * means it can also be focused — and a focus ring on a full-width box with
	 * no visible edge reads as an error state. It gets the ring only when the
	 * keyboard put it there.
	 */
	border-radius: var(--r-card, 12px);
}

.jmns-table:focus-visible {
	outline: 2px solid var(--wp--preset--color--interactive);
	outline-offset: 2px;
}

/*
 * Only a table with enough columns to need it, per the count in the plugin.
 * Wider than the column it sits in, so `margin-inline: auto` lines it up with
 * the page heading rather than the prose.
 */
.jmns-table--wide {
	max-inline-size: min(100%, var(--wp--style--global--wide-size));
}

.jmns-table > table {
	inline-size: 100%;
	border-collapse: collapse;
	/*
	 * A three-column table of month names was being squeezed to the width of
	 * its longest word and left floating in the middle of a 1200px page. Cells
	 * size to their content, so a table with nothing long in it has to be told
	 * to fill the space it was given.
	 */
	table-layout: auto;
}

.jmns-table th,
.jmns-table td {
	padding: 0.7rem 0.9rem;
	border: 1px solid rgb(var(--jmns-hairline-rgb) / var(--jmns-a-rule));
	text-align: start;
	vertical-align: top;
}

/*
 * Any `th`, not only one inside a `thead`.
 *
 * The Word-pasted tables have no `thead` — everything is in `tbody`, and the
 * header row is only a header because the plugin recognised it as one. Keying
 * the style off `thead` left those promoted rows looking like data.
 */
.jmns-table th {
	background-color: var(--wp--preset--color--text-primary);
	color: var(--wp--preset--color--background);
	font-weight: 600;
}

/* Only where there is a real header row to keep on one line. */
.jmns-table thead th {
	white-space: nowrap;
}

/*
 * Table text is table text.
 *
 * These cells hold `<p>` elements, because they were pasted from a document,
 * and a paragraph in the content area is set for reading prose — which put the
 * word "NEGARA" at 28px in a column too narrow to hold it, broken across two
 * lines mid-word. Inside a table the cell sets the size.
 */
.jmns-table :is(p, li) {
	margin-block: 0;
	font-size: 1rem;
	line-height: 1.45;
}

.jmns-table :is(p, li) + :is(p, li) {
	margin-block-start: 0.35rem;
}

.jmns-table tbody tr:nth-child(even) {
	background-color: var(--wp--preset--color--background);
}

/*
 * The numbering column is a counter, not data: it never needs more than the
 * three characters "100" and it should not take a share of the width.
 */
.jmns-table tbody td:first-child {
	inline-size: 1%;
	white-space: nowrap;
	color: var(--wp--preset--color--text-secondary);
	font-variant-numeric: tabular-nums;
}

/*
 * What a document link is about to hand over.
 *
 * Set quietly beside the label rather than in it: the format and size are for
 * the moment before a tap, not part of the link's name. It stays inside the
 * anchor so it is read out with the link — "Lihat / Muat Turun (PDF, 1.2 MB)" —
 * which is the whole point of putting it there.
 */
.jmns-doc-note {
	/*
	 * `inline-block`, not `text-decoration: none`.
	 *
	 * An underline propagates from the ancestor that declared it and cannot be
	 * switched off further down — the note came out underlined along with the
	 * label. A new block formatting context is what actually stops it, and it
	 * keeps the note from being split across two lines as a bonus.
	 */
	display: inline-block;
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--small, 0.875rem);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

/*
 * A month with no report yet.
 *
 * Said rather than left blank, because an empty cell is ambiguous — it reads
 * as "we forgot" just as easily as "there is nothing". Set quiet: in the 2026
 * table ten of the twelve rows say it, and they should not be the loudest
 * thing on the page.
 */
.jmns-none {
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--small, 0.875rem);
}

/*
 * The way out of a poster that is the whole article.
 *
 * 66 entries are a single 1024px image and nothing else, drawn at a third of
 * that on a phone. This is the link that opens the original — set quietly,
 * because it is an affordance rather than the content.
 */
.jmns-poster-open-line {
	margin-block: var(--wp--preset--spacing--20) var(--wp--preset--spacing--40);
	text-align: center;
}

.jmns-poster-open {
	display: inline-flex;
	align-items: center;
	min-block-size: 44px;
	padding-inline: var(--wp--preset--spacing--30);
	color: var(--wp--preset--color--interactive);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

/* ================================================================= forms == */

/*
 * Wrapped in :where() so it weighs nothing.
 *
 * These are defaults for an unstyled field, but as `input[type="search"]` they
 * are an element plus an attribute — which outranks any single class, so a
 * component that styles its own field silently lost. It cost the header search
 * a phantom border and the bot panel its own box model, and neither failed
 * loudly; the declarations were simply discarded.
 *
 * :where() drops the whole thing to zero specificity, so any component rule
 * beats it by existing and no component has to defend itself with an extra
 * ancestor. Nothing that overrode this before stops working — lowering a rule
 * only ever lets more things win.
 */
:where(input[type="text"], input[type="search"], input[type="email"], select, textarea) {
	min-block-size: 48px;
	padding: 0.75rem 1rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--r-input);
	background-color: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--text-primary);
	font-family: var(--wp--preset--font-family--body);
	/* Under 16px iOS zooms the page when the field takes focus. */
	font-size: max(1rem, 16px);
	transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}

label {
	display: inline-block;
	font-weight: 600;
}

/*
 * Same, with one deliberate exception: specificity is computed per selector in
 * a list, so `.button` keeps its own class weight while the bare elements drop
 * to zero. `.button` is a component someone opts into; `button` is whatever the
 * page happened to contain.
 */
:where(button, input[type="submit"]),
.button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.5rem;
	min-block-size: 48px;
	padding: 0.75rem 1.5rem;
	border: 1px solid transparent;
	border-radius: var(--r-button);
	/* `interactive` and `primary` are the same value since the 2026-08 palette
	   (#245746, white at 8.31:1). This keeps `interactive` because a button is
	   a control, which is what that token names. See DESIGN_SYSTEM.md §1. */
	background-color: var(--wp--preset--color--interactive);
	/*
	 * `--jmns-on-primary`, not `surface`. Reversed-out ink and the colour a card
	 * is made of are the same white today and mean different things: a scheme is
	 * entitled to warm its surfaces without warming the label on a green button,
	 * and `malam` is entitled to darken this one while its cards stay dark. Same
	 * class of defect as the `--jmns-on-dark` split above, and it costs nothing
	 * — it passes under all five either way, `malam` tightest at 7.27:1 with
	 * `surface` against 9.22:1 with this.
	 */
	color: var(--jmns-on-primary);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	line-height: 1.2;
	text-decoration: none;
	cursor: pointer;
	transition: background-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}

/*
 * The state goes in :where() for the same reason the resting style does
 * (ADR-052), and this one was doing visible damage.
 *
 * `button:hover` is an element plus a pseudo-class, so it outranked every ghost
 * control in the codebase — a caret, a pause button, a menu toggle, the bot's
 * close and retry — none of which declares a hover of its own because none of
 * them is a filled button. Pointing at any of them turned it into a solid dark
 * green block.
 *
 * The whole selector sits inside :where(), not just the element, so it weighs
 * nothing at all and a component wins by declaring anything — no reliance on
 * source order. `.button` keeps its weight: it opted in to being a button.
 */
:where(button:hover, input[type="submit"]:hover),
.button:hover {
	background-color: var(--wp--preset--color--interactive-hover);
	box-shadow: var(--shadow-card);
}

/*
 * Disabled outranks hover, deliberately and explicitly.
 *
 * `button[disabled]` is 0-1-1 and `.button:hover` is 0-2-0, so pointing at a
 * disabled `.button` filled it interactive green — a dead control that looks
 * live under the cursor, which is worse than one that looks live at rest. No
 * button is disabled at runtime today, so this was latent rather than visible;
 * it would have surfaced on the first form anyone adds.
 *
 * `.button[disabled]:hover` is 0-3-0, which settles it whatever else claims the
 * element.
 *
 * Ink on the grey, not white. WCAG exempts an inactive control from contrast,
 * but white measured 2.01:1 on this fill and was not readable by anyone; the
 * department's own ink is 6.56:1 and costs nothing. Disabled should read as
 * unavailable, not as illegible.
 */
button[disabled],
button[disabled]:hover,
.button[disabled],
.button[disabled]:hover,
.button[aria-disabled="true"],
.button[aria-disabled="true"]:hover {
	background-color: rgb(var(--jmns-shade-rgb) / 0.35);
	color: var(--wp--preset--color--text-primary);
	box-shadow: none;
	cursor: not-allowed;
}

/*
 * Secondary — design.md specifies it (white, green border, green text) and the
 * theme never built it, so every call to action on the site was a filled
 * primary. `interactive` carries both the border and the text: at 8.31:1 on
 * white it passes as text, and the same colour as a 1px border clears the 3:1
 * SC 1.4.11 asks of a control boundary.
 */
.button--secondary {
	background-color: var(--wp--preset--color--surface);
	border-color: var(--wp--preset--color--interactive);
	color: var(--wp--preset--color--interactive);
}

.button--secondary:hover {
	background-color: var(--wp--preset--color--interactive);
	color: var(--jmns-on-primary);
}

/* =============================================================== archive == */

/*
 * An archive headline is a heading that happens to be clickable.
 *
 * Nothing ever turned the browser's underline off here, so every title on the
 * news, penerbitan and pengumuman listings was set at 34px in the department's
 * green with a rule under every word — ten of them down a page, and on the
 * two-line titles twenty. The underline is doing no work: the whole line is the
 * link, it is the largest thing in its row, and it sits on a page whose only
 * other links are in the breadcrumb and the pagination.
 *
 * It comes back on hover and on focus, which is where a link has to say so.
 * Focus is not optional: keyboard users get no cursor to change, so the
 * underline plus the theme's ring is what tells them what will open.
 */
.entry-card .entry-title a {
	color: var(--wp--preset--color--text-primary);
	text-decoration: none;
}

.entry-card .entry-title a:hover,
.entry-card .entry-title a:focus-visible {
	color: var(--wp--preset--color--interactive);
	text-decoration: underline;
	text-decoration-thickness: 2px;
	text-underline-offset: 0.12em;
}

/* -------------------------------------------------- archive: filters --- */

/*
 * One search box and one year, above the list.
 *
 * A plain GET form on the archive's own URL. The alternative — filtering the
 * ten rows already rendered — would have told a reader searching the news that
 * "karnival" appears once, when it appears in five entries across nine pages.
 *
 * No panel, no ground. The finder on the fatwa archive sits on a surface
 * because it carries five facets and a count; two controls do not need a room
 * of their own.
 */
/*
 * The page head, stated rather than inherited.
 *
 * Both gaps at the top of an archive — breadcrumb to heading, heading to the
 * opening sentence — were the user-agent's `margin: 0.67em 0` on an h1, and
 * nothing in this theme had ever set them. Against a heading clamped between
 * 2rem and 2.5rem that means they measure 26.8px at 1440 and 21.4px at 390: the
 * page head breathes differently at every width, for no reason anyone chose.
 * Neither gap can be moved from the other end — the breadcrumb's own 12px
 * bottom margin and the description's 12px top margin both collapse into the
 * heading's larger one and never show — which is why setting `.jmns-crumbs`
 * alone would do nothing.
 *
 * So they are declared: 32px above the heading and 20px below it, the same at
 * every width. Both still collapse, and now the declared value is the one that
 * wins. This is the rhythm the department approved on the staff directory, and
 * it is stated here in the same terms so the two page heads agree.
 *
 * Deliberately on `.page-title` rather than on the archive alone. Search, 404,
 * the fatwa archive and the artikel series pages all print the same heading
 * under the same breadcrumb, and giving the four post-type archives one rhythm
 * while the other four page heads kept the user-agent's would be the defect
 * this rule exists to remove, moved somewhere less visible.
 *
 * Outside any width query on purpose: one rhythm at every viewport is the
 * point.
 */
.page-title {
	margin-block: var(--wp--preset--spacing--60) 1.25rem;
}

/*
 * The sentence under the heading.
 *
 * `the_archive_description()` prints the post type's own description, which is
 * the only place WordPress keeps archive copy — a post-type archive has no post
 * behind it to edit. It says what the list is before the controls ask what the
 * reader wants from it.
 */
.archive-description {
	max-inline-size: 45rem;
	margin-block-start: var(--wp--preset--spacing--30);
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--medium);
	line-height: 1.6;
}

.archive-description p {
	margin: 0;
}

.archive-filters {
	display: block;
	/*
	 * Air on both sides. The lead entry zeroes its own top padding so it sits
	 * tight under a header that used to end with the title — with the controls
	 * there instead, the first date landed directly beneath the search box and
	 * the filter bar read as part of the first story.
	 */
	margin-block: var(--wp--preset--spacing--60) var(--wp--preset--spacing--70);
}

/*
 * Closer on the publications archive, where a row of chips comes next.
 *
 * 40px is the gap between the controls and the first entry of a list, and the
 * chips are not an entry — they are the second half of the same control. At 40
 * they read as a row of filters somebody had parked above the results; at 20 the
 * search and the chips read as one bar, which is the rhythm the department
 * approved on the staff directory and the same 20px that file sets between its
 * own search and its own chips.
 *
 * Scoped to the archive rather than to `.archive-filters` generally: the other
 * four archives have a first entry under the controls, not a filter, and 40 is
 * right there.
 */
.post-type-archive-jmns_penerbitan .archive-filters {
	margin-block-end: 1.25rem;
}

.archive-filters__form {
	display: flex;
	flex-wrap: wrap;
	align-items: end;
	gap: var(--wp--preset--spacing--30);
	/* The same 720px the rows below are held to. Spanning the full 1,200 put the
	   controls on one edge and everything they control on another. */
	max-inline-size: 45rem;
}

.archive-filters__field {
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
	margin: 0;
	min-inline-size: 0;
}

.archive-filters__field label {
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

.archive-filters .archive-filters__search {
	/* States its own box, per ADR-052: the base field rule is in :where() and
	   weighs nothing, so anything left unsaid here is left unstyled. */
	inline-size: 100%;
	min-inline-size: 0;
	/* Both, because the base rule's 44px floor sits in :where() while its 0.75rem
	   block padding does not — stating the height alone left the field 46px and
	   the select beside it 48. */
	block-size: 44px;
	min-block-size: 44px;
	padding-block: 0;
	padding-inline: var(--wp--preset--spacing--30);
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--r-pill);
	background-color: var(--wp--preset--color--surface);
	font-size: max(1rem, 16px);
}

/* The search takes the room; the year takes what it needs. */
.archive-filters__field:not(.archive-filters__field--year) {
	flex: 1 1 20rem;
}

.archive-filters .archive-filters__year {
	block-size: 44px;
	min-block-size: 44px;
	padding-block: 0;
	padding-inline: var(--wp--preset--spacing--30);
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--r-input);
	background-color: var(--wp--preset--color--surface);
	font-size: max(1rem, 16px);
}

.archive-filters__actions {
	display: flex;
	align-items: center;
	gap: var(--wp--preset--spacing--30);
	margin: 0;
}

.archive-filters .button {
	min-block-size: 44px;
	padding-block: 0;
}

/* The same 44px as the button beside it: it sat at exactly the 24px floor, and
   a control that only just clears a minimum is one nobody chose the size of. */
.archive-filters__reset {
	display: inline-flex;
	align-items: center;
	min-block-size: 44px;
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--small);
}

/* Every control full width on a phone, per §24 — a 44px target beside a 44px
   target on a 342px row leaves neither of them a label that fits. */
@media (max-width: 30rem) {
	.archive-filters__form > * {
		inline-size: 100%;
	}

	.archive-filters__actions {
		justify-content: space-between;
	}

	.archive-filters .button {
		flex: 1 1 auto;
	}
}

/*
 * An editorial list, not a stack of cards.
 *
 * A rule between entries and nothing else: no ground, no border, no shadow. The
 * separation a chronology needs is the smallest thing that will do it, and ten
 * white boxes on a warm ground is the look this archive was asked not to have.
 */
.entry-card {
	position: relative;
	padding-block: var(--wp--preset--spacing--60);
	border-block-start: 1px solid rgb(var(--jmns-primary-rgb) / 0.1);
}

.entry-card:first-of-type {
	border-block-start: 0;
}

/* The stretched half of the pattern: the headline's link covers the row, so the
   excerpt and the photograph go where the headline goes. */
.entry-card .entry-title a::after {
	content: "";
	position: absolute;
	inset: 0;
}

/*
 * 720px of measure on a 1200px page.
 *
 * The excerpts ran the full container — 1,200px of 16px type is about 150
 * characters a line, which is roughly twice what anyone tracks back across
 * without losing the row.
 */
.entry-card__text {
	max-inline-size: 45rem;
}

.entry-card__meta {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.35rem 0.75rem;
	margin-block-end: var(--wp--preset--spacing--20);
}

/*
 * The date leads and is set as a marker rather than as a sentence: 13px,
 * uppercase, spaced. It is the thing a reader scans a chronology by, and at
 * 16px in sentence case under the headline it read as the first line of the
 * story.
 */
.entry-card .entry-meta {
	margin: 0;
	color: var(--wp--preset--color--text-secondary);
	font-size: 0.8125rem;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

.entry-card .entry-title {
	/* 22px. The archive heading is 40 and these are its contents; at 34 the ten
	   of them competed with it and with each other. */
	margin-block: 0 var(--wp--preset--spacing--20);
	font-size: 1.375rem;
	line-height: 1.3;
}

.entry-card .entry-summary {
	color: var(--wp--preset--color--text-secondary);
}

.entry-card .entry-summary p {
	margin: 0;
	font-size: 0.9375rem;
	line-height: 1.6;
	/* Three lines. The whole excerpt stays in the markup for anything reading
	   the page rather than looking at it. */
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 3;
	overflow: hidden;
}

.entry-card__go {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	margin-block-start: var(--wp--preset--spacing--30);
	color: var(--wp--preset--color--interactive);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

.entry-card__arrow {
	transition: transform var(--t-fast) var(--ease);
}

.entry-card:hover .entry-card__arrow {
	transform: translateX(3px);
}

/* ----------------------------------------------------- archive: lead --- */

/*
 * The newest entry, on the first page.
 *
 * Same parts, more of them visible: the flag says why it is bigger, the title
 * takes the size the others gave up, and the picture beside it is half again as
 * wide as the rows'. It used to be that the lead was the only row with a
 * photograph at all, which did most of the work of saying it was the lead; now
 * that every row has one, the hierarchy has to be carried by size alone — 28px
 * against the rows' 22, a 21rem column against their 14. Still a lead story,
 * not a hero.
 *
 * `.entry-card.entry-card--featured`, not `.entry-card--featured`. The row rule
 * `.entry-card { padding-block: … }` in the phone query below is also two
 * classes deep at (0,1,0), is declared later, and won — so this card had 0px of
 * top padding at 1440 and 24px at 390, and the "sits tight under the header"
 * the zero exists for only happened on a desktop. Doubling the class takes it
 * to (0,2,0) and the intent holds at both widths, the same fix the `.entry-title`
 * comment in that query describes.
 */
.entry-card.entry-card--featured {
	padding-block-start: 0;
}

.entry-card__flag {
	margin: 0;
	color: var(--wp--preset--color--interactive);
	font-size: 0.75rem;
	font-weight: 700;
	letter-spacing: 0.1em;
	text-transform: uppercase;
}

/* Doubled, for the reason given above `.entry-card.entry-card--featured`: the
   row rule below is the same (0,2,0) and is declared first only by luck. */
.entry-card.entry-card--featured .entry-title {
	font-size: 1.75rem;
}

.entry-card--featured .entry-summary p {
	font-size: 1rem;
	-webkit-line-clamp: 3;
}

/*
 * Text on the measure, picture on the right rail.
 *
 * The text column is a `1fr` that absorbs every bit of shrinking, so the row
 * can never be wider than its container — a fixed `minmax(0, 45rem)` track
 * beside a fixed picture track does not shrink, and at 900px of viewport the
 * two of them would have run 124px past the shell. The 45rem measure the
 * excerpts are held to is kept on `.entry-card__text` instead of being taken
 * off in here, which is what stops a row's excerpt from running 944px wide the
 * moment a picture is put beside it. The daylight that leaves between the text
 * and the picture is the point: one left edge for the reading, one right edge
 * for the pictures.
 *
 * The picture column is clamped rather than a percentage so the square it makes
 * has a height somebody chose — 224px on a row, 336px on the lead — and so the
 * `sizes` attribute in content-excerpt.php can name it exactly. At the narrow
 * end of this query it comes down to 144px rather than squeezing the headline
 * into a third of the row.
 */
@media (min-width: 48rem) {
	.entry-card--illustrated {
		display: grid;
		grid-template-columns: minmax(0, 1fr) clamp(9rem, 20%, 14rem);
		gap: var(--wp--preset--spacing--60);
		align-items: start;
	}

	.entry-card.entry-card--featured.entry-card--illustrated {
		grid-template-columns: minmax(0, 1fr) clamp(12rem, 30%, 21rem);
	}

	.entry-card--illustrated .entry-card__media {
		margin-block-start: 0;
	}
}

.entry-card__media {
	margin-block-start: var(--wp--preset--spacing--40);
}

/*
 * A square slot, because this library is square.
 *
 * The slot was 16:10, which is a shape almost nothing here is: of the 286
 * featured images across the four archives, 11 are 1.6 or wider, 15 sit between
 * 1.2 and 1.6, and 260 — 91% — are square or portrait. Cropping those 260 into
 * 16:10 discards a median 37.5% of their height, and on a taudhih poster or a
 * penerbitan cover that height is the title band at the top and the department's
 * crest at the bottom. It is the same defect the artikel poster grid was moved
 * off 1:1.414 to fix.
 *
 * So the slot is 1:1 — the shape 129 of these images already are exactly, which
 * they fill edge to edge with nothing cropped at all. That is most of what
 * moving off 16:10 was for.
 *
 * Everything fills the slot. Two other arrangements were built and rejected by
 * the department: cropping only the 26 landscape photographs and containing the
 * rest, and containing every image. Contained, a listing is a column of
 * pillarboxed rectangles floating on ground, and the ground reads as a fault
 * rather than as a choice. Filled, the row is a row of pictures.
 *
 * What it costs, so nobody has to re-derive it: the 129 square images lose
 * nothing, the 54 at 1.193 lose about 16% off the sides, and the 77 A4 covers
 * lose 29% off their height — which on a cover is the publisher's band and the
 * department's crest. That is a known trade, taken deliberately.
 *
 * The ground stays underneath. With `cover` it is only ever seen while the file
 * is still arriving, and a sage box is a better wait than a white one.
 */
.entry-card__image {
	display: block;
	inline-size: 100%;
	block-size: auto;
	aspect-ratio: 1 / 1;
	object-fit: cover;
	background-color: var(--wp--preset--color--surface-tint);
	border-radius: var(--r-card);
}

/*
 * A step down on a phone, where the archive heading is 32px rather than 40 and
 * a 28px lead title sits almost on top of it. The rows come down with it so the
 * gap between lead and row stays the same two points it is on a desktop.
 */
@media (max-width: 48rem) {
	.entry-card .entry-title {
		font-size: 1.25rem;
	}

	/*
	 * `.entry-card.entry-card--featured`, not `.entry-card--featured`. Both are
	 * two classes, the row rule is declared second, and at equal specificity the
	 * later one wins — so the lead title silently fell to the row size and the
	 * hierarchy this whole change is for disappeared below 768px.
	 */
	.entry-card.entry-card--featured .entry-title {
		font-size: 1.5rem;
	}

	.entry-card {
		padding-block: var(--wp--preset--spacing--50);
	}

	/*
	 * A thumbnail beside the headline, not above it.
	 *
	 * Stacked full width, a square picture on a 390px screen is 342px tall, and
	 * ten of them turn one page of an archive into 8,000px of scrolling where
	 * the reader sees roughly one headline at a time. Beside the text at 6rem it
	 * is a list again: the picture says which entry this is and the headline
	 * still leads.
	 *
	 * The `1fr` is what keeps this from overflowing a 342px row, exactly as in
	 * the desktop query.
	 */
	.entry-card--illustrated {
		display: grid;
		grid-template-columns: minmax(0, 1fr) 6rem;
		gap: var(--wp--preset--spacing--40);
		align-items: start;
	}

	.entry-card--illustrated .entry-card__media {
		margin-block: 0;
	}

	/*
	 * The lead keeps the full-width picture, above the headline.
	 *
	 * It is the one entry on the page whose picture is worth 342px, and it is
	 * now the only structural difference between the lead and the rows on a
	 * phone — a 24px title against 20px is not, on its own, a hierarchy anybody
	 * reads. Stacked, it had followed the read-more line, so the lead opened
	 * with a wall of type and its picture arrived after the reader had already
	 * decided.
	 *
	 * Visual order only. The image carries empty alt and is out of the
	 * accessibility tree, and the sole focusable thing in the row is the
	 * headline, so nothing that reads or tabs the page sees this move.
	 */
	.entry-card.entry-card--featured.entry-card--illustrated {
		display: flex;
		flex-direction: column;
	}

	.entry-card.entry-card--featured .entry-card__media {
		order: -1;
		margin-block: 0 var(--wp--preset--spacing--40);
	}
}

/*
 * The finder replaces .archive-search and .archive-filters, which are gone.
 *
 * Measured at 390px, the archive put three search affordances and 1,325px of
 * chrome above the first ruling: the masthead's site search, then a second
 * search field under the h1, then a 451px filter fieldset. This is one control
 * that does both jobs, with the facets folded away.
 *
 * 32px under the opening sentence, 16px above the first ruling.
 *
 * The rhythm of the whole block is declared from here down in absolute values
 * rather than in spacing presets, because the five gaps the department signed
 * off — 32 / 20 / 20 / 24 / 16 — are the ones already approved on the staff
 * directory, and the preset scale steps 16 / 24 / 32 with nothing at 20. Two
 * near-identical rhythms on one portal is worse than one stated in pixels.
 */
.fatwa-finder {
	/* <search> is an unknown element in browsers that predate it, and an unknown
	   element is inline. */
	display: block;
	margin-block: 2rem 1rem;
}

/*
 * No surface at all any more.
 *
 * It was a white card with a border, a radius, a shadow and 16px of padding
 * wrapped around one field and a disclosure — which read as an admin form
 * bolted to the top of a public archive, and gave the filters the same visual
 * weight as the search they are subordinate to. The controls carry their own
 * borders; the panel behind them was carrying nothing but weight.
 *
 * The count sits inside <search> because it is the search's own status — but it
 * describes the list below, not the controls above, so it stays on the page
 * ground with the results.
 */
.fatwa-finder__form {
	padding: 0;
	background: none;
	border: 0;
	border-radius: 0;
	box-shadow: none;
}

.fatwa-finder__label {
	display: block;
	margin-block: 0 var(--wp--preset--spacing--20);
}

.fatwa-finder__control {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--20);
	align-items: center;
}

/*
 * States its own box model, per ADR-052.
 *
 * The base `input[type="search"]` rule sits in :where() and weighs nothing, so
 * a component that assumed it would inherit a border, a radius and a 48px
 * height would silently get none of them. `min-inline-size: 0` is the other
 * half: a search input's intrinsic width is around 180px and a flex item will
 * not shrink below it by default, which is what pushes the submit button onto
 * its own row at 360px.
 */
.fatwa-finder__input {
	flex: 1 1 11rem;
	min-inline-size: 0;
	min-block-size: 48px;
	padding: 0.75rem 1rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--r-input);
	background-color: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--text-primary);
	font-family: var(--wp--preset--font-family--body);
	/* Under 16px iOS zooms the page when the field takes focus. */
	font-size: max(1rem, 16px);
	transition: border-color var(--t-fast) var(--ease);
}

.fatwa-finder__input:focus {
	border-color: var(--wp--preset--color--interactive);
}

.fatwa-finder__submit {
	flex: 0 0 auto;
}

/* Quiet: it undoes something rather than doing it, so it must not compete with
   Cari. 44px on both axes even though the label makes it wider (SC 2.5.8). */
.fatwa-finder__clear {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	min-block-size: 44px;
	min-inline-size: 44px;
	padding-inline: 0.75rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--r-button);
	background-color: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--interactive);
	font-family: var(--wp--preset--font-family--body);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.2;
	cursor: pointer;
}

.fatwa-finder__clear:hover {
	background-color: var(--wp--preset--color--surface-tint);
	color: var(--wp--preset--color--interactive-hover);
}

/* 20px under the search row: the second of the five approved gaps. */
.fatwa-finder__facets {
	margin-block-start: 1.25rem;
}

/*
 * The summary is the disclosure's button, so it is sized like one — a bare
 * <summary> is a 20px line box, which fails SC 2.5.8 exactly the way the
 * pagination did. list-item display is kept so the browser's own open/closed
 * marker survives; it is the only thing on the control that shows state
 * without colour.
 *
 * `fit-content`, not the full row. A summary is display:block by default, so
 * this one ran the whole width of the card — 308px at 390px wide, which put its
 * right-hand end underneath the chat launcher fixed at the corner of the
 * viewport. Measured: the launcher occupies 310–374 x 780–828 and the summary
 * ran 41–349 x 788–834, so the last 39px of a control were covered by another
 * control. Sized to its own label it stops at 131px and nothing reaches it.
 *
 * 600, not 700. The search is the primary utility on this page and the filters
 * are subordinate to it; at bold the disclosure was the heaviest text in the
 * block, heavier than the "Cari" button's own label.
 */
.fatwa-finder__facets-summary {
	display: list-item;
	list-style-position: inside;
	padding-block: 0.6rem;
	padding-inline: 0.25rem;
	min-block-size: 44px;
	border-radius: var(--r-button);
	color: var(--wp--preset--color--interactive);
	font-weight: 600;
	cursor: pointer;
}

.fatwa-finder__facets-summary:hover {
	color: var(--wp--preset--color--interactive-hover);
}

.fatwa-finder__row {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--30);
	align-items: flex-end;
	padding-block-start: var(--wp--preset--spacing--20);
}

.fatwa-finder__field {
	display: flex;
	flex-direction: column;
	gap: 0.25rem;
	flex: 1 1 12rem;
	margin: 0;
}

.fatwa-finder__field label {
	font-size: var(--wp--preset--font-size--small);
}

/* Same reason as the input: the base select rule weighs nothing (ADR-052). */
.fatwa-finder__select {
	inline-size: 100%;
	min-block-size: 48px;
	padding: 0.75rem 1rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: var(--r-input);
	background-color: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--text-primary);
	font-family: var(--wp--preset--font-family--body);
	font-size: max(1rem, 16px);
}

/* Provenance, not decoration: it says whose classification the badge carries,
   so it takes the whole row under the fields rather than sitting beside one. */
.fatwa-finder__note {
	flex: 1 1 100%;
	margin: 0;
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--small);
}

/*
 * .fatwa-finder__actions and .fatwa-finder__reset used to sit here, dressing
 * the "Set semula tapisan" link inside the disclosure. That link is now
 * "Kosongkan semua" in the chip row below, so both rules styled nothing.
 */

/* ------------------------------------------------------- active filters --- */

/*
 * What the reader narrowed the list to, on the page rather than folded inside
 * the disclosure that did the narrowing.
 *
 * Subtle on purpose (§4): outlined pills in the two greys the page already
 * uses, no fill and no accent colour. Three coloured chips over a text archive
 * would be the loudest thing on the page and they are the least important —
 * their job is to be readable and removable, not to be noticed.
 *
 * 20px under the filters, 24px above the count.
 */
.fatwa-chips {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--wp--preset--spacing--20) var(--wp--preset--spacing--30);
	margin-block-start: 1.25rem;
}

.fatwa-chips__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--20);
	margin: 0;
	padding: 0;
	list-style: none;
}

.fatwa-chips__item {
	margin: 0;
}

/*
 * The whole pill is the link, so the tap target is the chip and not the 12px
 * glyph inside it. 36px tall with 8px between neighbours clears SC 2.5.8's
 * 24px comfortably; the 44px the standalone controls on this page use would
 * make a row of removable filters heavier than the filters themselves.
 */
.fatwa-chip {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
	min-block-size: 36px;
	padding-inline: 0.75rem;
	border: 1px solid var(--wp--preset--color--border-strong);
	border-radius: 999px;
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
	line-height: 1.3;
	text-decoration: none;
	transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

.fatwa-chip:hover,
.fatwa-chip:focus-visible {
	background-color: var(--wp--preset--color--surface-tint);
	color: var(--wp--preset--color--text-primary);
	text-decoration: none;
}

/* Larger than the label it follows, because it is the affordance: at the
   label's 14px the multiplication sign is a speck. */
.fatwa-chip__remove {
	font-size: 1.125rem;
	line-height: 1;
}

.fatwa-chips__reset {
	display: inline-flex;
	align-items: center;
	min-block-size: 36px;
	color: var(--wp--preset--color--interactive);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

/* role="status": it is read on change, so it must also be readable at rest.
   24px below the chips — or below the filters, on a page with no chips. */
.fatwa-finder__status {
	margin-block: 1.5rem 0;
	color: var(--wp--preset--color--text-secondary);
	font-weight: 600;
}

/* Appended by fatwa-finder.js under the results, in place of the server
   pagination it hides. */
.fatwa-more {
	margin-block-start: var(--wp--preset--spacing--40);
	text-align: center;
}

/*
 * No gap. The rule between two rulings is the separator and the rows carry
 * their own padding, so a grid gap on top of both put the divider adrift in the
 * middle of 16px of nothing instead of against the rows it divides.
 */
.fatwa-list,
.tender-list {
	display: grid;
	gap: 0;
}

/*
 * 16px above the first row, which is the rhythm the department approved on the
 * staff directory and on the fatwa archive: crumbs to h1 32, h1 to the sentence
 * under it 20, and 16 to the top of the list.
 *
 * Declared on the tender list alone because the fatwa archive has its finder in
 * between and takes that spacing from `.fatwa-finder`. Nothing sits between the
 * heading and the first tender.
 */
.tender-list {
	margin-block-start: var(--wp--preset--spacing--40);
}

.fatwa-card,
.tender-card {
	padding-block-end: var(--wp--preset--spacing--30);
	border-block-end: 1px solid rgb(var(--jmns-hairline-rgb) / var(--jmns-a-rule));
}

/*
 * The archive row. Two classes deep so it beats the shared .fatwa-card above
 * without depending on source order — the homepage variants next to it are
 * `.fatwa-card--featured` at the same weight, and this codebase has been bitten
 * by that tie before.
 *
 * The tender archive is the same row and takes the same rule rather than a copy
 * of its values (ADR-057). Both are reference lists of documents somebody scans
 * for one line of status — the department approved this treatment for fatwa and
 * asked for tenders to match it, and two rules with the same numbers in them
 * would agree today and drift the first time either is tuned.
 *
 * 16px top and bottom, from 0 and 12. §6 asked for 14–18: the list was 12px of
 * padding plus a 16px grid gap, which is 28px between one ruling and the next
 * with all of it below the row — so the divider sat closer to the title above
 * it than to the one below, and the block a reader's eye had to hold together
 * was not the block the spacing described. Symmetrical padding makes the row
 * the unit.
 *
 * The 12px of inline padding is what the hover ground needs to not start at the
 * first glyph; the negative margin puts the text back on the column edge, so
 * the measure is unchanged and only the tint is wider. At 390px the row runs
 * 12–378 inside a 390 viewport.
 */
.fatwa-list .fatwa-card,
.tender-list .tender-card {
	position: relative;
	padding-block: 1rem;
	padding-inline: var(--wp--preset--spacing--30);
	margin-inline: calc(var(--wp--preset--spacing--30) * -1);
	border-block-end: 1px solid rgb(var(--jmns-hairline-rgb) / var(--jmns-a-rule));
	transition: background-color var(--t-fast) var(--ease);
}

/*
 * The whole row is the target, not the title.
 *
 * Same stretched pseudo-element the news entries and the homepage rulings use:
 * the accessible name stays the ruling's title, there is one tab stop per row
 * rather than one per element in it, and the heading stays a heading. Nothing
 * else in an archive row is interactive — the badge, the scan count and the
 * date are all plain text — so there is nothing for the overlay to swallow.
 */
.fatwa-list .fatwa-card__title a::after,
.tender-list .tender-card__title a::after {
	content: "";
	position: absolute;
	inset: 0;
}

/*
 * Subtle ground and an accent on the title, and nothing else. §8 rules out
 * elevation: a card that lifts turns a reference list into a browsing surface,
 * which is the pattern this archive was explicitly asked not to become.
 */
.fatwa-list .fatwa-card:hover,
.tender-list .tender-card:hover {
	background-color: var(--wp--preset--color--surface-tint);
}

.fatwa-list .fatwa-card:hover .fatwa-card__title a,
.tender-list .tender-card:hover .tender-card__title a {
	color: var(--wp--preset--color--interactive);
}

/* The ring belongs to the row, because the row is what the link covers. */
.fatwa-list .fatwa-card:focus-within,
.tender-list .tender-card:focus-within {
	background-color: var(--wp--preset--color--surface-tint);
}

.fatwa-list .fatwa-card__title a:focus-visible,
.tender-list .tender-card__title a:focus-visible {
	outline: var(--jmns-focus-width) solid var(--wp--preset--color--interactive);
	outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
	.fatwa-list .fatwa-card,
	.tender-list .tender-card {
		transition: none;
	}
}

/*
 * The same card in a homepage grid. The archive's divider is what separates one
 * row from the next; inside a bounded card it would draw a line under the
 * excerpt for no reason, so the card surface wins on both padding and border.
 */
.fatwa-card.jmns-card {
	padding: var(--wp--preset--spacing--50);
	border-block-end: 1px solid var(--wp--preset--color--border);
}

.fatwa-card__meta,
.tender-card__meta {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--20);
	align-items: center;
	margin-block-end: 0.35rem;
}

/*
 * What the ruling actually is, before the tap.
 *
 * .fatwa-card__number used to live here and styled nothing: it rendered from
 * `jmns_no_fatwa` meta, which exists on none of the 433 rulings. This is the
 * line that took its place, and it carries something every card has — 403 of
 * the 431 published fatwa are scanned pages rather than text.
 */
.fatwa-card__format,
.fatwa-card__date,
.tender-card__date,
.tender-card__ref {
	font-size: var(--wp--preset--font-size--small);
	font-variant-numeric: tabular-nums;
	color: var(--wp--preset--color--text-secondary);
}

/*
 * The reference number is a single unbreakable token —
 * "MUFTI.SEL/BKP/02/ICT/26" — and the archive has already been pushed off screen
 * once by one of those: a publication titled with a raw URL rendered 869px wide
 * at 390px. The same defect, the same fix, before it happens again.
 *
 * Only four of the thirteen carry a reference; the rest print nothing here.
 */
.tender-card__ref {
	overflow-wrap: anywhere;
}

.fatwa-card__title,
.tender-card__title {
	margin-block: 0.25rem;
	font-size: var(--wp--preset--font-size--title-sm);
}

/*
 * No underline, matching every other card on the homepage. This section was
 * built before the others and never got the rule, so the global `a` underline
 * landed on it — fatwa was the only row whose titles were underlined, which
 * made it read as a different kind of object beside berita, taudhih and the
 * posters.
 *
 * Not an SC 1.4.1 concern: that asks colour alone not carry meaning for links
 * in running prose. These are headings inside a bordered card that lifts on
 * hover and takes a pointer — the card is the affordance, and the underline
 * returns on hover like its neighbours.
 */
.fatwa-card__title a,
.tender-card__title a {
	color: var(--wp--preset--color--text-primary);
	text-decoration: none;
}

.fatwa-card__title a:hover,
.tender-card__title a:hover {
	color: var(--wp--preset--color--interactive);
	text-decoration: underline;
}

/*
 * The archive row's title, a step up from the shared card's.
 *
 * 24px at 1440 against 22, and 21px at 390 against 20 — the 1–2px §7 asked for,
 * not a size change anyone would call a redesign. It is the one thing on the
 * row a reader is scanning for, and at 22px it sat between a 40px h1 and a 14px
 * badge without clearly belonging above either.
 *
 * Its own clamp rather than the `title-sm` preset, because the preset tops out
 * at 22px; 1.5rem flat would be 24px on a phone as well, and the median fatwa
 * title is 71 characters, which is four lines in a 342px column. Semibold, not
 * bold: 20 of these in a column at 700 is a wall.
 *
 * Line-height 1.35, up from 1.25. These titles wrap — most of them do — and a
 * ruling's title running three tight lines is harder to read than the same
 * three lines with air in them.
 */
.fatwa-list .fatwa-card__title,
.tender-list .tender-card__title {
	margin-block: 0.25rem 0;
	font-size: clamp(1.3125rem, 0.9vw + 1.05rem, 1.5rem);
	font-weight: 600;
	line-height: 1.35;
}

/*
 * A closed tender is quieter, and it is quieter by one step of colour rather
 * than by opacity: these are procurement records and they stay legible and
 * printable. `text-secondary` is 5.46:1 on white, which clears AA at this size
 * with room to spare, so "past" reads as past without reading as disabled.
 *
 * **Both rules are needed, and the order matters.** `.tender-card--closed
 * .tender-card__title a` and `.tender-card__title a:hover` are both 0-2-1, so
 * whichever is written last wins the tie — without the second rule here, a
 * closed row would be the only row on the site that does not answer the pointer.
 */
.tender-card--closed .tender-card__title a {
	color: var(--wp--preset--color--text-secondary);
}

.tender-list .tender-card--closed:hover .tender-card__title a,
.tender-card--closed .tender-card__title a:hover {
	color: var(--wp--preset--color--interactive);
}

/*
 * Two lines, per §11. It is the third thing on the row and it must not be able
 * to push the next ruling off the screen: 393 of the 431 rulings produce a
 * summary and some of them run to a paragraph.
 *
 * Nothing reserves space when there is none — content-fatwa.php omits the
 * element rather than printing an empty one, so a row with no summary is
 * simply shorter than a row with one. Equal row heights were considered and
 * rejected: the archive is 431 rulings of unequal substance and padding them
 * to match would add empty space to hundreds of rows to tidy a few.
 */
.fatwa-list .fatwa-card__excerpt {
	/*
	 * 720px of measure inside a 1200px row, the same cap .entry-card__text
	 * takes and for the same reason: two clamped lines across the full column
	 * is about 300 characters of 15px type, which is not a summary a reader
	 * scans, it is a paragraph they have to track back across twice. The title
	 * above it keeps the whole width — it is set large enough that the line
	 * count stays low on its own.
	 */
	max-inline-size: 45rem;
	margin-block-start: 0.375rem;
	color: var(--wp--preset--color--text-secondary);
}

.fatwa-list .fatwa-card__excerpt p {
	margin: 0;
	font-size: 0.9375rem;
	line-height: 1.55;
	/* The whole summary stays in the markup for anything reading the page
	   rather than looking at it. */
	display: -webkit-box;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 2;
	overflow: hidden;
}

/* ------------------------------------------------- fatwa: the lead row --- */

/*
 * One ruling set larger, three beside it.
 *
 * Three identical cards filed the department's primary output as one more
 * content row between news and posters. The newest ruling takes the wider
 * column with its summary and a call to action; the three behind it are a list.
 *
 * Single column until 60rem. Below that the featured card and a stacked list
 * side by side leave both too narrow for a 71-character title.
 */
/*
 * The lead ruling across the top, the three behind it in a row underneath.
 *
 * Tried as two columns first — featured left, list right — and the heights
 * cannot be made to agree: 403 of the 431 rulings are gazette scans with no
 * text to summarise, so the featured card is usually a status, a date and a
 * title, and it cannot fill a column beside three cards. Stretched it had a
 * 300px hole in it; sized to its content it left the same hole in the band.
 * Full width, the size difference is the emphasis and there is no second
 * column to reconcile.
 */
.fatwa-lead {
	display: grid;
	gap: var(--wp--preset--spacing--50);
}

.fatwa-lead__list {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
	gap: var(--wp--preset--spacing--40);
	margin: 0;
	padding: 0;
	list-style: none;
}

.fatwa-lead__item {
	display: flex;
}

.fatwa-lead__item > * {
	flex: 1 1 auto;
}

/*
 * The card is the target, not just the title. `position: relative` is the half
 * of the stretched-link pattern that lives on the card; the other half is the
 * pseudo-element on the title's own anchor below.
 */
.fatwa-card--featured,
.fatwa-card--compact {
	position: relative;
	display: flex;
	flex-direction: column;
}

/*
 * Sized to its content, not stretched to the list beside it. 403 of the 431
 * rulings are gazette scans with no text to summarise, so the featured card is
 * usually a status, a date, a title and the link — and stretched to the height
 * of three cards that left roughly 300px of nothing above the call to action.
 * An uneven pair of columns reads as two things; a card with a hole in it reads
 * as broken.
 */
/*
 * Doubled selector, for the same reason `.jmns-card.taudhih-card` used to be:
 * `.jmns-card` sets padding, background and border at this same specificity and
 * is declared later in this file, so a single class here lost the padding and
 * the border colour silently — measured at 24px when the rule below asks for
 * 40. Specificity, not `!important`.
 */
.jmns-card.fatwa-card--featured {
	/*
	 * Overrides `.jmns-card`'s `block-size: 100%`, which is right in the
	 * equal-height grids it was written for and wrong here: this card is alone
	 * in its column, so 100% means the height of the three cards beside it.
	 */
	block-size: auto;
	/* §13: increase the vertical padding. 2.5rem, up from 2rem. */
	padding: var(--wp--preset--spacing--70);
	/*
	 * White, and that is the whole correction.
	 *
	 * This card was `surface-tint` on the reasoning that a ground would set it
	 * apart from the three plain cards below it. The Fatwa band is `surface-tint`
	 * too, so the ground was the same colour as the page behind it: the featured
	 * ruling rendered as a faintly outlined strip while the three secondary
	 * cards sat on it as crisp white panels with borders and shadow. The
	 * hierarchy was not weak, it was inverted — the three cards looked more
	 * substantial than the one they are subordinate to.
	 *
	 * On a sage band the differentiating surface is white, and white is also the
	 * right idea: §13 asks this to "feel like an official ruling, not a
	 * notification", and a ruling is a document, not a highlight. The step is
	 * carried by what §13 actually lists — the band's full width, 28px against
	 * 16, more padding, and a border a shade stronger than the cards below.
	 */
	background-color: var(--wp--preset--color--surface);
	border-color: var(--wp--preset--color--border-strong);
}

@media (min-width: 48rem) {
	/*
	 * Title and call to action side by side once there is room: the title is the
	 * thing, and on a wide band it would otherwise run to a single short line
	 * with the link stranded under it.
	 */
	.fatwa-card--featured {
		display: grid;
		grid-template-columns: minmax(0, 1fr) auto;
		column-gap: var(--wp--preset--spacing--60);
		align-items: end;
	}

	.fatwa-card--featured .fatwa-card__meta {
		grid-column: 1 / -1;
	}

	/*
	 * §13 puts the gazette status at one end of the line and the date at the
	 * other, which is how the ruling itself is headed. Together at the left they
	 * read as one run of small print.
	 */
	.fatwa-card--featured .fatwa-card__date {
		margin-inline-start: auto;
	}

	.fatwa-card--featured .fatwa-card__cta {
		padding-block-start: 0;
		white-space: nowrap;
	}
}

.fatwa-card--featured .fatwa-card__title {
	/*
	 * 24-26px. V3 §13 asked for 24-28 and the final review's §12 and §23 narrow
	 * it to 22-26, so the top of `title-md` is two pixels over. Capped rather
	 * than replaced: the preset still supplies the fluid step from 24, and the
	 * cap only bites at the widest end where it used to reach 28.
	 *
	 * Line-height stays up, which §13 asked for by name: a title this size set
	 * at 1.25 was tighter than the 16px cards beneath it.
	 */
	font-size: min( var(--wp--preset--font-size--title-md), 1.625rem );
	line-height: 1.3;
}

/*
 * §14: 15-17px, and "clearly subordinate to the featured item". These were 18,
 * four short of the featured card's 22 — close enough that the row read as four
 * rulings of equal standing. At 16 against 28 the featured one leads.
 */
.fatwa-card--compact .fatwa-card__title {
	font-size: var(--wp--preset--font-size--medium);
	line-height: 1.45;
}

/*
 * Titles run to 211 characters at the top of the corpus and 71 at the median,
 * so they are capped rather than left to set the card's height. Clamping is
 * visual only — the whole title stays in the markup, so a screen reader reads
 * it complete and nothing is summarised or rewritten, which is the one thing
 * that must not happen to the name of a ruling.
 */
.fatwa-card--featured .fatwa-card__title,
.fatwa-card--compact .fatwa-card__title {
	display: -webkit-box;
	-webkit-box-orient: vertical;
	overflow: hidden;
	text-wrap: pretty;
}

.fatwa-card--featured .fatwa-card__title {
	-webkit-line-clamp: 4;
}

.fatwa-card--compact .fatwa-card__title {
	-webkit-line-clamp: 3;
}

.fatwa-card--featured .fatwa-card__excerpt {
	margin-block-start: var(--wp--preset--spacing--30);
	color: var(--wp--preset--color--text-secondary);
}

/*
 * Reads as a button, is not one. The card resolves to the title's anchor, so
 * this is the affordance drawn where a reader looks for it, and it is out of
 * the accessibility tree — the heading link is the real control.
 */
.fatwa-card__cta {
	margin-block: 0;
	padding-block-start: var(--wp--preset--spacing--40);
}

/* §13 asks for a more visible call to action; it was body size and unremarked. */
.fatwa-card__cta-label {
	display: inline-flex;
	align-items: center;
	gap: 0.4em;
	padding-block-end: 0.15em;
	border-block-end: 2px solid var(--wp--preset--color--primary-light);
	color: var(--wp--preset--color--interactive);
	font-weight: 700;
	transition: border-color var(--t-fast) var(--ease);
}

.fatwa-card--featured:hover .fatwa-card__cta-label {
	border-block-end-color: var(--wp--preset--color--interactive);
}

.fatwa-card__cta-label::after {
	content: "\2192";
	transition: transform var(--t-fast) var(--ease);
}

.fatwa-card--featured:hover .fatwa-card__cta-label::after {
	transform: translateX(3px);
}

/* The stretched half of the pattern. */
.fatwa-card--featured .fatwa-card__link::after,
.fatwa-card--compact .fatwa-card__link::after {
	content: "";
	position: absolute;
	inset: 0;
}

/*
 * The ring goes round the card, because the card is what the link now covers.
 * Left on the anchor it would outline three clamped lines of text and leave the
 * rest of the target unmarked.
 */
.fatwa-card--featured:focus-within,
.fatwa-card--compact:focus-within {
	outline: 3px solid var(--wp--preset--color--interactive);
	outline-offset: 3px;
}

.fatwa-card--featured .fatwa-card__link:focus-visible,
.fatwa-card--compact .fatwa-card__link:focus-visible {
	outline: none;
}

@media (prefers-reduced-motion: reduce) {
	.fatwa-card__cta-label::after {
		transition: none;
	}

	.fatwa-card--featured:hover .fatwa-card__cta-label::after {
		transform: none;
	}
}

/* ============================================================ pagination == */

/*
 * The archive had no pagination CSS at all — `grep page-numbers` over this file
 * returned nothing — so 44 pages of navigation were bare inline links. Measured
 * at 390px: the current-page marker 7x20, "2" 10x20, "Seterusnya" 83x20. Every
 * one of them is a standalone control in a row of controls, so none of SC
 * 2.5.8's exemptions apply and all of them failed it.
 *
 * 44px rather than the 24px floor: this is the last thing a visitor touches
 * after a long scroll, on a phone, and a mis-tap costs a page load.
 */
.pagination {
	margin-block-start: var(--wp--preset--spacing--60);
}

/* 12px between targets, from 8. At 8 the gap was narrower than the ring a
   focused link draws, so two adjacent controls read as one block. */
.pagination .nav-links {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--30);
	align-items: center;
	justify-content: center;
}

/*
 * No resting border. These are text links, and a 1px box around each would need
 * 3:1 against the page ground to satisfy SC 1.4.11 — `border` measures 1.30:1
 * on the surface and `border-strong` 2.79:1 on the tint the archive foot sits
 * on, so neither could carry it honestly. The link text does the identifying
 * instead, at 8.31:1 on white and 7.24:1 on the tint. The transparent border is
 * held so the hover state does not resize the box.
 */
.page-numbers {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/*
	 * The space between an arrow and its word. A flex container discards the
	 * whitespace text node that would have made it, so "Seterusnya →" rendered
	 * as "Seterusnya→". The numbered links are unaffected: their only other
	 * child is the screen-reader label, which is out of flow and never a flex
	 * item.
	 */
	gap: 0.35rem;
	/*
	 * 48px, from 44. §12 asked for a more comfortable tap area and this is the
	 * last control on a 22-page archive: a mis-tap here costs a page load and
	 * the reader's place in the list. 48 is also the height every other control
	 * on the page already stands at, so the pagination stops being the one row
	 * of undersized targets on the archive.
	 */
	min-block-size: 48px;
	min-inline-size: 48px;
	padding-inline: 1rem;
	border: 1px solid transparent;
	border-radius: var(--r-button);
	color: var(--wp--preset--color--interactive);
	font-weight: 600;
	line-height: 1.2;
	text-decoration: none;
	transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}

.page-numbers:hover {
	background-color: var(--wp--preset--color--surface-tint);
	border-color: var(--wp--preset--color--border-strong);
	color: var(--wp--preset--color--interactive-hover);
}

/*
 * Restated rather than left to the global rule. The links sit 8px apart, so the
 * 3px ring plus its 2px offset has to be accounted for deliberately — and a
 * focus treatment on a control this small is the one thing that must not be
 * inherited by accident.
 */
.page-numbers:focus-visible {
	outline: var(--jmns-focus-width) solid var(--wp--preset--color--interactive);
	outline-offset: 2px;
}

/*
 * Filled, not merely coloured. The current page is the only solid shape in the
 * row, so it reads as current in greyscale and in forced colours as well as in
 * hue — SC 1.4.1 — and WordPress puts aria-current="page" on it for anyone not
 * looking at it. White on `interactive` is 8.31:1.
 */
.page-numbers.current {
	background-color: var(--wp--preset--color--interactive);
	border-color: var(--wp--preset--color--interactive);
	color: var(--wp--preset--color--surface);
}

/*
 * The arrow moves the way it points, on the control it belongs to — the same
 * 3px the entry rows and the featured fatwa use, so every "go on" gesture on
 * the portal behaves alike.
 */
.pagination__arrow {
	display: inline-block;
	transition: transform var(--t-fast) var(--ease);
}

.page-numbers.prev:hover .pagination__arrow {
	transform: translateX(-3px);
}

.page-numbers.next:hover .pagination__arrow {
	transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
	.pagination__arrow {
		transition: none;
	}

	.page-numbers.prev:hover .pagination__arrow,
	.page-numbers.next:hover .pagination__arrow {
		transform: none;
	}
}

/* Not a control: the gap marker takes no target size and no boundary. */
.page-numbers.dots {
	min-inline-size: 1.5rem;
	padding-inline: 0;
	color: var(--wp--preset--color--text-secondary);
}

.page-numbers.dots:hover {
	background-color: transparent;
	border-color: transparent;
	color: var(--wp--preset--color--text-secondary);
}

/*
 * The sentence the numbers are hiding behind. Redundant next to a visible "1 2
 * 3 … 44", so it only appears where the numbers do not.
 */
.pagination__summary {
	display: none;
	align-items: center;
	min-block-size: 48px;
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

@media (max-width: 30rem) {
	.pagination .page-numbers:not(.prev):not(.next) {
		display: none;
	}

	/* A whole line to itself, which is what forces the two controls onto the
	   next one — see jmns_pagination() for why it leads rather than sits
	   between them. */
	.pagination__summary {
		display: flex;
		flex-basis: 100%;
		justify-content: center;
	}

	/* Then the controls, at the edges of their row: the thumb reaches "Sebelum"
	   and "Seterusnya" without hunting for them. */
	.pagination .nav-links {
		justify-content: space-between;
	}
}

/* ======================================================== fatwa document == */

/*
 * A scanned gazette page is not prose and must not be set like it.
 *
 * 405 of the 433 rulings are images of a gazette rather than text. Inside
 * .entry-content they inherited `max-inline-size: content-size` — 42rem, which
 * is 672px on a desktop and about 342px on a phone. A statutory instrument
 * reproduced 342px wide is a picture of a document, not a document: the body
 * text in the scan lands somewhere near 5px tall.
 *
 * The measure exists to keep a line of prose readable. There is no line of
 * prose here, so this takes the wide size instead. It beats the
 * `.entry-content > *` cap on source order — both are one class — which is the
 * same mechanism the spacing overrides at the top of this file rely on.
 */
.fatwa-doc {
	max-inline-size: var(--wp--style--global--wide-size);
	margin-inline: auto;
	margin-block: var(--wp--preset--spacing--50);
}

/*
 * Informational, not a warning.
 *
 * The note says the ruling below is a scan and that its words cannot be
 * searched or read aloud. That is a property of the record, not a fault the
 * visitor has committed or a danger they are in — so it takes the page's own
 * sage tint and an `interactive` edge, and none of the error or warning
 * colours. `text-primary` on the tint is 13.05:1.
 */
.fatwa-doc__note {
	margin-block: 0 var(--wp--preset--spacing--40);
	padding: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
	border: 1px solid var(--wp--preset--color--border);
	border-inline-start: var(--jmns-rule) solid var(--wp--preset--color--interactive);
	border-radius: var(--r-card);
	background-color: var(--wp--preset--color--surface-tint);
	color: var(--wp--preset--color--text-primary);
	font-size: var(--wp--preset--font-size--small);
}

/*
 * The markers go, the list does not: the page count is worth announcing, and
 * "senarai, 5 item" is how a screen reader says "this ruling is five pages".
 * Safari drops a list from the accessibility tree when its markers are removed
 * this way, so the <ol> needs role="list" in the markup to keep it.
 */
.fatwa-doc__pages {
	display: grid;
	gap: var(--wp--preset--spacing--40);
	margin: 0;
	padding: 0;
	list-style: none;
}

.fatwa-doc__page {
	margin: 0;
}

/*
 * The scan is a link because the full-size original is worth reaching, and a
 * link wrapping an image needs a boundary of its own or the white margin of the
 * gazette page bleeds into the white of the surface behind it.
 */
.fatwa-doc__link {
	display: block;
	overflow: hidden;
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--r-card);
	background-color: var(--wp--preset--color--surface);
	box-shadow: var(--shadow-card);
	transition: border-color var(--t-fast) var(--ease);
}

.fatwa-doc__link:hover {
	border-color: var(--wp--preset--color--border-strong);
}

.fatwa-doc__link img {
	display: block;
	inline-size: 100%;
	block-size: auto;
}

.fatwa-doc__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--30);
	margin-block: var(--wp--preset--spacing--40) 0;
}

@media (max-width: 48rem) {
	/*
	 * Full bleed on a phone. The shell insets content by 24px a side, which on
	 * a 390px screen is 12% of the only dimension the scan has to work with.
	 * A document is the one thing on this site that earns the whole screen.
	 */
	.fatwa-doc__page {
		margin-inline: calc(50% - 50vw);
	}

	/* A rounded corner on an edge-to-edge image is a sliver of page ground in
	   the corner of a document, and the side borders have nothing to separate. */
	.fatwa-doc__link {
		border-inline: 0;
		border-radius: 0;
		box-shadow: none;
	}
}

/* =========================================================== empty state == */

/*
 * The fatwa archive's empty state. The generic one said "Tiada kandungan
 * dijumpai" and printed the site-wide search form — which carries no post_type,
 * so it silently widened the search the visitor had just scoped.
 */
.fatwa-empty,
.tender-empty {
	margin-block: var(--wp--preset--spacing--40);
	padding: var(--wp--preset--spacing--50);
	background-color: var(--wp--preset--color--surface);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--r-card);
	box-shadow: var(--shadow-card);
}

.fatwa-empty__title,
.tender-empty__title {
	margin-block: 0 var(--wp--preset--spacing--30);
	font-size: var(--wp--preset--font-size--title-sm);
}

/* The sentence that says why. Held to the prose measure — it is the one thing
   on this panel a visitor has to read rather than scan. */
.fatwa-empty__reason,
.tender-empty__reason {
	max-inline-size: var(--wp--style--global--content-size);
	margin-block: 0 var(--wp--preset--spacing--30);
}

.fatwa-empty__hint,
.tender-empty__hint {
	max-inline-size: var(--wp--style--global--content-size);
	margin-block: 0 var(--wp--preset--spacing--40);
	color: var(--wp--preset--color--text-secondary);
}

/*
 * The fatwa panel ends on a button, so its hint keeps a 16px gap under it. The
 * tender panel has nothing to offer a visitor — no scope to widen, no filter to
 * clear — so the hint is the last thing in it and that gap would be a strip of
 * unbalanced padding along the bottom of an otherwise even box.
 */
.tender-empty__hint {
	margin-block-end: 0;
}

.fatwa-empty__actions {
	margin-block: 0;
}

/* ================================================================ footer == */

/*
 * The footer is where the page's descent lands, so it deepens rather than
 * sitting flat: interactive-hover at the top edge falling to ink at the base.
 *
 * Both stops carry white comfortably — 8.31:1 at the lightest, 12.00:1 at the
 * darkest — which is the whole test for putting text over a gradient. The
 * background-color underneath is the dark stop, so a failed paint degrades to
 * the safer of the two rather than the lighter.
 */
/*
 * The closing band, at the client's direction: "a calm, modern institutional
 * closing section", not "a decorative Islamic event backdrop".
 *
 * Its own colour system, still not in theme.json — these are values that only
 * mean anything on an ink band, and in the palette they would appear as seven
 * more swatches in every colour picker in the editor. They are declared on
 * `:root` rather than here, though, because a colour scheme writes to `:root`
 * and a custom property set on the element itself beats one inherited from an
 * ancestor no matter what the specificity arithmetic says. Set here, the footer
 * was the one band on the site that no scheme could reach; the rules below read
 * exactly the same tokens and render exactly the same colours.
 *
 * The gradient used to end at text-primary, a near-black ink, so the page
 * closed by draining to grey. It now descends green into deeper green, which
 * continues the statistics band above it (#245746) instead of interrupting it.
 *
 * No pattern behind any of it. The band carried `has-motif`, which tiles the
 * girih at 120px across the whole surface; a corner watermark replaced it and
 * then went too, at the client's word. Depth here is the gradient, the spacing
 * and the hierarchy, and nothing else.
 *
 * Contrast on the darker of the two stops, and on the lighter, since the
 * gradient means text sits on both: text 10.9:1 and 12.6:1, muted 6.8:1 and
 * 7.8:1, accent 4.5:1 and 5.2:1. The two rgba tokens are decorative — a
 * container edge and a rule — and carry no information on their own.
 */
.site-footer {
	background-color: var(--footer-bottom);
	background-image: linear-gradient(
		180deg,
		var(--footer-top) 0%,
		var(--footer-bottom) 100%
	);
	color: var(--footer-text);
	/*
	 * 3.5rem. The client asks for 56-64px; this is the floor of that range, and
	 * the floor rather than the middle because the bottom edge already adds the
	 * bot launcher's clearance on top of whatever is set here — at 4rem the
	 * band became the deepest thing on the page.
	 */
	padding-block: 3.5rem;
	/*
	 * Half a step less at the foot than at the head, at the client's direction.
	 * The band is the last thing on the page, so its bottom padding is trailing
	 * space with nothing under it — the top edge is the one doing separation
	 * work, against the section above.
	 *
	 * The floating controls are position:fixed in both bottom corners, so at the
	 * page end they sit over the legal row. --jmns-stack-clear-block is
	 * published by the plugin's corner stack, and is absent when the plugin is
	 * not there — the theme reserves nothing rather than reserving for controls
	 * that do not exist.
	 *
	 * This was --jmns-bot-clearance, which reserved for the chat launcher in the
	 * inline-end corner and nothing whatever for the reading-preferences
	 * launcher in the other one — a control that has been sitting over the
	 * start end of this same legal row since it shipped, on a band that spans
	 * both corners. The stack's token is the taller of the two columns.
	 *
	 * max(), not calc(). The clearance is already a whole reservation — the
	 * column, the offset beneath it and a gap above (see stack.css) — so adding
	 * the footer's own padding to it counted that gap twice and left 124px under
	 * the last line of small print against 48px over the first heading. Taking
	 * the larger of the two clears the column by exactly the margin it asks for
	 * and no more.
	 */
	padding-block-end: max( var(--wp--preset--spacing--70), var(--jmns-stack-clear-block, 0px) );
	border-block-start: 0;
}
/*
 * Underlines stay, but quietly.
 *
 * Twelve links on an ink band, each with a full-strength white underline, read
 * as a page of struck-through text. Removing them outright is the obvious fix
 * and the wrong one: several of these sit inline in the address block, where
 * nothing but the underline says they are links, and SC 1.4.1 does not accept
 * colour alone. So it is dimmed and pushed off the baseline, and comes back to
 * full strength on hover and focus.
 *
 * 45%, not less: at a third it measured 2.74:1 against the ink, and a cue that
 * is the only thing marking a link should clear the 3:1 asked of non-text
 * information. This one is 3.99:1.
 */
/* §28 puts body copy at 15-16px; the footer was at 14 across the board. */
/*
 * 1.6, at the client's direction — the footer ran at the body's 1.7, which is a
 * reading measure and this is a reference block of addresses and short links.
 */
.site-footer {
	line-height: 1.6;
}

.site-footer a,
.jmns-footer-block p,
.jmns-footer-block li {
	font-size: 0.9375rem;
}

/* On the band's own text colour, like everything else down here. */
.site-footer a {
	color: var(--footer-text);
	text-decoration-color: rgb(255 255 255 / 0.45);
	text-underline-offset: 0.18em;
	transition: text-decoration-color var(--t-fast) var(--ease);
}

.site-footer a:hover,
.site-footer a:focus-visible {
	text-decoration-color: var(--wp--preset--color--surface);
}

.site-copyright {
	font-size: var(--wp--preset--font-size--small);
}

/* ================================================================ arabic == */

/*
 * Arabic and Jawi passages. `lang` drives this rather than a class, so correct
 * markup produces correct rendering — and screen readers switch voice too.
 *
 * The face itself, its subsets and their `unicode-range` live in fonts.css.
 * That is what keeps the Arabic file off the ~980 pages with no Arabic on
 * them: measured, the front page requests it 0 times and a Taudhih post once.
 */
[lang="ar"],
[lang^="ar-"],
.is-jawi {
	font-family: var(--wp--preset--font-family--arabic);
	font-size: 1.15em; /* Naskh reads small next to Latin at the same px size. */

	/*
	 * Leading is deliberately not set here, because this hook covers a phrase
	 * inside a Malay sentence as well as a passage standing on its own. An
	 * inline run shares its line box with the prose around it, so a fixed
	 * value stretches that one line and opens a gap mid-paragraph — measured
	 * at 32px against 20px for the lines either side of it. Inheriting instead
	 * picks up the unitless ratio of whatever it sits in (1.7 in prose, 1.25
	 * in a heading) and scales it against the larger Arabic size, so the line
	 * grows by about 4px rather than 12px. Blocks take their leading below.
	 */

	/*
	 * Arabic is a joining script. Letter-spacing prises apart letters that are
	 * required to connect, and word-spacing distorts a rhythm the spaces
	 * already carry. Both are reset in case a component above sets them — the
	 * headings and labels in this file do, and a tagged run can land in one.
	 */
	letter-spacing: normal;
	word-spacing: normal;
}

/*
 * Tagging nests: a phrase marked up inside an already-marked passage. Left
 * alone, 1.15em compounds on 1.15em and the inner run renders at 21.2px inside
 * an 18.4px quotation. The bump pays for the change of script, and the script
 * only changes once.
 */
[lang="ar"] :is([lang="ar"], [lang^="ar-"], .is-jawi),
[lang^="ar-"] :is([lang="ar"], [lang^="ar-"], .is-jawi),
.is-jawi :is([lang="ar"], [lang^="ar-"], .is-jawi) {
	font-size: 1em;
}

/*
 * A passage that is its own block gets the full leading. Vocalised naskh needs
 * it: the marks sit above and below the letters, so two lines set at Latin
 * leading collide.
 *
 * 2 is measured rather than assumed. Across 565 adjacent line pairs taken from
 * the 137 vocalised posts and laid out at the narrowest column the site uses,
 * the worst pair puts 35px of ink into the 36.8px between baselines — 1.8px of
 * clearance, and nothing collides. That is the floor: 1.9 would put the same
 * pair 0.04px short. It does not go lower.
 */
p[lang="ar"],
li[lang="ar"],
div[lang="ar"],
td[lang="ar"],
figcaption[lang="ar"],
blockquote[lang="ar"],
p.is-jawi,
li.is-jawi,
div.is-jawi,
blockquote.is-jawi {
	line-height: 2;
}

/*
 * And it is read as a quotation, because that is what it almost always is — a
 * verse or a hadith standing apart from the Malay argument around it, not a
 * paragraph that happened to change direction. A rule on the side the text
 * starts from, which mirrors with the writing direction on its own.
 *
 * Jawi is excluded: it is Malay in Arabic script, so it is the prose itself
 * rather than something quoted, and a quotation rule would misrepresent it.
 *
 * The plugin states the same treatment for Taudhih articles, where its own
 * selector is the more specific of the two and wins. This is the site-wide
 * floor under it, for the Arabic in the pengumuman, berita and fatwa.
 */
p[lang="ar"],
blockquote[lang="ar"] {
	margin-block: var(--wp--preset--spacing--40);
	padding-inline-start: var(--wp--preset--spacing--30);
	border-inline-start: 3px solid var(--wp--preset--color--border-strong);
}

/* ============================================================== motion === */

@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
}

/* ============================================================ responsive == */

/*
 * Mobile-first: the audience is majority mobile (§4.4). Everything above is
 * written to work at 320px; these queries only handle wrapping at the edges.
 * All units are rem so a 200% browser zoom reflows rather than clipping.
 */
@media (max-width: 75rem) {
	/* The crest plus six items stops fitting one row here; the nav drops to
	   its own line instead of overflowing. */
	.primary-navigation {
		flex-basis: 100%;
	}

	.primary-navigation ul {
		justify-content: flex-start;
	}
}

@media (max-width: 48rem) {
	.site-header > .wp-block-group {
		flex-direction: column;
		align-items: flex-start;
	}

	.fatwa-finder__row {
		flex-direction: column;
		align-items: stretch;
	}

	.prayer-bar__inner {
		justify-content: space-between;
	}

	/*
	 * Room under the last card for the floating controls.
	 *
	 * "Tanya Al Hakim" is position:fixed at the bottom-inline-end corner, and at
	 * 390px it sat over the text of the final fatwa card and over the pagination
	 * that follows it — a control covering another control, which is a 2.5.8
	 * problem as well as a reading one. The footer already reserves this; the
	 * end of <main> did not.
	 *
	 * Not on the front page, which is excluded from the rule this extends: its
	 * last band is the deep green statistics strip, and the page's colour
	 * descent depends on that meeting the ink footer at 0px.
	 *
	 * max(), matching .site-footer: the clearance variable is published by the
	 * plugin's corner stack and is absent without the plugin, and the last card
	 * needs breathing room from the footer either way. 5rem is off the token
	 * scale deliberately — the scale steps 3rem to 4rem to 6rem, and 4rem does
	 * not clear even the single launcher this was written for.
	 */
	body:not(.home) .site-main {
		padding-block-end: max( 5rem, var(--jmns-stack-clear-block, 0px) );
	}
}

/* ================================================================= print == */

/*
 * Minimum target size for links that stand on their own.
 *
 * SC 2.5.8 asks 24x24 of any target, and exempts a link sitting inside a
 * sentence, whose size is constrained by the line-height around it. The contact
 * block's phone and email are exactly that case and are left alone.
 *
 * These are not: one link per row in a list, a statistic's caption, a partner
 * logo, a ticker announcement. A line box gave them 17 to 20px. The height is
 * added as centring room rather than as leading, so nothing shifts on the page
 * — only the area a finger can land on changes.
 */
.home-stats__label a,
.jmns-gov-links__list a,
.jmns-legal__links a,
.jmns-qr__link,
.jmns-partners__item a {
	display: inline-flex;
	align-items: center;
	min-block-size: 24px;
}

/*
 * A title must not be able to break the page.
 *
 * One publication is titled with a raw URL, which is a single unbreakable
 * string: at 390px it rendered 869px wide and pushed the whole archive 503px
 * off screen. The title is content and JMNS should retitle it, but a layout
 * that a bad title can break is a layout defect on its own — the next long
 * filename or pasted link would do the same.
 *
 * `anywhere` rather than `break-word` so the break is allowed to count when the
 * browser computes intrinsic width, which is what stops the container widening
 * in the first place.
 */
.entry-title,
.entry-card__title,
.fatwa-card__title,
.tender-card__title,
.taudhih-entry__title,
.poster-card__title,
.jmns-card__title,
.home-section__title,
.site-main h1,
.site-main h2,
.site-main h3 {
	overflow-wrap: anywhere;
}

/* ====================================================== mobile homepage == */

/*
 * A phone reads the same page in a column, so the rhythm and the picture sizes
 * that suit a 1280px canvas both need revisiting. Measured at 492px before this:
 * 8,967px of document, of which 768px was band padding and 1,799px was one row
 * of three posters.
 */
@media (max-width: 48rem) {
	/*
	 * 40px instead of 64 above and below every band. Six bands at 128px of
	 * padding each is 768px of the scroll spent on nothing — on a wide screen
	 * that air separates columns of content, and stacked there is only ever one
	 * thing on screen to separate it from.
	 */
	.home-section,
	.jmns-quick-links,
	.home-latest {
		padding-block: var(--wp--preset--spacing--70);
	}

	/* The heading needs less clearance from its own cards at this width. */
	.home-section__title,
	.home-latest > h2,
	.jmns-quick-links__heading {
		margin-block-end: var(--wp--preset--spacing--40);
	}

	/*
	 * The poster row is declared once, in the mobile block further down, and it
	 * used to be declared here as well.
	 *
	 * The two disagreed — this one made the row a flex container that snapped to
	 * `start`, that one makes it a grid that snapped to `center`, and both sat
	 * inside `max-width: 48rem` at the same specificity — so the later block won
	 * silently and half of what was written here never applied. Whatever was
	 * still wanted from it (the focus-ring clearance, the momentum-scrolling
	 * hint) has moved to the surviving rule.
	 */

	/* Tighter tiles: ten of them stacked two-up is the longest block on the
	   page after the posters. */
	.jmns-tile {
		gap: var(--wp--preset--spacing--20);
		padding: var(--wp--preset--spacing--40) var(--wp--preset--spacing--30);
	}

	/* The four footer columns are stacked, so the column gap is now vertical
	   space between unrelated blocks and reads as a hole. */
	.site-footer__columns {
		gap: var(--wp--preset--spacing--50);
	}
}

@media print {
	.site-header,
	.site-footer,
	.fatwa-finder,
	.pagination {
		display: none;
	}

	.has-motif::before {
		display: none;
	}

	/*
	 * A printed ruling must still show its gazette status.
	 *
	 * The fill is reset as well as the colour. Browsers drop background colours
	 * by default but not when the visitor turns backgrounds on, and the filled
	 * "Diwartakan" badge would then print black ink on dark green.
	 */
	.warta-badge,
	.tender-badge {
		background: none;
		border: 1px solid #000;
		color: #000;
	}
}

/* ------------------------------------------------------- prayer schedule --- */

/*
 * The full monthly schedule page. The bar's own rules live in the prayer bar
 * section above; this block used to redeclare .prayer-bar__date, __times,
 * __zone and __zone select AFTER them, so the later duplicates silently won —
 * which is why the zone select stayed 22rem wide against a 16rem cap.
 */

.prayer-schedule__table th,
.prayer-schedule__table td {
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

.prayer-schedule__source {
	font-size: var(--wp--preset--font-size--small);
}

@media print {
	/* The schedule is meant to be printed and pinned up. */
	.prayer-schedule__table thead th {
		background: transparent;
		color: #000;
		border-block-end: 2px solid #000;
	}

	/* A printed sheet keeps the date, the times and the source; a live clock,
	   a countdown and a control to change zone all mean nothing on paper. */
	.prayer-bar__zone,
	.prayer-bar__zone-form,
	.prayer-bar__now,
	.prayer-schedule .prayer-bar__zone-form {
		display: none;
	}
}

/* ----------------------------------------------------------- quick links --- */

/*
 * Fixed tracks, centred — not 1fr.
 *
 * Two problems came from the same declaration. `margin: 0` cancelled the
 * margin-inline:auto that centres the shell, so the tiles sat at the band's left
 * edge while the heading above them was centred. And 1fr tracks stretch to fill
 * the row, so nine tiles across five columns left the second row's four items
 * packed at the left with a tile-sized hole on the right.
 *
 * Fixed 13rem tracks with justify-content:center give both rows the same tile
 * width and centre each row under the heading — 5 then 4, symmetrical, no hole.
 */
.jmns-quick-links__grid {
	/*
	 * Flex, not grid, and that is the whole point.
	 *
	 * `justify-content: center` on a grid centres the track grid, not the items
	 * inside it — nine tiles across five tracks still left the second row's four
	 * packed against the first four tracks with a tile-sized hole on the right.
	 * A wrapping flex row centres each line independently, so 5 + 4 comes out
	 * symmetrical under a centred heading.
	 */
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: var(--wp--preset--spacing--50);
	margin-block: 0;
	padding: 0;
	list-style: none;
}

.jmns-quick-links__item {
	flex: 0 0 13rem;
	max-inline-size: 13rem;
	margin: 0;
}

/*
 * The whole tile is one link, so there is exactly one tab stop and one
 * accessible name per service — not an icon link plus a text link.
 */
/*
 * Left-aligned, per brief §14's own example and §3's instruction to stop
 * centring everything. Centred tiles are what made ten different services read
 * as one repeated shape: with the icon, the label and the affordance all on a
 * shared centre line, the eye gets no edge to run down and every card looks
 * like the last one.
 */
.jmns-tile {
	display: flex;
	flex-direction: column;
	align-items: start;
	justify-content: start;
	gap: var(--wp--preset--spacing--20);
	block-size: 100%;
	min-block-size: 128px;
	/*
	 * Even, at the client's direction to improve card internal spacing. It was
	 * 24px down and 16px across, which is a proportion that reads as cramped
	 * from the side while looking generous from the top.
	 */
	padding: var(--wp--preset--spacing--50);
	background-color: var(--wp--preset--color--surface);
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--r-card);
	box-shadow: var(--shadow-card);
	color: var(--wp--preset--color--text-primary);
	text-align: start;
	text-decoration: none;
	transition: box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease),
		border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}

/*
 * §14's hover, and no more than it: the border takes the brand green, the icon
 * gets a soft sage wash, the card lifts 2px. §25 caps the lift at 4px and bans
 * the rest, so there is no scale, no rotation and no shadow bloom.
 */
/*
 * Keyboard focus gets the same card, not just an outline.
 *
 * All of this was bound to `:hover` alone, so tabbing through ten services
 * moved a focus ring across cards that never otherwise responded — the border
 * stayed grey, the icon stayed flat and the arrow never appeared. The outline
 * satisfies SC 2.4.7 on its own; matching the pointer's feedback is what makes
 * the two ways of using the grid feel like the same grid.
 */
.jmns-tile:is( :hover, :focus-visible ) {
	border-color: var(--wp--preset--color--primary);
	box-shadow: var(--shadow-hover);
	/* §15 names -3px explicitly, inside §25's 2-4px cap. */
	transform: translateY(-3px);
}

.jmns-tile:is( :hover, :focus-visible ) .jmns-tile__icon {
	background-color: var(--wp--preset--color--surface-tint);
}

@media (prefers-reduced-motion: reduce) {
	.jmns-tile:is( :hover, :focus-visible ),
	.jmns-tile:is( :hover, :focus-visible ) .jmns-tile__arrow {
		transform: none;
	}
}

/*
 * No disc behind the icon, at the client's direction. The uploaded PNGs carry
 * their own shape and colour, so a tinted circle underneath was a second
 * container around artwork that already had one.
 */
.jmns-tile__icon {
	display: grid;
	place-items: center;
	/*
	 * 72, up from 56, at the client's direction — the artwork is detailed and
	 * was reading as a thumbnail once the disc behind it went.
	 */
	/*
	 * §13 asks for a 48-56px visual icon area and says the current icons "feel
	 * small compared with the card area". Measured, they were at the floor of
	 * that range and not below it — 52x64 on desktop, 48x48 on a phone — so the
	 * complaint is proportion rather than absolute size. The slot grows so the
	 * artwork lands at the top of the range instead of the bottom.
	 */
	inline-size: 76px;
	/*
	 * The slot has a fixed height even though the artwork does not. The source
	 * PNGs are not all the same ratio — 104x128 and 103x128 among them — so at a
	 * common width they render 73 to 76px tall, and a slot sized to its own
	 * image left every tile in the row centred on a slightly different line.
	 * The box is uniform; the image keeps its own proportions inside it.
	 */
	/*
	 * 88, up from 78. The artwork is portrait, so height binds before width and
	 * the slot's height is what actually sizes it: at 78 the images rendered
	 * 55x68 inside a 208px card. This puts them at 63x78 without touching the
	 * width, which is what keeps the row of ten aligned.
	 */
	block-size: 88px;
	/* Room for the hover wash to sit behind the artwork without touching it. */
	padding: 0.35rem;
	border-radius: var(--r-button);
	color: var(--wp--preset--color--interactive);
	transition: background-color var(--t-fast) var(--ease);
}

/*
 * Width-led, height free, at the client's direction.
 *
 * The icons are 104x128 portrait PNGs and both axes were pinned to 28px, so
 * every one was squeezed 23% narrower than it was drawn. The width is capped and
 * the height follows the artwork's own ratio.
 */
.jmns-tile__icon img,
.jmns-tile__icon svg {
	inline-size: auto;
	block-size: auto;
	max-inline-size: 85%;
	/* Whichever axis binds first wins, so nothing is ever stretched. */
	max-block-size: 100%;
	object-fit: contain;
}

/*
 * The description, when there is one. All ten tiles are currently blank —
 * describing the department's own services is their writing, not ours — so this
 * rule mostly reserves the shape for when they fill it in.
 */
.jmns-tile__description {
	margin-block-start: 0.15rem;
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--caption);
	font-weight: 400;
	letter-spacing: 0;
	line-height: 1.45;
	text-transform: none;
}

/*
 * The arrow alone, at the foot of the card (§14).
 *
 * It used to read "Terokai →" on all ten. Ten cards ending in the same word is
 * ten times the words for one piece of information, and the card has always
 * been the link — so the word goes and the affordance stays.
 */
.jmns-tile__arrow {
	/*
	 * Under the label, not pushed to the card's foot. `margin-block-start:auto`
	 * pinned it to the bottom, which on a two-line-reserved label left it
	 * floating alone in a band of empty card — §14's example puts the arrow
	 * with the words it belongs to.
	 */
	margin-block-start: 0.15rem;
	color: var(--wp--preset--color--text-disabled);
	font-size: 1.1rem;
	line-height: 1;
	/*
	 * On approach, not always — the option §14 offered and this round's
	 * direction to quieten the section.
	 *
	 * Ten grey arrows, one per card, are ten marks carrying the same piece of
	 * information: that a card is a link. The card is a link whether or not the
	 * arrow is drawn — it sits in a nav list, it has a border and a pointer,
	 * and its whole area has always been the target.
	 *
	 * Faded rather than removed from the flow, so the card does not change
	 * height when a pointer crosses it.
	 */
	opacity: 0;
	transform: translateX(-3px);
	transition:
		opacity var(--t-fast) var(--ease),
		transform var(--t-fast) var(--ease),
		color var(--t-fast) var(--ease);
}

.jmns-tile:is( :hover, :focus-visible ) .jmns-tile__arrow {
	opacity: 1;
	transform: translateX(0);
	color: var(--wp--preset--color--interactive);
}

/*
 * Except where there is no pointer to approach with. A touch screen never
 * fires hover, so on a phone the arrow would simply never exist — and that is
 * the context where the affordance is worth most, because there is no cursor
 * change to tell anybody the card is tappable.
 */
@media (hover: none) {
	.jmns-tile__arrow {
		opacity: 1;
		transform: none;
	}
}

/*
 * §10: "reduce the card height slightly, do not leave unnecessary empty space
 * at the bottom".
 *
 * The arrow is only drawn on approach but it still held its place in the
 * column — 18px of glyph, its margin and the flex gap, so every card reserved
 * 29px below its label for something invisible. Taken out of flow it costs
 * nothing until it is shown, and it appears in the corner a card affordance
 * belongs in rather than under the first word of the label.
 *
 * Scoped to the grid. Below 25.5rem the tile is a row — icon, label, arrow
 * along the width — and there the arrow is in flow on purpose.
 */
@media (min-width: 25.5625rem) {
	.jmns-tile {
		position: relative;
	}

	.jmns-tile__arrow {
		position: absolute;
		inset-block-end: var(--wp--preset--spacing--40);
		inset-inline-end: var(--wp--preset--spacing--40);
		margin-block-start: 0;
	}
}


.jmns-tile__label {
	display: flex;
	flex-direction: column;
	gap: 0.15em;
	font-family: var(--wp--preset--font-family--heading);
	/*
	 * 16px, up from 14. §28 is explicit that minimalism must not be achieved by
	 * making supporting text small, and this is a service label — the primary
	 * text of the card, not supporting anything. It was the one role on the
	 * homepage measuring under its target range.
	 */
	font-size: var(--wp--preset--font-size--medium);
	font-weight: 600;
	letter-spacing: 0.03em;
	line-height: 1.35;
	text-transform: uppercase;
	/*
	 * Two lines are always reserved, whether or not the second is used.
	 *
	 * Six of the ten labels wrap and four do not, and with the block sized to
	 * its own text the row centred each tile's contents independently — so
	 * JUFFAS and PENERBITAN sat a half-line off their neighbours and the row of
	 * icons above them stepped up and down across the grid. Reserving the space
	 * puts every icon on one line and every label on the same baseline.
	 *
	 * 2 lines plus the flex gap, in the label's own line-height, so it holds if
	 * the type scale changes.
	 */
	min-block-size: calc(2 * 1.35em + 0.15em);
	min-block-size: calc(2lh + 0.15em);
	justify-content: flex-start;
	/* Start, with the rest of the tile since §14. The reserved two lines above
	   are what keeps the row aligned; the centring was never doing that job. */
	text-align: start;
}

/*
 * Mobile: two columns rather than a horizontal scroller. A scroller hides
 * services below the fold with no affordance; two columns keeps all ten
 * reachable by scrolling the page normally.
 *
 * This block used to restate the desktop auto-fit verbatim, so it claimed two
 * columns in a comment and produced one in the browser: a 13rem track minimum
 * cannot fit twice into a 342px content box, so auto-fit collapsed to a single
 * column and nine tiles measured 1759px tall on a phone. Two explicit tracks
 * with a zero minimum is the only form that holds at 360px.
 */
@media (max-width: 48rem) {
	.jmns-quick-links__grid {
		gap: var(--wp--preset--spacing--30);
	}

	/*
	 * Two per row on a phone, sized by the viewport rather than the fixed 13rem
	 * the desktop row uses — 208px twice plus a gap overflows a 360px screen.
	 */
	.jmns-quick-links__item {
		flex: 1 1 calc(50% - var(--wp--preset--spacing--30));
		max-inline-size: none;
	}

	/*
	 * §13's range applies on a phone too, and 3rem put the artwork at exactly
	 * its floor. Height is left free so the portrait PNGs keep their own ratio
	 * rather than being squared off — pinning both axes is what squashed them
	 * before.
	 */
	.jmns-tile__icon img {
		inline-size: 3.5rem;
		block-size: auto;
		max-block-size: 100%;
	}
}

/*
 * Narrow phones get a row per service, not a broken two-up.
 *
 * A tile's min-content is 174px: 48px of padding around the longest word a
 * label contains. Two of those plus the gap need 360px of grid, and a 390px
 * phone has 342 — so the wide tiles dropped to their own line while the narrow
 * ones paired up, and ten services came out as six rows of alternating one and
 * two. Ragged is worse than either arrangement on purpose.
 *
 * Two columns cannot be made to hold here without shrinking the label below
 * the size §28 sets for it or breaking a Malay word across lines. Turned on its
 * side the same card needs no compromise: the icon and the label sit along the
 * width the screen actually has, the target grows rather than shrinks, and the
 * section comes in at less than half the height ten stacked cards would take.
 *
 * 25.5rem, from the arithmetic rather than from a device: two tiles need
 * 2 x 174 + 12px of gap = 360px of grid, and the grid sits inside 48px of
 * section padding, so the columns hold from 408px of viewport up. Rows apply
 * below that and nowhere else.
 */
@media (max-width: 25.5rem) {
	.jmns-quick-links__item {
		flex: 1 1 100%;
	}

	.jmns-tile {
		flex-direction: row;
		align-items: center;
		gap: var(--wp--preset--spacing--40);
		min-block-size: 0;
		padding: var(--wp--preset--spacing--40);
	}

	.jmns-tile__icon {
		flex: 0 0 auto;
		inline-size: 64px;
		block-size: 64px;
	}

	.jmns-tile__icon img {
		inline-size: auto;
		max-inline-size: 100%;
	}

	/*
	 * No two-line reservation in a row: it exists to put ten icons on one line
	 * across a grid, and in a row there is one icon and it is already centred
	 * against the label beside it.
	 *
	 * The authored line breaks go with it. Labels are stored with a newline in
	 * them — "FATWA\nSELANGOR" — which shapes a 208px card and means nothing
	 * beside a 64px icon on a 342px screen, where the same words fit on one
	 * line. As a wrapping row the break becomes a space and each label takes
	 * only the lines it actually needs.
	 */
	.jmns-tile__label {
		flex-direction: row;
		flex-wrap: wrap;
		gap: 0 0.3em;
		min-block-size: 0;
	}

	/* The affordance goes where a list row puts it. */
	.jmns-tile__arrow {
		margin-block-start: 0;
		margin-inline-start: auto;
	}
}

/* ------------------------------------------------------- footer columns --- */

/*
 * The same shell as every other band on the page.
 *
 * It was max-inline-size plus its own padding-inline, which is a different sum
 * from the one the masthead, nav, prayer bar and content sections use — so the
 * footer's first column began at x=51 while everything above it began at 41.
 * Ten pixels is not much to see and is exactly enough to feel.
 */
.site-footer__columns {
	display: grid;
	/* Columns collapse to one at narrow widths without a breakpoint list. */
	/*
	 * Asymmetric, per the design: the identity column holds a full postal
	 * address and the three beside it hold short lists, so equal tracks left the
	 * address wrapping while a column of four links ran half empty.
	 */
	/*
	 * The third track is wider than it looks it needs to be. It holds five
	 * social buttons at the 44px target §23 asks for — 220px of button before
	 * any gap — and at 1fr they wrapped four-and-one, which reads as a mistake
	 * rather than a row. Sized to what it actually has to hold.
	 */
	grid-template-columns: 1.5fr 1fr 1.25fr 1.1fr;
	gap: var(--wp--preset--spacing--60);
	inline-size: min(100% - 2 * var(--wp--preset--spacing--50), var(--wp--style--global--wide-size));
	margin-inline: auto;
}

.footer-widget + .footer-widget {
	margin-block-start: var(--wp--preset--spacing--40);
}

/*
 * Column labels, not headings. At 16px uppercase bold they were the same size
 * as the body beneath them, so each column opened with two lines of equal
 * weight and nothing led. 14px with wider tracking reads as a label and lets
 * the content below it be the content.
 */
/*
 * Label, hairline, content — the rhythm design/JMNS Homepage.dc uses for every
 * footer column. The rule is what makes four columns of different lengths read
 * as one row rather than four stacks.
 *
 * 0.35 alpha measures 2.28:1 against the gradient's lightest stop, which is
 * fine: this is a decorative divider, not a control boundary or the sole
 * carrier of any information.
 */
.footer-widget__title,
.jmns-footer__heading {
	margin-block: 0 var(--wp--preset--spacing--40);
	padding-block-end: var(--wp--preset--spacing--30);
	/*
	 * The same hairline as the other two rules in the band. It was white at
	 * 35%, which was fine when it was the only divider down here and wrong once
	 * the strip and the legal rule came down to 10% — four bright lines across
	 * the top of the footer, brighter than anything they were separating.
	 *
	 * The rule's job is to make four columns of different lengths read as one
	 * row. That is done by the alignment, not by the strength.
	 */
	border-block-end: 1px solid var(--footer-divider);
	font-size: var(--wp--preset--font-size--small);
	color: var(--footer-text);
	text-transform: uppercase;
	letter-spacing: 0.12em;
	/*
	 * Two lines' worth of room whatever the label says.
	 *
	 * "KAJIAN KEPUASAN PELANGGAN" wraps and the other three do not, so its rule
	 * sat a line lower than theirs and the row of four read as misaligned. The
	 * line-height is stated rather than inherited so the reservation is exact,
	 * and the padding is added back because box-sizing is border-box here — a
	 * bare 2.8em min reserved two lines *including* the gap under the rule,
	 * which is one line of text and a space.
	 */
	line-height: 1.4;
	min-block-size: calc(2.8em + var(--wp--preset--spacing--30));
}

.site-footer ul {
	margin: 0;
	padding: 0;
	list-style: none;
}

.site-footer li + li {
	margin-block-start: 0.35rem;
}

/*
 * ...except where the list is already a flex column with its own gap. Both were
 * applying, so the social and government lists ran at 11px between items while
 * the contact list ran at 5.6px — one footer, two rhythms, from a rule meeting
 * a gap that did the same job.
 */
.jmns-social__list li + li,
.jmns-gov-links__list li + li,
.jmns-partners__list li + li,
.jmns-legal__links li + li {
	margin-block-start: 0;
}

/* --------------------------------------------------------------- blocks --- */

/*
 * The department leads; the address supports it.
 *
 * Everything in this block was full-strength white at one size, so the name of
 * the organisation sat level with the floor it is on. At the client's direction
 * the name now carries the emphasis and the detail steps back — one hierarchy,
 * expressed in brightness rather than in more type sizes.
 *
 * Stepped back with colour, not opacity. Opacity multiplies down the tree, so a
 * child of a faded parent cannot be brighter than it — the name would have been
 * capped at whatever the block was set to, which is the opposite of the point.
 *
 * Nothing drops below 4.5:1 on this band: the address measures 8.9:1 against the
 * gradient's lightest stop and the labels 6.6:1, so stepping back is a matter of
 * emphasis and not of legibility.
 */
.jmns-contact__address {
	font-style: normal; /* <address> italicises by default; it is not a quote. */
	margin-block-end: var(--wp--preset--spacing--30);
	font-size: var(--wp--preset--font-size--small);
	line-height: 1.6;
	/*
	 * Sage, not white at 82%. A neutral wash over a coloured band reads as
	 * faded text; a muted tone taken from the band's own hue reads as a second
	 * level of the same voice. 6.8:1 at the gradient's lightest stop.
	 */
	color: var(--footer-muted);
}

.jmns-contact__address strong {
	display: inline-block;
	margin-block-end: 0.35rem;
	font-family: var(--wp--preset--font-family--heading);
	/*
	 * 18px, per V3 §29, and §30's "the Jabatan identity should be the strongest
	 * column". It was `medium` at 16 — two above the address beneath it, which
	 * is a difference you can measure and not one you can see.
	 *
	 * `large`, not a bare value: there is no `base` preset, and naming one that
	 * does not exist is how this line silently matched the address once before.
	 */
	font-size: var(--wp--preset--font-size--large);
	font-weight: 700;
	line-height: 1.3;
	letter-spacing: 0.01em;
	color: var(--footer-text);
}

.jmns-contact__list {
	font-size: var(--wp--preset--font-size--small);
}

/* The label says which number it is; the number is the useful part. */
.jmns-contact__label {
	color: var(--footer-muted);
}

/*
 * The phone and email are 24px targets after all.
 *
 * These were excluded from the earlier target-size pass on the grounds that
 * SC 2.5.8 exempts a link inside a sentence. Verified at a true 390px viewport
 * and the exclusion was wrong: "Telefon:" followed by a number on its own list
 * row is a label and a value, not prose, and the exception is written for a
 * link embedded in running text where enlarging it would disturb the paragraph.
 * There is no paragraph here to disturb.
 *
 * The line box is already 22.4px at this size, so the floor costs 1.6px and
 * changes nothing anyone can see.
 */
.jmns-contact__list a {
	display: inline-flex;
	align-items: center;
	min-block-size: 24px;
}

.jmns-gov-links__list {
	display: flex;
	flex-direction: column;
	/* Matches the 0.35rem the contact list gets from `li + li`, so every column
	   in the row shares one vertical rhythm. */
	gap: 0.35rem;
}

/*
 * Social becomes chips, not a list of underlined lines.
 *
 * Six platform names stacked as underlined text was the noisiest thing in the
 * footer and the least like the design. As bordered 44px targets they read as
 * what they are — a row of destinations — and the border does the work the
 * underline was doing, so these opt out of the footer's link rule.
 *
 * The border is white at 60%: 3.73:1 against the gradient's lightest stop and
 * 5.80:1 against its darkest, so it clears the 3:1 SC 1.4.11 asks of a control
 * boundary along the whole band.
 */
.jmns-social__list {
	display: flex;
	flex-wrap: wrap;
	/* Tighter than the default so five 44px targets clear the track. */
	gap: 0.4rem;
}

.jmns-social__list a {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	/* Square now that the label is an icon: 44px each way, which is the target
	   size the brief asks for and exactly what the glyph needs around it. */
	min-block-size: 44px;
	min-inline-size: 44px;
	padding: 0;
	/*
	 * A filled hit area rather than a drawn ring.
	 *
	 * White at 60% put five hard outlines in a row across the band, which is
	 * most of what made this column the loudest thing in the footer. The fill
	 * marks the same target more quietly, and the border stays as a hairline so
	 * the boundary does not disappear entirely against the gradient.
	 *
	 * SC 1.4.11 wants 3:1 on a control boundary, and neither the fill nor the
	 * hairline carries that on its own. Neither has to: the icon inside is the
	 * control, it is white on ink at better than 10:1, and it remains a link
	 * with an accessible name whatever the chip behind it does.
	 */
	border: 1px solid var(--footer-divider);
	border-radius: var(--r-button);
	background-color: var(--footer-card);
	font-size: var(--wp--preset--font-size--small);
	text-decoration: none;
	transition:
		border-color var(--t-fast) var(--ease),
		background-color var(--t-fast) var(--ease),
		transform var(--t-fast) var(--ease);
}

/*
 * Gold on hover, as the design has it — but only as a border, never as the
 * text: gold is 3.57:1 on the gradient's lightest stop, fine for a boundary and
 * nowhere near the 4.5:1 a label needs. The fill lifts instead.
 */
.jmns-social__icon {
	inline-size: 1.25rem;
	block-size: 1.25rem;
}

.jmns-social__list a:hover,
.jmns-social__list a:focus-visible {
	border-color: var(--footer-accent);
	background-color: rgb(255 255 255 / 0.08);
	transform: translateY(-1px);
}

@media (prefers-reduced-motion: reduce) {
	.jmns-social__list a {
		transition: none;
	}

	.jmns-social__list a:hover,
	.jmns-social__list a:focus-visible {
		transform: none;
	}
}

.jmns-partners__list {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--30);
	align-items: center;
}

.jmns-partners__item img {
	/*
	 * 3.6rem, up from 3. These are other agencies' marks and several carry
	 * wordmarks rather than symbols; at 48px the lettering inside them was
	 * smaller than any type on the page it sits in.
	 */
	block-size: 3.6rem;
	inline-size: auto;
	/*
	 * Partner logos arrive in many colours and would clash on deep green, but
	 * they must stay recognisable — a light background chip rather than a
	 * filter that would distort brand colours.
	 */
	padding: 0.35rem 0.5rem;
	background-color: var(--wp--preset--color--surface);
	/*
	 * Softened, not rounded. The chip is a plate holding somebody else's mark,
	 * so the corner should read as the plate's rather than as a shape imposed
	 * on the logo — 4px does that and anything more starts restyling the guest.
	 */
	border-radius: 4px;
}

/*
 * Code and instruction side by side, as the design lays it out, inside a panel
 * of its own.
 *
 * The QR plate is a white square on an ink band and was the brightest thing in
 * the footer by a distance, floating with nothing around it. A faint container
 * gives it somewhere to sit, which is all the client asks for — no shadow, no
 * glass, no lift.
 */
/*
 * The code down the side, its instruction and link stacked against it.
 *
 * Two equal columns put the sentence in about 112px and broke it into four
 * ragged lines; a single stacked column left two thirds of the panel empty
 * beside an 88px square. The code spans both rows instead, so the text takes
 * the width the panel actually has and the panel is only as tall as the code.
 */
.jmns-qr__panel {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: var(--wp--preset--spacing--30) var(--wp--preset--spacing--40);
	align-items: start;
	padding: var(--wp--preset--spacing--40);
	border: 1px solid rgb(255 255 255 / 0.08);
	border-radius: var(--r-card);
	background-color: var(--footer-card);
}

/* The code beside both lines of text, not above one of them. */
.jmns-qr__image {
	grid-row: 1 / span 2;
}

.jmns-qr__image img {
	inline-size: 5.5rem;
	block-size: auto;
	padding: 0.35rem;
	/* A QR code must be dark-on-light to scan at all, so it keeps a white
	   plate whatever the band behind it is doing. */
	background-color: var(--wp--preset--color--surface);
}

/*
 * Four fixed tracks are a desktop idea. Below 64rem the identity column's
 * address needs the full width, and at phone widths every column does.
 */
@media (max-width: 64rem) {
	.site-footer__columns {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

@media (max-width: 40rem) {
	.site-footer__columns {
		grid-template-columns: minmax(0, 1fr);
	}

	.site-footer__strip {
		flex-direction: column;
		align-items: flex-start;
	}

	.site-footer__meta {
		margin-inline-start: 0;
		text-align: start;
	}
}

/* ---------------------------------------------------------------- strip --- */

/*
 * Partners on one side, the count and the copyright on the other. Both were
 * whole columns of the four-up row before, which gave a single logo the same
 * width as the department's full address.
 */
.site-footer__strip {
	inline-size: min(100% - 2 * var(--wp--preset--spacing--50), var(--wp--style--global--wide-size));
	margin-inline: auto;
	margin-block-start: var(--wp--preset--spacing--80);
	padding-block-start: var(--wp--preset--spacing--50);
	border-block-start: 1px solid var(--footer-divider);
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--wp--preset--spacing--50);
}

.site-footer__strip .jmns-footer__heading {
	/* The strip is one line; a label with a rule under it would restate the
	   rule already above the whole strip. */
	margin-block-end: var(--wp--preset--spacing--20);
	padding-block-end: 0;
	border-block-end: 0;
}

.site-footer__meta {
	margin-inline-start: auto;
	text-align: end;
}

.site-footer__meta .jmns-counter__value {
	font-size: var(--wp--preset--font-size--large);
}

.jmns-counter__value {
	margin: 0;
	font-family: var(--wp--preset--font-family--heading);
	font-size: var(--wp--preset--font-size--title-sm);
	font-weight: 700;
	line-height: 1.1;
	font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------- legal --- */

/* Same shell again, so the rule above it starts where the columns start. */
.site-footer__legal {
	inline-size: min(100% - 2 * var(--wp--preset--spacing--50), var(--wp--style--global--wide-size));
	margin-inline: auto;
	margin-block-start: var(--wp--preset--spacing--60);
	padding-block-start: var(--wp--preset--spacing--40);
	/*
	 * The quietest rule on the page, at the client's direction: the copyright
	 * is the last thing anybody needs and the divider is there to separate it,
	 * not to announce it. Both rules in the footer now use the same token.
	 */
	border-block-start: 1px solid var(--footer-divider);
	font-size: var(--wp--preset--font-size--small);
}

.jmns-legal__links ul {
	display: flex;
	flex-wrap: wrap;
	gap: var(--wp--preset--spacing--20) var(--wp--preset--spacing--30);
}

/*
 * In the Pautan column the mandated links are a list under a heading, not a
 * strip across the foot of the page, so they stack on the same 0.35rem rhythm
 * as the government links directly above them and read as one list of four.
 */
.jmns-legal--links {
	margin-block-start: 0.35rem;
}

.jmns-legal--links ul {
	flex-direction: column;
	gap: 0.35rem;
}

.jmns-legal__links li + li {
	margin-block-start: 0;
}

/*
 * Below the rule is small print, and should read as small print.
 *
 * Colour, not opacity, matching the contact block above and for the same
 * reason: opacity multiplies down the tree, and these lines sit on the darkest
 * stop of the gradient where a 75% wash is closest to grey. 7.8:1 down here.
 */
/*
 * Scoped to the band for specificity, not for reach. `.jmns-footer-block p`
 * sets the footer's 15px body size at (0,1,1) and these are paragraphs inside
 * one, so a bare class here lost by a single element selector and the size
 * below silently did nothing.
 */
.site-footer .jmns-legal__copyright,
.site-footer .jmns-legal__updated {
	margin-block: var(--wp--preset--spacing--20) 0;
	/*
	 * 13px. §29 puts footer metadata at 12-13 and §6 puts metadata at 13, and
	 * this line was inheriting the footer's 15px body size from the rule that
	 * covers links and block paragraphs — so a copyright notice read at the
	 * same size as the address above it.
	 *
	 * Not a case of the "excessively small text" the reviews warn against: that
	 * warning is about body copy, and every other string down here stays at 14
	 * or 15. This one is a date and a legal notice. 7.6:1 on the band.
	 */
	font-size: var(--wp--preset--font-size--caption);
	color: var(--footer-muted);
}

@media print {
	.site-footer__columns {
		display: none;
	}
}

/* --------------------------------------------------------------- header --- */

/*
 * Room for the sticky header whenever the page scrolls something into view.
 *
 * `position: sticky` means no compensating body padding is needed for normal
 * reading — but it is still an opaque bar at z-index 100, so anything the
 * browser scrolls to the top of the viewport lands behind it. That covers
 * in-page anchors, the skip link's target, and keyboard focus: tabbing to a
 * link that sits near the top of a section put it at y=1, under 154px of
 * masthead.
 *
 * Found when V3 §23 moved the four section links up beside their headings,
 * which is exactly "near the top of a section" — but the gap was never
 * specific to them.
 *
 * 170px clears the tallest measured stuck header (161 on a phone, 154 on
 * desktop) with a little air above whatever was scrolled to.
 */
html {
	scroll-padding-block-start: 170px;
}

/*
 * Sticky primary nav (§5.2). position:sticky rather than fixed, so the header
 * never overlaps content and no compensating body padding is needed.
 */
.site-header {
	position: sticky;
	inset-block-start: 0;
	z-index: 100;
	background-color: var(--wp--preset--color--surface);
	border-block-end: 1px solid var(--wp--preset--color--border);
}

.site-header.is-stuck {
	box-shadow: 0 2px 8px rgb(var(--jmns-shade-rgb) / 0.12);
}

/* Collapsed height when stuck, as §5.2 asks. */
.site-header.is-stuck > .wp-block-group {
	padding-block: var(--wp--preset--spacing--20);
}

.menu-toggle {
	display: none;
	align-items: center;
	gap: 0.5rem;
	min-block-size: 44px;
	padding-inline: 0.75rem;
	border: 1px solid var(--wp--preset--color--interactive);
	background: transparent;
	color: var(--wp--preset--color--text-primary);
	font: inherit;
	font-weight: 600;
	cursor: pointer;
	transition: background-color var(--t-fast) var(--ease);
}

/*
 * An outlined control tints; it does not fill. Declared because the shared
 * button hover no longer reaches it, and a control with no feedback at all
 * would be a worse answer than the wrong feedback it had.
 */
.menu-toggle:hover {
	background-color: var(--wp--preset--color--surface-tint);
	box-shadow: none;
}

/* ----------------------------------------------------------- submenus --- */

.primary-navigation li {
	position: relative;
}

.primary-navigation .has-submenu > a {
	padding-inline-end: 0.25rem;
}

.submenu-toggle {
	/*
	 * 28 wide, not 44. The chevron is centred in its box, so a 44px box put it
	 * 24px clear of the label it belongs to and it read as floating between two
	 * items rather than belonging to either. The row is 44px tall, which is
	 * where the height of the target comes from, and 28x44 still clears the
	 * 24x24 SC 2.5.8 asks for.
	 */
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-inline-size: 28px;
	min-block-size: 44px;
	padding: 0;
	border: 0;
	border-radius: var(--r-input);
	background: transparent;
	color: inherit;
	cursor: pointer;
	transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}

/*
 * A hint, not a button.
 *
 * This is a secondary control sitting beside a link, so it takes the same quiet
 * tint the panel rows take rather than announcing itself. It used to fill solid
 * dark green, which was the shared button hover reaching it — see the :where()
 * note on that rule.
 */
.submenu-toggle:hover {
	background-color: var(--wp--preset--color--surface-tint);
	color: var(--wp--preset--color--interactive);
	box-shadow: none;
}

.submenu-toggle__icon {
	display: inline-flex;
	inline-size: 12px;
	block-size: 12px;
	transition: rotate var(--t-fast) var(--ease);
}

.submenu-toggle__icon svg {
	inline-size: 100%;
	block-size: 100%;
	display: block;
}

.has-submenu.is-open .submenu-toggle__icon {
	rotate: 180deg;
}

.sub-menu {
	position: absolute;
	inset-block-start: 100%;
	inset-inline-start: 0;
	z-index: 10;
	/*
	 * Sized to its longest label rather than pinned at 15rem, which cut
	 * "JURNAL FATWA DAN FALAK SELANGOR (JUFFAS)" and "Lawatan Balai Cerap
	 * Selangor" to the panel edge with nothing either side of them. The ceiling
	 * stops one long item stretching a panel across the page.
	 */
	min-inline-size: 17rem;
	max-inline-size: 24rem;
	inline-size: max-content;
	/*
	 * PERKHIDMATAN holds twelve items and grows to sixteen when its own submenu
	 * opens inline below. Without a ceiling that runs off the bottom of a laptop
	 * screen with no way to reach the end.
	 *
	 * 40rem, not 32: twelve items measure 554px, so the tighter ceiling made the
	 * ordinary case scroll for the sake of the deep one. At this height the only
	 * panel that scrolls is one with a branch expanded inside it.
	 *
	 * The viewport figure subtracts the chrome above the panel rather than
	 * taking a flat percentage of the window. A plain 78vh looked right on a
	 * 900px viewport and put the panel 38px below the fold on a 700px one,
	 * because the panel does not start at the top of the screen — it starts
	 * under a masthead, a search row and the nav itself. dvh so a phone's
	 * collapsing toolbar is counted as it changes.
	 */
	max-block-size: min(40rem, calc(100vh - 13rem));
	max-block-size: min(40rem, calc(100dvh - 13rem));
	overflow-y: auto;
	margin: 0;
	padding-block: var(--wp--preset--spacing--20);
	padding-inline: 0;
	background-color: var(--wp--preset--color--surface);
	border: 1px solid rgb(var(--jmns-hairline-rgb) / var(--jmns-a-rule));
	border-block-start: 2px solid var(--wp--preset--color--primary-light);
	border-radius: 0 0 var(--r-input) var(--r-input);
	box-shadow: 0 4px 14px rgb(var(--jmns-shade-rgb) / 0.12);
	list-style: none;
	flex-direction: column;
}

/*
 * The top-level row wraps; a panel must not.
 *
 * A panel inherits `flex-wrap: wrap` from `.primary-navigation ul`, and a
 * wrapping *column* whose height is capped starts a second column beside the
 * first — so PERKHIDMATAN's twelve items spilled into a phantom column 400px to
 * the right, carrying the Penerbitan branch out of the panel with them.
 * Overflow is what the height cap is for, and it scrolls.
 *
 * The layout half of `.primary-navigation ul` is still element-plus-class, so
 * this keeps its ancestor — it is overriding that rule, not the reset.
 */
.primary-navigation .sub-menu {
	flex-wrap: nowrap;
}

.sub-menu a {
	display: block;
	padding-block: 0.5rem;
	padding-inline: var(--wp--preset--spacing--30);
	border-block-end: 0;
}

/*
 * Shorter rows inside a panel.
 *
 * Every link inherited the 44px minimum meant for the top row, so twelve items
 * measured 554px — a dropdown taller than half a laptop screen, which is hard
 * to scan and needs a scroll it should not need. 36px is comfortable for a
 * pointer and still well past the 24x24 SC 2.5.8 asks of a target; the 44px
 * AAA figure is a touch measurement and the mobile branch below keeps it.
 */
.primary-navigation .sub-menu a {
	min-block-size: 36px;
	padding-block: 0.35rem;
	/* Level two: the reading level, so the largest of the three and set in
	   sentence case with no tracking. */
	font-size: 0.875rem;
	font-weight: 500;
	letter-spacing: 0;
	line-height: 1.35;
}

/* Level three: subordinate to the group label above it. */
.primary-navigation .sub-menu--group a {
	font-size: 0.8125rem;
	font-weight: 400;
}

/*
 * The row, not the text, is the target. A 17rem panel where only the words
 * highlight makes a twelve-item list read as twelve separate small things.
 */
.sub-menu a:hover,
.sub-menu a:focus-visible {
	background-color: var(--wp--preset--color--surface-tint);
	border-block-end-color: transparent;
}

.sub-menu [aria-current] {
	background-color: var(--wp--preset--color--surface-tint);
}

/* ------------------------------------------------------------ groups --- */

/*
 * The third level is a labelled group, not a second disclosure.
 *
 * It was a nested dropdown, and being one cost more than it paid. Nine items
 * live down there — four Bahagian pages, four Penerbitan pages, one report —
 * and reaching any of them took a second click through a control that had
 * already produced three layout bugs of its own. Shown as a group inside the
 * open panel, the panel says everything it holds in one look.
 */
.sub-menu--group {
	position: static;
	inline-size: auto;
	min-inline-size: 0;
	max-inline-size: none;
	max-block-size: none;
	overflow: visible;
	margin-block-end: var(--wp--preset--spacing--20);
	margin-inline-start: var(--wp--preset--spacing--30);
	padding-block: 0;
	background: transparent;
	border: 0;
	border-inline-start: 1px solid var(--wp--preset--color--border);
	border-radius: 0;
	box-shadow: none;
}


/*
 * The label reads as a heading, so it is set as one: smaller, spaced, and in
 * secondary ink. It is not a target — where the container has no page behind it
 * the walker renders a span rather than a link to `#`.
 */
.primary-navigation .sub-menu .sub-menu__label {
	display: block;
	min-block-size: 0;
	padding-block: var(--wp--preset--spacing--20) 0.15rem;
	padding-inline: var(--wp--preset--spacing--30);
	font-size: 0.75rem;
	font-weight: 700;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--wp--preset--color--text-secondary);
}

/*
 * Qualified to outrank `.primary-navigation .sub-menu a`, which was setting the
 * label back to 14px/500 — so a group that happens to be a page (Penerbitan)
 * was styled as one of its own children rather than as their heading.
 *
 * One that is also a page keeps its underline on hover; a span cannot hover.
 */
.primary-navigation .sub-menu a.sub-menu__label:hover,
.primary-navigation .sub-menu a.sub-menu__label:focus-visible {
	color: var(--wp--preset--color--interactive);
	text-decoration: underline;
	background: transparent;
}

/* ----------------------------------------------------- two columns --- */

/*
 * Long panels split in two.
 *
 * :has() counts for us, so no class has to be maintained by hand or by script.
 * Eight is the threshold rather than five: at five to seven the second column
 * is two or three items long and the panel reads as though something failed to
 * load. PERKHIDMATAN holds sixteen, PENGURUSAN nine, WARGA JMNS eight.
 *
 * Only above 62rem. A two-column panel is 32rem wide, and below that width the
 * items it hangs from sit far enough right that it would run off the edge — a
 * panel that has to be dragged into view is worse than a panel that scrolls.
 */
@media (min-width: 62rem) {
	.primary-navigation > ul > li > .sub-menu:has(> li:nth-child(8)) {
		display: block;
		inline-size: 32rem;
		min-inline-size: 0;
		max-inline-size: none;
		columns: 2;
		column-gap: var(--wp--preset--spacing--30);
	}

	/*
	 * A group must not be split down the middle by a column break — its label
	 * would end one column and its items begin the next.
	 */
	.primary-navigation > ul > li > .sub-menu:has(> li:nth-child(8)) > li {
		break-inside: avoid;
	}

	/*
	 * The last items open leftward from their own right edge. Anchored to the
	 * left they would leave the viewport: WARGA JMNS starts at 578px, and a
	 * 32rem panel from there ends 34px past a 1256px page.
	 */
	.primary-navigation > ul > li:nth-last-child(-n+3) > .sub-menu {
		inset-inline-start: auto;
		inset-inline-end: 0;
	}
}

@media (prefers-reduced-motion: reduce) {
	.submenu-toggle__icon {
		transition: none;
	}
}

/* ------------------------------------------------------------- mobile --- */

@media (max-width: 48rem) {
	.menu-toggle {
		display: inline-flex;
	}

	/*
	 * Gone from the top of the page before it paints, not after.
	 *
	 * `navigation.js` moves this bar into the menu at this exact breakpoint,
	 * but it runs after first paint, so the bar was drawn above the header,
	 * took 53px on a tablet and 105px on a phone, and then vanished — pulling
	 * the header, the prayer bar and the whole page up with it. Measured at
	 * 0.1244 CLS at 390px and 0.0652 at 768px, from one shift.
	 *
	 * Scoped to `.jmns-js`, which the head sets before the stylesheet applies:
	 * only hide the bar for a visitor whose browser will run the script that
	 * puts it back. Without scripts it stays where the server put it, which is
	 * the whole point of leaving it in the markup.
	 */
	.jmns-js .jmns-utility:not( .jmns-utility--in-menu ) {
		display: none;
	}

	/*
	 * The utility links, once the script has moved them into the menu.
	 *
	 * The bar is a centred row of a link list and a language select. Inside the
	 * menu it has to be a column of full-width rows like the navigation above
	 * it, or the select floats up beside the links and lands on top of one of
	 * them — which is what the first cut did.
	 *
	 * Separated by a rule rather than a heading: these are the same kind of
	 * thing as the menu items, just less used, and a heading over three links is
	 * more furniture than help.
	 */
	.jmns-utility--in-menu {
		display: flex;
		flex-direction: column;
		align-items: stretch;
		gap: var(--wp--preset--spacing--20);
		margin-block-start: var(--wp--preset--spacing--20);
		padding-block: var(--wp--preset--spacing--20) 0;
		padding-inline: 0;
		border-block-start: 1px solid var(--wp--preset--color--border);
		background: none;
		min-block-size: 0;
	}

	.jmns-utility--in-menu .jmns-utility__list {
		flex-direction: column;
		align-items: stretch;
		gap: 0;
	}

	.jmns-utility--in-menu .jmns-utility__link {
		justify-content: flex-start;
		min-block-size: 44px;
		padding-inline: 0;
	}

	/* The select is a control, so it gets the width a control expects. */
	.jmns-utility--in-menu .jmns-translate {
		inline-size: 100%;
	}

	.jmns-utility--in-menu .jmns-translate__select {
		inline-size: 100%;
	}

	/*
	 * Brand and the menu button share the top row; search takes the next whole.
	 *
	 * It was the other way round — crest alone, then search and the button side
	 * by side — and the search paid for it. Sharing a row with a 44px control
	 * and its gap left the field about 235px wide, which truncated its own
	 * placeholder to "Cari fatwa, hukum," and made the one thing most people
	 * open this site to use look like an afterthought.
	 *
	 * The hamburger is a 44px square and the crest does not need the full width,
	 * so they sit together comfortably and the field below runs edge to edge.
	 * Same two rows, same height, a search field that fits its own words.
	 */
	.site-header__masthead {
		display: grid;
		grid-template-columns: minmax(0, 1fr) auto;
		grid-template-areas:
			"brand menu"
			"search search";
		gap: var(--wp--preset--spacing--30);
		align-items: center;
		min-block-size: 0;
		padding-block: var(--wp--preset--spacing--30);
	}

	.site-header__brand {
		grid-area: brand;
	}

	.site-search {
		grid-area: search;
		min-inline-size: 0;
	}

	.menu-toggle {
		grid-area: menu;
		/* 44px minimum, the brief's floor and WCAG 2.5.5's. */
		min-block-size: 44px;
		min-inline-size: 44px;
	}

	/*
	 * Sticky on a short viewport would eat the screen. A phone showing a
	 * masthead, a search field and a nav toggle has little left for content,
	 * so the header scrolls away here and comes back on the way up.
	 */
	.site-header {
		position: static;
	}

	.primary-navigation {
		display: none;
		inline-size: 100%;
	}

	.primary-navigation.is-open {
		display: block;
	}

	.primary-navigation ul {
		flex-direction: column;
		align-items: stretch;
		gap: 0;
	}

	/*
	 * Submenus stack inline on mobile: an absolutely positioned dropdown would
	 * sit off-screen at this width. Same treatment the third level now gets on
	 * desktop, so both are the one idea.
	 *
	 * The size and scroll limits are reset because there is no floating panel
	 * here to bound — the list is part of the page and scrolls with it.
	 */
	.sub-menu {
		position: static;
		inline-size: auto;
		min-inline-size: 0;
		max-inline-size: none;
		max-block-size: none;
		overflow: visible;
		padding-block: 0;
		box-shadow: none;
		border: 0;
		border-radius: 0;
		border-inline-start: 2px solid var(--wp--preset--color--primary-light);
	}

	.sub-menu {
		margin-inline-start: var(--wp--preset--spacing--30);
		padding-block: 0;
	}

	.sub-menu--group {
		margin-inline-start: var(--wp--preset--spacing--30);
	}

	/* Fingers, not a pointer: the 44px target comes back. */
	.primary-navigation .sub-menu a {
		min-block-size: 44px;
		padding-block: 0.5rem;
		font-size: 1rem;
	}
}

/* ========================================================= mobile === */

/*
 * Below 48rem, per design/review_mobile.txt.
 *
 * Not a scaled-down desktop: the desktop type ramp is built for a 1200px band
 * where a 24px card title is one line, and at 375px the same title is four. The
 * numbers here are the brief's — titles 15-16px, metadata 12-13px, summaries
 * capped at three lines, 44px targets, 16px page gutter — measured back at 375
 * and 430 rather than assumed.
 */
@media (max-width: 48rem) {

	/* ------------------------------------------------------ card type -- */

	/*
	 * 1rem, down from 1.5rem on fatwa and 1.25rem on berita. A 71-character
	 * median fatwa title at 24px is four lines on a 375px screen and pushes the
	 * status and date off the first card before anything else is read.
	 */
	.fatwa-card--compact .fatwa-card__title,
	.poster-card__title {
		font-size: 1rem;
		line-height: 1.45;
	}

	/*
	 * Taudhih keeps 18px on a phone. It was grouped with the cards above at
	 * 1rem, which §15 puts below its 17-19 range — and the reason for the group
	 * has expired: these are full-width editorial entries now, not titles in a
	 * narrow card, so a longer one has the whole screen to wrap into.
	 */
	.taudhih-entry__title {
		font-size: 1.125rem;
		line-height: 1.4;
	}

	/*
	 * The featured ruling keeps a step above the three beside it.
	 *
	 * It was in the rule above, so on a phone it read at exactly the size of
	 * the compact cards and the composition the client asks us to refine —
	 * one featured fatwa and three secondary — became four identical cards.
	 * Desktop separates them 22 against 18 and mobile separated them not at
	 * all.
	 *
	 * The size the rule above was avoiding was 24px. This is 18, and the title
	 * is clamped to four lines regardless, so the status and date it was
	 * written to protect cannot be pushed off the card whatever the title
	 * runs to.
	 */
	.fatwa-card--featured .fatwa-card__title {
		/*
		 * 20px, up from 18 alongside §14 taking the compact cards to 16. The
		 * desktop step is 28 against 16 and a phone cannot carry that — a
		 * 71-character median title at 28px is most of the screen — but 18
		 * against 16 was barely a step at all. Four lines at worst here, under
		 * the clamp of four either way.
		 */
		font-size: 1.25rem;
		line-height: 1.3;
	}

	.fatwa-card__date,
	.fatwa-card__format,
	.tender-card__date,
	.tender-card__ref,
	.poster-card__meta,
	.entry-meta {
		font-size: 0.8125rem;
	}

	/* Three lines, the brief's cap. The full text stays in the markup. */
	.news-card__excerpt,
	.taudhih-entry__excerpt,
	.fatwa-card__excerpt p {
		display: -webkit-box;
		-webkit-box-orient: vertical;
		-webkit-line-clamp: 3;
		overflow: hidden;
	}

	/* --------------------------------------------------- touch targets -- */

	/*
	 * 44px on every link that stands alone in a list. Written as padding rather
	 * than a height so the text keeps its own line-height and the row grows
	 * around it — a fixed height would centre a two-line footer link badly.
	 */
	/*
	 * An email address and a domain are single unbreakable tokens. At 200% text
	 * zoom "mail@muftiselangor.gov.my" measured 368px inside a 375px page and
	 * took the footer 41px past the edge. WCAG 1.4.4 asks that text scale to
	 * 200% without loss of content, so these have to be allowed to break.
	 */

	.jmns-contact__list a,
	.home-stats__label a,
	.jmns-gov-links__list a,
	.jmns-legal__links a,
	.jmns-qr__link a,
	.jmns-partners__item a,
	.home-section__more a {
		display: inline-flex;
		align-items: center;
		min-block-size: 44px;
	}

	/* ------------------------------------------------------- posters -- */

	/*
	 * A swipeable row rather than three stacked squares.
	 *
	 * Stacked, the three posters are 1,150px of scrolling for one section on a
	 * 375px screen. As a scroll-snap row they are one screen, and the sliver of
	 * the next card is the affordance — it is what says the row continues, which
	 * a scrollbar on a phone does not.
	 */
	.home-section__grid--posters {
		display: grid;
		grid-auto-flow: column;
		/*
		 * 78%, so about a third of the next card shows at every width: 32% at
		 * 320, 31% at 390, 30% at 768.
		 *
		 * It was 68%, and 68% was measured against a row whose own geometry was
		 * broken — see the bleed below. The row stopped 64px short of the right
		 * edge, so the second card was cut in half by the container rather than
		 * running off the screen, and widening the track would only have moved
		 * the cut. With the bleed fixed, 68% leaves half of the second card
		 * showing, which is exactly what the client read as "clipped mid-way".
		 * A third of a card is a peek; half of one is a mistake.
		 */
		grid-auto-columns: 78%;
		grid-template-columns: none;
		gap: var(--wp--preset--spacing--40);
		overflow-x: auto;
		scroll-snap-type: x mandatory;
		/*
		 * Full bleed to the band, with the page gutter put back as padding.
		 *
		 * This is the "no gutter on Dari Meja Mufti" defect. The row bled with
		 * `margin-inline: calc(spacing--40 * -1)` — 16px — while the shell it
		 * sits in is inset by spacing--50, 24px, and it kept the shell's own
		 * `inline-size` at the same time. Those three cannot all hold, so the
		 * margins were over-constrained and the browser absorbed the difference
		 * into the end margin: at 390 the row ran from -16 to 326 instead of 0
		 * to 390 — 16px past the left edge and 64px short of the right one.
		 * 16px of padding then put the first card at exactly x=0, flush with
		 * the screen and 24px left of the heading above it.
		 *
		 * Cancelling the shell is what makes this hold from 320 up, rather than
		 * trying to out-margin it: the row *is* the band, and the gutter is
		 * stated once, in the same token the shell uses.
		 */
		inline-size: auto;
		margin-inline: 0;
		padding-inline: var(--wp--preset--spacing--50);
		/* Room for the focus ring, which the scroll container's own edge would
		   otherwise clip. */
		padding-block-end: 0.5rem;
		/* Snapping stops at the gutter, not at the screen edge, so a card that
		   has been swiped to lands on the heading's grid line too. */
		scroll-padding-inline: var(--wp--preset--spacing--50);
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;
	}

	.home-section__grid--posters::-webkit-scrollbar {
		display: none;
	}

	/*
	 * `start`, not `center`.
	 *
	 * Centring undoes the gutter the moment the row is touched: the card being
	 * swiped to settles in the middle of the screen with slivers of both
	 * neighbours beside it, so nothing in the row lines up with the heading any
	 * more. `start` was what the deleted duplicate of this rule asked for.
	 */
	.home-section__grid--posters > .home-section__item {
		scroll-snap-align: start;
	}

	/* --------------------------------------------------- prayer times -- */

	/*
	 * Seven times in a scrollable row, not a four-wide grid that wraps.
	 *
	 * At 375px the grid gave each time 78px and broke the day across two rows,
	 * so Maghrib and Isyak sat under Imsak with nothing saying the sequence had
	 * continued. A prayer timetable is an ordered thing; wrapping it is what
	 * makes it stop reading as one.
	 *
	 * Scrolled, each time keeps a comfortable column and the order survives. The
	 * next prayer is highlighted already, and it is scrolled into view by the
	 * script, so the one that matters is what a visitor lands on.
	 */
	/*
	 * The track has to be allowed to shrink, not just the row inside it.
	 *
	 * `.prayer-bar__inner` is a grid, and a grid track sizes to its content by
	 * default — so the seven times made the track 579px wide and the page
	 * scrolled 22px, even though the row itself was set to scroll. `min-width:
	 * 0` on the child cannot fix that; the track is what needs the floor.
	 */
	.prayer-bar__inner {
		/*
		 * Two tracks: the date, and the zone chip beside it.
		 *
		 * Declared here rather than with the rest of the mobile bar because this
		 * is the rule that runs last, and the tracks have to be explicit. Set to
		 * a single track, the `grid-column: 2` on the zone chip created an
		 * *implicit* second column — which the grid will happily do — but
		 * `grid-column: 1 / -1` on the times counts to the end of the *explicit*
		 * grid, so the times row spanned one column of 250px while the chip sat
		 * in a phantom one beside it.
		 */
		grid-template-columns: minmax(0, 1fr) auto;
		column-gap: var(--wp--preset--spacing--30);
	}

	/*
	 * Five across, and nothing scrolls.
	 *
	 * This was a scroll-snapping flex row of all seven entries at
	 * `min-inline-size: 4.5rem` each — 590px of content in a 342px track. The
	 * browser then scrolled the next prayer into view, which pushed Subuh off
	 * the left edge, so a phone showed four of seven and looked truncated. That
	 * is what the client reported, and the concept they sent shows the five
	 * obligatory prayers together with none hidden.
	 *
	 * So the five share the width as a grid and always fit. Imsak and Syuruk
	 * come out of the compact bar only — both remain on the full schedule at
	 * /jadual-waktu-solat/, which is where somebody looking for Imsak in
	 * Ramadan is going anyway.
	 */
	.prayer-bar__times {
		display: grid;
		grid-template-columns: repeat(5, minmax(0, 1fr));
		gap: 0.25rem;
		overflow-x: visible;
		/*
		 * No full-bleed here. The poster row can bleed because its container
		 * carries the page gutter to give back; this bar has zero padding, so a
		 * negative margin simply pushed 22px of times past the viewport and
		 * turned a scrolling row into a scrolling page.
		 *
		 * min-inline-size: 0 is what actually lets it scroll: a flex item's
		 * default `min-width: auto` sizes it to its content, so the container
		 * grew to fit seven times instead of clipping them.
		 */
		min-inline-size: 0;
		max-inline-size: 100%;
		scrollbar-width: none;
	}

	.prayer-bar__times::-webkit-scrollbar {
		display: none;
	}

	.prayer-bar__time[data-prayer="imsak"],
	.prayer-bar__time[data-prayer="syuruk"] {
		display: none;
	}

	.prayer-bar__time {
		/*
		 * No floor on the width now: five columns divide 342px into about 68px
		 * each, and a floor of 4.5rem would put the overflow straight back.
		 */
		min-inline-size: 0;
		/*
		 * A containing block for the hidden "Waktu seterusnya:" label.
		 *
		 * `.screen-reader-text` is `position: absolute` with no offsets, so it
		 * resolves against the nearest *positioned* ancestor — which, with every
		 * cell static, was outside this scrolling row. Once the row scrolled to
		 * bring the next prayer into view, that 1px span sat at x=396 on a 375px
		 * screen and the page gained 22px of real horizontal scroll. Relative
		 * here puts it back inside the row, where the overflow clips it.
		 */
		position: relative;
	}

	/* ---------------------------------------------------- statistics -- */

	/*
	 * 12px between the figures rather than 24px, and 44px of caption box rather
	 * than 24px.
	 *
	 * The gap is what the band can afford here: 24px between four shares costs
	 * 72px of the 272px the shell has at 320, and it buys nothing, because at
	 * this end the figures are held apart by their own width rather than by the
	 * gutter — three across at 320 needs 195px of digits either way and the row
	 * has 272. Spending a quarter of the row on air is most of what made the
	 * band read as cramped.
	 *
	 * The caption box follows the touch target, which the shared rule under this
	 * same 48rem query takes from 24px to 44px. Both numbers are here rather
	 * than on the link because the caption has to stand in the same box whether
	 * or not it holds one — see .home-stats__label.
	 *
	 * There is no padding rule to go with either any more. The item used to be
	 * given 12px of inline padding to hold the figure off the divider, and
	 * cancelled here because at 320 that padding was taking 24px out of an 83px
	 * column and pushing "diterbitkan" — one 66px word, and it does not break —
	 * outside it. Neither half of that is still true: the divider is drawn in
	 * the gap and needs nothing from the column, and a column can no longer be
	 * narrower than the word standing in it.
	 */
	.home-stats__grid {
		--stat-gap: var(--wp--preset--spacing--30);
		--stat-label-target: 44px;
	}

	.home-stats__label {
		font-size: 0.8125rem;
		line-height: 1.3;
	}
}

/*
 * Footer disclosures, built by footer-accordion.js on a phone.
 *
 * Styled here rather than inline so the closed state is right on first paint —
 * the script runs deferred, and a summary that restyles after the footer has
 * drawn is a visible jump.
 */
@media (max-width: 48rem) {
	.jmns-footer-block__summary {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: var(--wp--preset--spacing--30);
		min-block-size: 44px;
		cursor: pointer;
		list-style: none;
	}

	.jmns-footer-block__summary::-webkit-details-marker {
		display: none;
	}

	/* The chevron, drawn rather than loaded: one glyph, rotated when open. */
	.jmns-footer-block__summary::after {
		content: "";
		flex: 0 0 auto;
		inline-size: 0.55em;
		block-size: 0.55em;
		border-inline-end: 2px solid currentColor;
		border-block-end: 2px solid currentColor;
		rotate: 45deg;
		translate: 0 -0.15em;
		transition: rotate var(--t-fast) var(--ease);
	}

	.jmns-footer-block__disclosure[open] .jmns-footer-block__summary::after {
		rotate: -135deg;
		translate: 0 0.1em;
	}

	/*
	 * The rule belongs to the row, not to the label.
	 *
	 * `.jmns-footer__heading` carries its own hairline, which is right in a
	 * column where the heading is a block. Inside the summary it is a flex item
	 * beside a chevron, so it shrank to the width of its own text and each
	 * accordion got a rule as long as its label — 130px under PAUTAN, 185 under
	 * IKUTI KAMI, 520 under KAJIAN KEPUASAN PELANGGAN, against the full-width
	 * rule of the one column that is not an accordion.
	 *
	 * So the summary takes the rule and the heading gives it up. Same with the
	 * two-line reservation: it exists to align four rules across a desktop row
	 * and there is no row here, only stacked headings, where it was adding an
	 * empty line under each one.
	 */
	.jmns-footer-block__summary {
		padding-block-end: var(--wp--preset--spacing--30);
		border-block-end: 1px solid var(--footer-divider);
	}

	.jmns-footer-block__summary .jmns-footer__heading {
		margin-block: 0;
		padding-block-end: 0;
		border-block-end: 0;
	}

	/*
	 * Air on the other side of the divider.
	 *
	 * The summary pads itself away from its own rule and the panel below it did
	 * not, so an opened section put its first row hard against the line —
	 * measured at 0px for all three. The social icons and the survey card show
	 * it worst because both are solid shapes; a list of links hides it better
	 * but is just as cramped.
	 *
	 * Padding rather than margin, and the same token the summary uses above, so
	 * the line sits centred in its own space rather than closer to one side.
	 */
	.jmns-footer-block__summary + * {
		padding-block-start: var(--wp--preset--spacing--30);
	}

	/*
	 * And the reservation goes for every heading at this width, not only the
	 * ones in a summary. It reserves two lines so that four rules line up
	 * across a desktop row; stacked on a phone there is nothing to line up
	 * with, and the column that is not an accordion — Hubungi Kami — was
	 * carrying an empty line between its label and its rule while the three
	 * accordions under it sat tight.
	 */
	.jmns-footer__heading {
		min-block-size: 0;
	}

	@media (prefers-reduced-motion: reduce) {
		.jmns-footer-block__summary::after {
			transition: none;
		}
	}
}

/* ======================================================= reveal === */

/*
 * Scoped to `.jmns-motion`, which reveal.js puts on the root only when the
 * observer exists and the visitor has not asked for reduced motion. Without
 * that class none of this applies and every section is simply visible — which
 * is the state a failed script, an old browser and a reduce-motion setting all
 * arrive at.
 *
 * Opacity and a 10px rise. No scale, no blur, no stagger beyond four steps:
 * the brief asks for calm and official, and motion here is meant to be noticed
 * only in its absence.
 */
.jmns-motion .jmns-reveal {
	opacity: 0;
	translate: 0 10px;
	transition:
		opacity 380ms var(--ease, ease-out),
		translate 380ms var(--ease, ease-out);
}

.jmns-motion .jmns-reveal.is-revealed {
	opacity: 1;
	translate: 0 0;
}

/*
 * The items inside arrive just behind their section. `--jmns-reveal-step` is
 * set per item by the script and capped at four, so a ten-tile grid does not
 * trail half a second behind itself.
 */
.jmns-motion .jmns-reveal .home-section__item,
.jmns-motion .jmns-reveal .jmns-tile,
.jmns-motion .jmns-reveal .home-stats__item {
	opacity: 0;
	translate: 0 8px;
	transition:
		opacity 320ms var(--ease, ease-out),
		translate 320ms var(--ease, ease-out);
	transition-delay: calc( var(--jmns-reveal-step, 0) * 55ms );
}

.jmns-motion .jmns-reveal.is-revealed .home-section__item,
.jmns-motion .jmns-reveal.is-revealed .jmns-tile,
.jmns-motion .jmns-reveal.is-revealed .home-stats__item {
	opacity: 1;
	translate: 0 0;
}

/*
 * Belt and braces. The global catch-all above already flattens every duration,
 * and the script never adds `.jmns-motion` under this query — but if either
 * ever changes, content must not be left at opacity 0.
 */
@media (prefers-reduced-motion: reduce) {
	.jmns-motion .jmns-reveal,
	.jmns-motion .jmns-reveal .home-section__item,
	.jmns-motion .jmns-reveal .jmns-tile,
	.jmns-motion .jmns-reveal .home-stats__item {
		opacity: 1;
		translate: none;
		transition: none;
	}
}

/* ============================================== first-entry intro === */

/*
 * The branded loader and the page reveal behind it, once per browser session —
 * design/jmns-first-entry-loader-page-reveal.md.
 *
 * Everything below is scoped to `.jmns-intro` on the root element, which the
 * inline head script in inc/enqueue.php adds only after it has decided the
 * intro should run. Without that class not one of these rules matches and the
 * portal is the page it has always been.
 *
 * That is the same inversion as the section reveals above, and for a harder
 * reason. The document's §9 puts the hidden state in the stylesheet and reveals
 * it from script; written that way, a 404 on one JavaScript file leaves every
 * band of a state religious authority's portal at opacity 0 with nothing left
 * that can undo it. Hidden-by-default is a page that is one failure away from
 * being blank, and no transition is worth that.
 */

.portal-loader {
	display: none;
}

.jmns-intro .portal-loader {
	display: block;
	/* §19: fixed, inset 0, and the portal already rendered behind it. */
	position: fixed;
	inset: 0;
	/*
	 * Above everything the portal and the plugin put on screen — the chat
	 * launcher is at 9999, the search overlay at 9998, the sticky header at
	 * 100. The admin bar's 99999 is deliberately left above it: that is
	 * WordPress's chrome for an editor, not part of the portal being revealed.
	 */
	z-index: 10000;
	/*
	 * #FFFFFF exactly, and not a palette token, because the background is part
	 * of the logo asset.
	 *
	 * logo-jmns-loader.webp is flattened onto pure white with no alpha channel
	 * — transparency doubled the file size of an image that is only ever seen
	 * on this overlay. Warm off-white is the tempting change here, since it is
	 * the page's own ground (--background, #FAF8F2) and §4 permits it; making
	 * it would draw a visible white rectangle around the crest. If this ever
	 * has to become off-white, the asset has to be recut with alpha first.
	 */
	background-color: #fff;
}

/*
 * §3: the logo sits above the vertical centre, not on it, so there is visual
 * room beneath for the portal to rise into.
 *
 * The block is anchored by its own middle, so the offset below is where the
 * *block* centres — the logo is the tallest thing in it and its own centre
 * lands a little above that, inside §3's 40-43% band at every viewport height.
 */
.portal-loader__brand {
	position: absolute;
	inset-block-start: 45%;
	inset-inline: 0;
	translate: 0 -50%;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: var(--wp--preset--spacing--40, 1.5rem);
	padding-inline: var(--wp--preset--spacing--50, 1.5rem);
	/*
	 * §5: opacity and a 1.5% scale, nothing else. No rotation, no bounce, no
	 * stretch — this is a department crest and a state seal, and animating it
	 * like a splash screen is the one thing that would make a calm transition
	 * read as a cheap one.
	 */
	animation: jmns-loader-brand 200ms var(--ease, ease-out) both;
}

/*
 * 0.02 rather than 0, and it is not a rounding error.
 *
 * Chrome will not count a paint as the First Contentful Paint if the content is
 * fully transparent at the moment it is painted, and an opacity animation runs
 * on the compositor — so the crest is painted once, at opacity 0, and then never
 * repainted from the main thread while it fades in. There is nothing left for
 * the paint-timing detector to see. Measured on this page: first paint at 232ms,
 * the logo genuinely on screen at ~290ms, and FCP reported at 1392ms in four
 * runs out of six, whenever the animation happened to start a frame or two after
 * the first paint. The reported figure was out by 1.1 seconds.
 *
 * Two hundredths of one percent is not visible on a light crest against white —
 * the fade still reads as starting from nothing — but it is not *transparent*,
 * so the paint counts and the metric reports the moment the logo really did
 * start appearing. FCP is 220-244ms with this, against a 216-292ms baseline
 * before the loader existed.
 */
@keyframes jmns-loader-brand {
	from {
		opacity: 0.02;
		scale: 0.97;
	}

	to {
		opacity: 1;
		scale: 1;
	}
}

/*
 * A 2x asset at 320px on desktop, and 58vw below that — §18 asks for a smaller
 * logo on a phone, and 58vw leaves the progress line under it wider than the
 * wordmark rather than narrower, which is what makes the block read as one
 * stack instead of two objects.
 */
.portal-loader__logo {
	inline-size: min(320px, 58vw);
	block-size: auto;
}

/* §6: 260-320px of track at 2-3px, and no spinner beside it. */
.portal-loader__track {
	/* 68vw on a phone, inside §18's 60-70vw. */
	inline-size: min(300px, 68vw);
	block-size: 3px;
	overflow: hidden;
	border-radius: var(--r-pill);
	background-color: var(--wp--preset--color--border);
}

.portal-loader__progress {
	display: block;
	block-size: 100%;
	inline-size: 0;
	border-radius: inherit;
	background-color: var(--wp--preset--color--primary);
	/*
	 * §8 Phase B, ending at 88% and holding there on the keyframes' fill until
	 * the page is ready. Held rather than run to 100%, because a bar that
	 * reaches the end while the portal is still working has told the visitor
	 * something untrue — §11 and §15 both refuse that.
	 *
	 * 150-370ms rather than §8's 150-850ms, and this is the one place §8 and §11
	 * cannot both be had.
	 *
	 * §11 puts the whole transition inside 1.0-1.4s. Working back from that, the
	 * reveal takes 520ms and the loader's exit 240ms, which leaves the line about
	 * 370ms to travel in. §8's own Phase B would still be running when the reveal
	 * had finished. §8 calls its numbers recommended and §11 states a target, so
	 * the phase keeps its shape — it starts 150ms after the logo, it grows, it
	 * stops at 88% — and loses its length.
	 *
	 * Timed to arrive rather than to park. It reaches 88% at ~370ms after first
	 * paint and loader.js completes it at ~365ms, so the line is still moving
	 * when the hold ends and there is no frame in which it sits full-but-waiting.
	 * That was the risk worth checking: a bar that reaches 88% and then stops for
	 * half a second reads as a stall, which is the one thing a progress line must
	 * never say when nothing is wrong.
	 *
	 * Declared rather than driven from script, so it starts at this overlay's
	 * first paint instead of whenever the deferred loader.js arrives — on a slow
	 * connection those are seconds apart, and a bar that sits at zero until the
	 * JavaScript lands reads as a stall on exactly the connection the loader is
	 * there to reassure. loader.js pins whatever width it reached before
	 * completing it; see the comment there.
	 */
	animation: jmns-loader-progress 220ms 150ms cubic-bezier(0.33, 0, 0.2, 1) both;
}

@keyframes jmns-loader-progress {
	from {
		inline-size: 0;
	}

	to {
		inline-size: 88%;
	}
}

.portal-loader__text {
	margin: 0;
	font-size: var(--wp--preset--font-size--small);
	color: var(--wp--preset--color--text-secondary);
	/* 4.86:1 on white. §7 asks for small and secondary, not faint. */
}

/* §8 Phase D: the caption goes before the block moves, so the movement is the
   last thing on screen rather than one of two things happening at once. */
.portal-loader.is-complete .portal-loader__text {
	opacity: 0;
	transition: opacity 160ms linear;
}

/*
 * §8 Phase D and §10. The brand lifts 24px and the overlay takes it a further
 * 16px as it fades — the 24 to 40 the document asks for, split so the fade
 * starts fractionally after the lift and the block is seen to move before it is
 * seen to go.
 */
.portal-loader.is-leaving .portal-loader__brand {
	translate: 0 calc(-50% - 24px);
	transition: translate 200ms cubic-bezier(0.4, 0, 1, 1);
}

.portal-loader.is-leaving {
	opacity: 0;
	translate: 0 -16px;
	transition:
		opacity 180ms 50ms linear,
		translate 180ms 50ms cubic-bezier(0.4, 0, 1, 1);
}

/* ------------------------------------------------- the portal itself --- */

/*
 * §9, applied to the bands rather than to a wrapper around them.
 *
 * There is no `.portal-content` element and none is introduced. §17 asks that
 * the portal render in its final layout from the start and that the loader be
 * an overlay over it and nothing more, and a new wrapper div around the whole
 * document is a structural change to every page for the sake of 1.5 seconds.
 * Naming the bands costs one selector list and changes nothing about the
 * markup.
 *
 * All five move together, which is the point. Revealing `<main>` alone would
 * slide the content up from under a header and a prayer bar that stayed
 * perfectly still — the page would look like it was loading inside a frame
 * rather than arriving. They can travel because none of them is fixed: the
 * header is `position: sticky` and at scroll 0 sticky and static are the same
 * position, so it lifts with everything else and settles exactly where it
 * already is.
 *
 * The one thing that must not be inside this is the loader, which is fixed —
 * hence its place above the header in header.php, outside every element named
 * here.
 *
 * Transforms and opacity only, so there is no layout involved and therefore no
 * layout shift to contribute to CLS (§17).
 */
.jmns-intro .jmns-utility,
.jmns-intro .site-header,
.jmns-intro .prayer-bar,
.jmns-intro .site-main,
.jmns-intro .site-footer {
	opacity: 0;
	translate: 0 60px;
}

/*
 * The floating widgets fade but do not travel. They are pinned to the viewport
 * corner rather than to the page, so sliding them up 60px and back reads as a
 * control that has come loose; and left out altogether they would appear at
 * full strength through a portal that is still at 30% opacity, which makes a
 * chat launcher the first thing a visitor sees on the site.
 *
 * Named by stack membership rather than one by one, so a fourth control pinned
 * to a corner is covered by the arrangement it joined instead of by whoever
 * remembers this list exists. The scroll-to-top button is the reason: it is
 * `hidden` until a viewport has been scrolled, which is almost never true while
 * the intro is up — but a first entry that lands on an anchor near the foot of
 * a long ruling is exactly that case, and it is not the loader's job to be
 * lucky. The loader itself sits at 10000 and the stack at 95, so the button
 * cannot be drawn over the overlay either way; this is only the fade.
 */
.jmns-intro [data-jmns-stack] {
	opacity: 0;
}

.jmns-intro.is-portal-ready .jmns-utility,
.jmns-intro.is-portal-ready .site-header,
.jmns-intro.is-portal-ready .prayer-bar,
.jmns-intro.is-portal-ready .site-main,
.jmns-intro.is-portal-ready .site-footer,
.jmns-intro.is-portal-ready [data-jmns-stack] {
	opacity: 1;
	translate: 0 0;
	/* The floor of §9's 500-650ms, on §9's easing. The floor rather than the
	   middle because this is the longest single phase and §11's total is what it
	   is; it is still enough for the portal to read as rising into place rather
	   than appearing. */
	transition:
		opacity 520ms cubic-bezier(0.22, 1, 0.36, 1),
		translate 520ms cubic-bezier(0.22, 1, 0.36, 1);
}

/*
 * §18: 30-45px on a phone. 60px of travel on an 844px screen is a seventh of
 * the viewport, which stops being a reveal and starts being a scroll.
 */
@media (max-width: 47.999rem) {
	.jmns-intro .jmns-utility,
	.jmns-intro .site-header,
	.jmns-intro .prayer-bar,
	.jmns-intro .site-main,
	.jmns-intro .site-footer {
		translate: 0 36px;
	}
}

/*
 * Belt and braces, and it does real work here.
 *
 * The head script already skips the intro entirely when this query matches, so
 * this should be unreachable — but it is reachable if the setting is turned on
 * mid-transition, and it would be reachable if `matchMedia` were ever absent.
 * The site-wide rule above flattens every duration to 0.01ms under this query,
 * which for the intro alone is the worst of both: the animations end instantly
 * while the script's timers still hold the loader on screen for a second and a
 * half. So under reduced motion the loader is not drawn at all and the portal
 * is never hidden in the first place.
 */
@media (prefers-reduced-motion: reduce) {
	.jmns-intro .portal-loader {
		display: none;
	}

	.jmns-intro .jmns-utility,
	.jmns-intro .site-header,
	.jmns-intro .prayer-bar,
	.jmns-intro .site-main,
	.jmns-intro .site-footer,
	.jmns-intro [data-jmns-stack] {
		opacity: 1;
		translate: none;
	}
}

/*
 * The site's own reduce-motion switch, which is a separate answer to the same
 * question and has to be honoured the same way.
 *
 * The accessibility panel stores the preference in localStorage and its boot
 * script puts `data-jmns-motion="on"` on this same element — but it runs after
 * the head script that decides on the intro, so the decision cannot be made in
 * JavaScript without the theme reading the plugin's storage key. Declaring it
 * here needs neither: whatever the intro thinks it is doing, the loader is not
 * drawn and the portal is not hidden.
 *
 * The attribute is on the root beside the class, so these outrank the reveal
 * rules above on specificity rather than on source order.
 */
.jmns-intro[data-jmns-motion="on"] .portal-loader {
	display: none;
}

.jmns-intro[data-jmns-motion="on"] .jmns-utility,
.jmns-intro[data-jmns-motion="on"] .site-header,
.jmns-intro[data-jmns-motion="on"] .prayer-bar,
.jmns-intro[data-jmns-motion="on"] .site-main,
.jmns-intro[data-jmns-motion="on"] .site-footer,
.jmns-intro[data-jmns-motion="on"] [data-jmns-stack] {
	opacity: 1;
	translate: none;
}

/* --------------------------------------------- artikel archive --- */

/*
 * The poster grid outside a `.home-section`.
 *
 * The homepage row inherits its width and gutter from the section shell it sits
 * in; the archive has no shell, so without this the cards ran edge to edge while
 * the heading above them kept its margin — the page looked like two different
 * layouts stacked. Same shell value, stated once here.
 */
.archive-posters {
	inline-size: var(--shell, min(100% - 3rem, 75rem));
	margin-inline: auto;
	margin-block: var(--wp--preset--spacing--50) 0;
}

/*
 * Four across on a wide screen. The homepage row is three because it is a
 * sample; this is the whole series, and twelve to a page divides into four
 * without stranding a card on its own row.
 */
@media (min-width: 64rem) {
	.archive-posters {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}

/*
 * A grid on a phone, not the homepage's carousel.
 *
 * `.home-section__grid--posters` becomes a scroll-snap row below 48rem, which is
 * right for the three-card sample on the front page and wrong here: it turned a
 * page of twelve into a horizontal swipe with the pagination sitting under it,
 * and nothing said the row continued past the edge.
 *
 * Two columns. The posters are thumbnails at this size and the hadith inside
 * them is not readable either way — the title under each is what identifies it,
 * and two columns keeps twelve of them to a page somebody can actually scroll.
 */
@media (max-width: 48rem) {
	.archive-posters {
		grid-auto-flow: row;
		grid-auto-columns: auto;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		overflow-x: visible;
		scroll-snap-type: none;
		padding-inline: 0;
		margin-inline: auto;
	}

	.archive-posters > .home-section__item {
		scroll-snap-align: none;
	}
}

.archive-count {
	margin-block: var(--wp--preset--spacing--20) 0;
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--small);
}

/*
 * The one entry with no poster. Same square as its neighbours, so the row stays
 * a row — and labelled, because a blank tile in a grid of hadith graphics reads
 * as a failed image rather than as content the department has not sent yet.
 */
.poster-card__missing {
	display: flex;
	align-items: center;
	justify-content: center;
	aspect-ratio: 1 / 1;
	padding: var(--wp--preset--spacing--40);
	border: 1px dashed var(--wp--preset--color--border-strong);
	border-radius: var(--r-card);
	background-color: var(--wp--preset--color--surface-tint);
}

.poster-card__missing-text {
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--small);
	text-align: center;
	text-wrap: balance;
}

/* ------------------------------------------------- footer, at any size -- */

/*
 * The footer's unbreakable strings, wrappable at every width.
 *
 * These rules lived inside the phone breakpoint, which was where the problem
 * had been *found* rather than where it existed. "mail@muftiselangor.gov.my" is
 * one token whatever the viewport, and `.jmns-qr__link` is an inline-flex row —
 * which does not wrap its line — so the moment anything enlarges the text the
 * footer ran past the page edge. On a 1280 screen with the reading-preferences
 * panel at its largest step that was 28px of real horizontal scroll on every
 * page of the site.
 *
 * A rule that only holds at one text size is not a fix; it is the same bug with
 * a narrower reproduction.
 */
.jmns-contact__list a,
.jmns-gov-links__list a,
.jmns-qr__caption,
.jmns-qr__link a {
	overflow-wrap: anywhere;
}

.jmns-qr__link {
	flex-wrap: wrap;
}

/*
 * Instruction, not content. The caption explains what to do with the code
 * beside it; the link is the thing to act on, so it keeps full strength.
 */
.jmns-qr__caption {
	color: var(--footer-muted);
}

/* ------------------------------------- artikel: poster and rail -- */

/*
 * Dari Meja Mufti uses the whole band without a column of empty page.
 *
 * These entries carry no prose — a poster, the line that opens it, and that is
 * the article — so a 672px square sat in a 1200px band with 528px blank beside
 * it. There is no measure to widen here; there is a half to use. The artwork
 * keeps the first column and the related pieces take the second, moving up
 * from underneath where they followed the poster.
 *
 * The poster grows to 736 as a consequence, which suits an entry whose artwork
 * is its content.
 *
 * Not the rulings and not the reports. A fatwa is a legal document read start
 * to finish with its flipbook already spanning the band; berita carried this
 * frame for a while and the department asked for it back the way it was.
 *
 * Grid tracks are stated with `minmax(0, …)`. Making a container a grid when
 * its children carry their own widths is how the homepage sections collapsed
 * earlier — a percentage resolving against a track the content is also sizing.
 */
/*
 * 69rem, from the arithmetic. The rail needs about 280px to hold a headline
 * and its date without every line wrapping twice; with a 736px column and a
 * 40px gutter that wants 1,056px of band, which is a 1,104px viewport once the
 * page margins are paid. At 64rem the rail came out 200px and the reports in
 * it were three lines each — below that width the article is better off with
 * them underneath.
 */
@media (min-width: 69rem) {
	.single-jmns_artikel .entry-content {
		display: grid;
		grid-template-columns: minmax(0, 46rem) minmax(0, 1fr);
		column-gap: var(--wp--preset--spacing--70);
		align-items: start;
	}

	/* Everything reads in the first column unless it says otherwise. */
	.single-jmns_artikel .entry-content > * {
		grid-column: 1;
		max-inline-size: 46rem;
	}

	/* The gallery still gets the whole band; it is why the band is this wide. */
	.single-jmns_artikel .entry-content > .jmns-gallery {
		grid-column: 1 / -1;
		max-inline-size: none;
	}

	/*
	 * The rail. Row 1 so it starts level with the first paragraph, and sticky
	 * so it stays with the reader down a long report rather than scrolling away
	 * in the first screen.
	 */
	.single-jmns_artikel .entry-content > .jmns-related {
		grid-column: 2;
		/*
		 * Spans the column rather than sitting in row one.
		 *
		 * At `grid-row: 1` the rail shared a row with the opening paragraph and
		 * the row took the taller of the two, so a 280px block of links left
		 * 180px of white between the first paragraph and the second. Spanning
		 * lets every prose row size to its own text.
		 */
		grid-row: 1 / span 99;
		align-self: start;
		position: sticky;
		inset-block-start: var(--wp--preset--spacing--90);
		max-inline-size: none;
		margin-block-start: 0;
	}
}

/* ----------------------------------- documents and tables, full width -- */

/*
 * A measure exists to keep a line of text readable. These are not lines of
 * text, and the 42rem cap on `.entry-content > *` was reaching them anyway.
 *
 * It started as a fatwa problem — a gazette scan whose entire content is the
 * document, rendered 672px wide inside a 1200px article with 264px of empty
 * page either side — and was fixed for `.single-jmns_fatwa` alone. The same
 * squeeze was still happening everywhere else the same components appear:
 *
 *   penerbitan   9 of them carry a flipbook, all at 672px
 *   takwim-solat the live timetable and each scanned month, all at 672
 *
 * The takwim viewer is the worst of them: an eight-column prayer timetable
 * that had already earned `.jmns-table--wide` and could not use it, because
 * the `details` around it was capped and `min(100%, …)` resolves against the
 * parent. It scrolled sideways inside a page with 500px to spare.
 *
 * So the exception is by component rather than by post type. Each of these is
 * a document or a data table dropped into prose, and none of them is prose.
 *
 * Raised at the department's own review, twice.
 */
.entry-content > .jmns-flipbook,
.entry-content > .fatwa-doc,
.entry-content > .jmns-takwim-now,
.entry-content > .jmns-takwim__viewer,
.entry-content > .jmns-gallery {
	max-inline-size: var(--wp--style--global--wide-size);
	margin-inline: auto;
}

/*
 * A block of event photographs, laid out as one.
 *
 * The plugin marks any block that is three or more images and nothing else;
 * see Content\Render::group_galleries(). Before this they stacked at the prose
 * measure — the Karnival report carried twelve, 6,210px of them, on a 9,332px
 * page, with the related rulings stranded underneath.
 *
 * `contain`, not `cover`. These are unlinked photographs, so a crop is the only
 * version a visitor will ever see, and the run mixes landscape event shots with
 * the occasional portrait — cropping those to a common ratio takes the faces
 * out. Letterboxed in a tinted cell they stay whole, and the cell keeps the
 * grid even.
 */
.jmns-gallery {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
	gap: var(--wp--preset--spacing--30);
}

/*
 * Two up on a phone. `auto-fill` gives one at 342px, which is twelve
 * full-width photographs and 3,200px of scrolling — the same complaint the
 * grid exists to answer, just narrower. A minimum small enough to fit two here
 * would fit eight on a desktop, so the narrow case is stated instead.
 */
@media (max-width: 30rem) {
	.jmns-gallery {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}
}

.jmns-gallery img {
	inline-size: 100%;
	block-size: 100%;
	aspect-ratio: 4 / 3;
	object-fit: contain;
	border-radius: var(--r-card);
	background-color: var(--wp--preset--color--surface-tint);
}

/* ==================================================== search facets ======= */

/*
 * "What kind of thing" on search results.
 *
 * A search for "solat" returns 153 documents across seven types — rulings,
 * bulletins, news, announcements, plain pages — in one undifferentiated list.
 * The rail is the one question a visitor can answer about their own search.
 *
 * Links, not a form: every state is in the URL, so a filtered result set can be
 * sent to somebody, and it works with scripting off.
 */

.search-facets {
	margin-block: var(--wp--preset--spacing--40) var(--wp--preset--spacing--50);
}

.search-facets__list {
	display: flex;
	flex-wrap: wrap;
	gap: 0.5rem;
	margin: 0;
	padding: 0;
	list-style: none;
}

.search-facets__item {
	display: inline-flex;
	align-items: baseline;
	gap: 0.45rem;
	/* 44px of target, because half this site's visitors are on a phone. */
	min-block-size: 44px;
	padding: 0.5rem 0.9rem;
	border: 1px solid var(--wp--preset--color--border);
	border-radius: var(--r-pill);
	background-color: var(--wp--preset--color--surface);
	color: var(--wp--preset--color--text-primary);
	font-size: var(--wp--preset--font-size--small);
	text-decoration: none;
	align-content: center;
	transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}

.search-facets__item:hover {
	border-color: var(--wp--preset--color--border-strong);
	background-color: var(--wp--preset--color--surface-tint);
}

/*
 * The chosen one, filled. This is a control rather than a passing state, and
 * unlike the prayer chip it has to be told apart from six siblings sitting
 * right beside it — so it takes the fill the timetable deliberately gave up.
 */
.search-facets__item.is-active {
	border-color: var(--wp--preset--color--interactive);
	background-color: var(--wp--preset--color--interactive);
	color: var(--wp--preset--color--surface);
}

.search-facets__count {
	font-variant-numeric: tabular-nums;
	font-size: var(--wp--preset--font-size--caption);
	color: var(--wp--preset--color--text-secondary);
}

.search-facets__item.is-active .search-facets__count {
	/*
	 * `text-secondary` on the fill would fail contrast; the surface colour at
	 * reduced strength keeps the hierarchy and stays legible.
	 *
	 * That is what this always meant, and for a year it said something else.
	 * It was written `rgb(255 255 255 / 0.82)` because `surface` was white, and
	 * the literal survived the palette becoming five palettes. Under `malam`
	 * the pill fills with a pale `interactive` (#8FC0AB) and the label beside
	 * this correctly turns dark, while the count stayed white: **2.04:1**,
	 * measured, on the only count a search result page shows.
	 *
	 * `inherit` takes the label's own colour, so the count cannot disagree with
	 * the thing it sits next to again, whatever a sixth scheme does. `opacity`
	 * rather than an alpha channel because that needs no `--jmns-on-*-rgb`
	 * token to exist, and on text with no background of its own the two
	 * composite identically.
	 */
	color: inherit;
	opacity: 0.82;
}

.search-facets__state {
	margin-block: var(--wp--preset--spacing--30) 0;
	color: var(--wp--preset--color--text-secondary);
	font-size: var(--wp--preset--font-size--small);
}

/* =================================================== berita, as rows ====== */

/*
 * Editorial rows, not image cards (client brief §16, §20).
 *
 * §16 asks for cards with a thumbnail, a category, a date, a headline, an
 * excerpt and an arrow. Every part of that is here except the assumption
 * underneath it: that there is a picture. **Twenty-eight of eighty-eight
 * berita have a featured image.** A grid of top-image cards over content that
 * is two-thirds text either renders holes or, as this section did until now,
 * gives up and drops the images entirely so the row stays consistent.
 *
 * Rows invert the dependency. The words carry the row and the picture is an
 * ornament at the end, so a post with one is richer and a post without is just
 * shorter. That is also the honest reading of the content: these are written
 * announcements, not a photo feed.
 */

/* --------------------------------------------------------- news lead --- */

/*
 * The lead story, per V3 §17.
 *
 * The section was three rows of equal weight, which is exactly the pattern §33
 * warns about — "three things beside each other" — and it left the newest news
 * looking like the third thing on the list. One lead and two beneath it is the
 * same content saying which is the news.
 *
 * Text first in the source and on the left on desktop, picture second: §19 asks
 * to avoid "a single small thumbnail floating far to the right with no strong
 * relationship to the article", which is what a 12rem row thumbnail becomes at
 * this size. The lead's picture is a third of the band, not an ornament.
 */
.news-lead {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--wp--preset--spacing--50);
	padding-block-end: var(--wp--preset--spacing--50);
	border-block-end: 1px solid var(--wp--preset--color--divider);
}

@media (min-width: 48rem) {
	/*
	 * Only when there is a picture to put in the second track. A text lead
	 * takes the whole band and caps its own measure below.
	 */
	.news-lead:has(.news-lead__figure) {
		grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
		gap: var(--wp--preset--spacing--70);
		align-items: center;
	}
}

/*
 * 736px. V3 §20 allowed 700-760 and the final review's §18 narrows it to
 * 680-740, so the cap comes in one step. It is on the text and not on the grid
 * track, so it holds whether the lead has a picture beside it or the whole
 * band to itself — a headline set across 1200px is what the rule prevents.
 */
.news-lead__body {
	max-inline-size: 46rem;
}

.news-lead__meta {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.5rem;
	margin: 0 0 var(--wp--preset--spacing--20);
	font-size: var(--wp--preset--font-size--caption);
	color: var(--wp--preset--color--text-secondary);
}

/* The same CATEGORY · DATE the rows use, so the section reads as one list. */
.news-lead__kind {
	font-weight: 600;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--wp--preset--color--interactive);
}

.news-lead__kind::after {
	content: "·";
	margin-inline-start: 0.5rem;
	color: var(--wp--preset--color--text-secondary);
	font-weight: 400;
}

.news-lead__date {
	font-variant-numeric: tabular-nums;
}

/*
 * §6 puts a featured content title at 24-28px. The three below it are 18, so
 * the step is what marks the lead rather than any panel or tint around it.
 */
.news-lead__title {
	margin-block: 0;
	font-size: var(--wp--preset--font-size--title-md);
	line-height: 1.25;
	text-wrap: pretty;
}

.news-lead__title a {
	color: var(--wp--preset--color--text-primary);
	text-decoration: none;
}

.news-lead__title a:hover {
	color: var(--wp--preset--color--interactive);
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

.news-lead__excerpt {
	margin: var(--wp--preset--spacing--30) 0 0;
	max-inline-size: 62ch;
	color: var(--wp--preset--color--text-secondary);
	line-height: 1.6;
}

.news-lead__go {
	display: inline-flex;
	align-items: center;
	gap: 0.35rem;
	margin-block-start: var(--wp--preset--spacing--30);
	color: var(--wp--preset--color--interactive);
	font-size: var(--wp--preset--font-size--small);
	font-weight: 600;
}

.news-lead__arrow {
	transition: transform var(--t-fast) var(--ease);
}

.news-lead:hover .news-lead__arrow {
	transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
	.news-lead:hover .news-lead__arrow {
		transform: none;
	}
}

/* §16's ratio again, so the lead and the archive cards agree. */
.news-lead__figure {
	display: block;
	overflow: hidden;
	border-radius: var(--r-card);
	background-color: var(--wp--preset--color--surface-tint);
}

.news-lead__image {
	display: block;
	inline-size: 100%;
	block-size: 100%;
	aspect-ratio: 16 / 9;
	object-fit: cover;
}



.news-rows {
	/* margin-block only. `margin: 0` also zeroes the `margin-inline: auto` that
	   `.home-section > *` uses to centre content in the shell, and the rows go
	   full-bleed to the window edge — the same trap `.home-section__grid`
	   already carries a note about. */
	margin-block: 0;
	padding: 0;
	list-style: none;
}

/*
 * A rule under each row rather than a border around each. §20 asks to stop
 * putting everything in the same rounded white box, and a list separated by
 * hairlines is the oldest and quietest way to show a sequence.
 *
 * Under, not between: V3 §18 closes its sketch with a rule beneath the last
 * item, and it earns its place — without it the list trailed off into the
 * section's "Lihat semua berita" link with nothing saying where it ended.
 */
.news-rows__item {
	border-block-end: 1px solid var(--wp--preset--color--divider);
}

.news-row {
	display: grid;
	grid-template-columns: 1fr;
	gap: var(--wp--preset--spacing--40);
	padding-block: var(--wp--preset--spacing--40);
}

/*
 * The two under the lead are a headline and its date, nothing else — §18 draws
 * them that way, and dropping the excerpt and the thumbnail is what leaves the
 * lead as the only row carrying either.
 *
 * Their metadata sat 22px off the headline while every other card on the page
 * runs 8 to 12 — the row's own 16px grid gap stacking on top of the meta's
 * margin. §25 asks for metadata-to-title spacing to be consistent, so this
 * drops the gap and states the margin: 8px, the same as the lead above them.
 *
 * Declared after `.news-row`, not before. At equal specificity the later rule
 * wins, and the first cut of this sat above it and did nothing.
 */
.news-row--compact {
	gap: 0;
}

.news-row--compact .news-row__meta {
	margin-block-end: var(--wp--preset--spacing--20);
}

.news-row__meta {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: 0.5rem;
	margin: 0 0 0.4rem;
	font-size: var(--wp--preset--font-size--caption);
	color: var(--wp--preset--color--text-secondary);
}

/* The category, when the post has one. Uppercase and lettered out so it reads
   as a label rather than as the first words of the headline. */
.news-row__kind {
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--wp--preset--color--interactive);
}

.news-row__date {
	/* §20: "muted, visually secondary". */
	opacity: 0.85;
}

.news-row__kind + .news-row__date::before {
	content: "·";
	margin-inline-end: 0.5rem;
	color: var(--wp--preset--color--text-disabled);
}

.news-row__title {
	margin: 0;
	/*
	 * §19: a long headline should wrap to about two lines rather than running
	 * almost edge to edge. At the full content width the longest of these ran
	 * to a single 1100px line, which is past any comfortable measure.
	 */
	max-inline-size: 760px;
	font-family: var(--wp--preset--font-family--heading);
	/* §28: card title 16-19px. */
	font-size: var(--wp--preset--font-size--large);
	font-weight: 700;
	line-height: 1.3;
	text-wrap: balance;
}

.news-row__title a {
	color: var(--wp--preset--color--text-primary);
	text-decoration: none;
}

.news-row__title a:hover {
	color: var(--wp--preset--color--interactive);
	text-decoration: underline;
	text-underline-offset: 0.15em;
}
