/*
Theme Name: GRI Theme
Theme URI: https://globalreorderinginitiative.org
Author: Youssef El Mays
Description: Custom block theme for The Global Re-ordering Initiative (Munk School of Global Affairs & Public Policy, University of Toronto). Built from the Figma "Open Field / Ink & Brass" design. Block-editor-first: no page builder, styling lives in theme.json, structure lives in templates/ and patterns/.
Version: 2.3.0
Requires at least: 6.4
Tested up to: 7.0
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: LICENSE
Text Domain: gri-theme
*/

/*
 * ============================================================================
 * SHARED VISUAL SYSTEM — read this before adding anything.
 * ============================================================================
 *
 * Colors, typography, spacing and layout widths live in theme.json. This file
 * only holds what theme.json cannot express: the logo lockup, the nav active
 * state, placeholder media boxes, the card / pill / eyebrow vocabulary the
 * page patterns compose from, and the responsive breakpoints.
 *
 * This file is SHARED and FROZEN during page implementation. Page patterns
 * must compose from the classes below rather than adding new ones. Never
 * hardcode a hex value here — always a var(--wp--preset--color--<slug>).
 *
 * Accessibility rule that shapes the palette usage below:
 *   brand-accent (#C79A44 brass) measures ~2.3:1 on paper — it is for accents
 *   on DARK grounds and for fills only. On light grounds, small text uses
 *   accent-deep (#A87C2E). Do not swap one for the other.
 *
 * Breakpoints: 1024px (tablet) and 640px (phone).
 */

/* ---------------------------------------------------------------------------
 * 0. Utilities
 * ------------------------------------------------------------------------ */

.screen-reader-text {
	border: 0;
	clip-path: inset(50%);
	height: 1px;
	margin: -1px;
	overflow: hidden;
	padding: 0;
	position: absolute;
	width: 1px;
	word-wrap: normal !important;
}

/* ---------------------------------------------------------------------------
 * 1. Logo lockup
 *
 * One markup (patterns/logo-lockup.php), two placements. The surrounding
 * .gri-logo-slot sets --gri-logo-mark (the rendered height of the two-globe
 * mark) and everything else scales off it.
 *
 * The header is deliberately capped at a 46px mark. The Figma nav renders the
 * lockup at 94px inside a 134px bar — roughly double a conventional header —
 * and that is not reproduced here.
 * ------------------------------------------------------------------------ */

.gri-logo-slot {
	--gri-logo-mark: 46px;
	--gri-logo-word: 28px;
	--gri-logo-tagline: 9px;
}

.gri-logo-slot--footer {
	--gri-logo-mark: 64px;
	--gri-logo-word: 34px;
	--gri-logo-tagline: 12px;
}

.gri-logo {
	display: inline-flex;
	flex-direction: column;
	align-items: center;
	gap: 3px;
	text-decoration: none;
}

.gri-logo:hover,
.gri-logo:focus {
	text-decoration: none;
}

.gri-logo__row {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: calc(var(--gri-logo-mark) * 0.1);
}

.gri-logo__mark {
	height: var(--gri-logo-mark);
	width: auto;
	display: block;
	flex: none;
}

.gri-logo__word {
	font-family: var(--wp--preset--font-family--heading);
	font-weight: 700;
	font-size: var(--gri-logo-word);
	line-height: 1;
	letter-spacing: 0.56px;
	color: var(--wp--preset--color--white);
}

.gri-logo__tagline {
	font-family: var(--wp--preset--font-family--body);
	font-weight: 600;
	font-size: var(--gri-logo-tagline);
	letter-spacing: 1.6px;
	text-transform: uppercase;
	line-height: 1.2;
	white-space: nowrap;
	color: var(--wp--preset--color--caption-text);
}

/* ---------------------------------------------------------------------------
 * 1b. Top-level rhythm
 *
 * Every section on this site is a full-bleed band that carries its own vertical
 * padding, so the default blockGap between top-level blocks just opens a stripe
 * of page background between the header and the hero. Core emits its gap from
 * `:where(.wp-site-blocks) > *`, which has zero specificity, so this wins.
 * ------------------------------------------------------------------------ */

.wp-site-blocks > * {
	margin-block-start: 0;
	margin-block-end: 0;
}

/*
 * Same rule one level down, for the <main> landmark that wraps a page's
 * section bands. Scoped to .gri-main on purpose: the <main> in single.html /
 * page.html wraps post content, where the default block gap IS wanted.
 */
.gri-main > * {
	margin-block-start: 0;
	margin-block-end: 0;
}

/* ---------------------------------------------------------------------------
 * 2. Header + navigation
 * ------------------------------------------------------------------------ */

.gri-header {
	position: relative;
	z-index: 10;
	padding-left: 80px;
	padding-right: 80px;
}

/*
 * Nav active state — this is the ONE rule.
 *
 * Two cues, never colour alone. Every item reserves the underline's 2px in its
 * default state via a transparent bottom border, so lighting an item up does
 * not shift its neighbours' baseline. The underline itself is a pseudo-element
 * sitting ~6px below the label.
 *
 * The homepage has no active item by design — the logo is the home affordance.
 * `current-menu-item` comes from WordPress core for post-type links, and from
 * gri_navigation_link_current_class() in functions.php for the custom-URL
 * links this theme ships with.
 */
.gri-nav .wp-block-navigation-item__content {
	position: relative;
	padding-bottom: 6px;
	border-bottom: 2px solid transparent;
	text-decoration: none;
}

.gri-nav .current-menu-item > .wp-block-navigation-item__content {
	color: var(--wp--preset--color--brand-accent);
}

.gri-nav .current-menu-item > .wp-block-navigation-item__content::after {
	content: "";
	position: absolute;
	inset-inline: 0;
	bottom: -2px;
	height: 2px;
	background-color: var(--wp--preset--color--brand-accent);
}

.gri-nav .wp-block-navigation-item__content:hover {
	color: var(--wp--preset--color--brand-accent);
}

/* ---------------------------------------------------------------------------
 * 3. Pills and small-caps labels
 * ------------------------------------------------------------------------ */

.gri-pill .wp-block-button__link {
	line-height: 1;
}

/* Small-caps label. Use the --on-dark modifier inside ink/deep bands. */
.gri-label {
	font-family: var(--wp--preset--font-family--body);
	font-weight: 700;
	font-size: 11px;
	letter-spacing: 1.32px;
	text-transform: uppercase;
	color: var(--wp--preset--color--accent-deep);
}

.gri-label--on-dark {
	color: var(--wp--preset--color--brand-accent);
}

/* Eyebrow above a page-header H1. Always on a dark ground. */
.gri-eyebrow {
	font-family: var(--wp--preset--font-family--body);
	font-weight: 700;
	font-size: 10px;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: var(--wp--preset--color--caption-text);
}

/* A small-caps label that is also a link keeps the label's colour. */
.gri-label a {
	color: inherit;
	text-decoration: none;
}

.gri-label a:hover,
.gri-label a:focus-visible {
	text-decoration: underline;
}

/*
 * .gri-eyebrow is tuned for dark grounds. On a light ground its caption-text
 * only reaches ~3.3:1, so section eyebrows on paper/surface use body-text.
 */
.gri-eyebrow--on-light {
	color: var(--wp--preset--color--body-text);
}

/* Status / kind chips: "IN PERSON", "HYBRID", "ESSAY", "BY INVITATION". */
.gri-chip {
	display: inline-flex;
	align-items: center;
	border-radius: 999px;
	padding: 7px 14px;
	font-family: var(--wp--preset--font-family--body);
	font-weight: 700;
	font-size: 11px;
	letter-spacing: 1.1px;
	text-transform: uppercase;
	line-height: 1;
	background-color: var(--wp--preset--color--warm-bg);
	color: var(--wp--preset--color--accent-deep);
}

.gri-chip--on-dark {
	background-color: color-mix(in srgb, var(--wp--preset--color--brand-accent) 16%, transparent);
	color: var(--wp--preset--color--brand-accent);
}

/* Solid ink fill — the featured event card's status pill. */
.gri-chip--solid {
	background-color: var(--wp--preset--color--ink-bg);
	color: var(--wp--preset--color--white);
}

/* ---------------------------------------------------------------------------
 * 3b. Research filter tabs
 *
 * Ordinary links, not a JavaScript filter: each points at a publication-type
 * archive. Contrast, both states clear AA for small text:
 *   active   heading-text on brand-accent .... 6.70:1
 *   inactive body-text on warm-bg ........... 8.02:1
 * The active pill keeps a same-colour 1px border so it stays exactly the same
 * size as the outlined pills and the row doesn't shift as the tab changes.
 * ------------------------------------------------------------------------ */

.gri-filter-tabs {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 12px;
}

.gri-filter-tab {
	display: inline-flex;
	align-items: center;
	border: 1px solid var(--wp--preset--color--caption-text);
	border-radius: 999px;
	padding: 9px 18px;
	background-color: transparent;
	color: var(--wp--preset--color--body-text);
	font-family: var(--wp--preset--font-family--body);
	font-weight: 700;
	font-size: 10px;
	letter-spacing: 1.4px;
	line-height: 1;
	text-transform: uppercase;
	text-decoration: none;
	white-space: nowrap;
}

.gri-filter-tab:hover,
.gri-filter-tab:focus-visible {
	border-color: var(--wp--preset--color--accent-deep);
	color: var(--wp--preset--color--accent-deep);
}

.gri-filter-tab--current,
.gri-filter-tab--current:hover,
.gri-filter-tab--current:focus-visible {
	border-color: var(--wp--preset--color--brand-accent);
	background-color: var(--wp--preset--color--brand-accent);
	color: var(--wp--preset--color--heading-text);
}

/* ---------------------------------------------------------------------------
 * 3c. Button
 *
 * A real anchor styled as a solid brass pill — used where a URL comes from post
 * meta and so cannot go through core/button (which is the only block that can
 * bind an href, but does not match this treatment). Currently the single event
 * page's "Register for this event".
 *
 * heading-text on brand-accent measures 6.70:1 — clears AA for small text.
 * ------------------------------------------------------------------------ */

.gri-button {
	display: inline-flex;
	align-items: center;
	border: 1px solid var(--wp--preset--color--brand-accent);
	border-radius: 999px;
	padding: 13px 26px;
	background-color: var(--wp--preset--color--brand-accent);
	color: var(--wp--preset--color--heading-text);
	font-family: var(--wp--preset--font-family--body);
	font-weight: 700;
	font-size: 11px;
	letter-spacing: 1.32px;
	line-height: 1;
	text-transform: uppercase;
	text-decoration: none;
	white-space: nowrap;
}

.gri-button:hover,
.gri-button:focus-visible {
	background-color: var(--wp--preset--color--accent-light);
	border-color: var(--wp--preset--color--accent-light);
	color: var(--wp--preset--color--heading-text);
}

/* Rows holding a .gri-button or a back-link inside a card. */
.gri-register-row {
	margin: 0
	margin-top: auto;
	flex-shrink: 0;
}

/* ---------------------------------------------------------------------------
 * 4. Page header band (ink ground) — shared by every interior page
 * ------------------------------------------------------------------------ */

.gri-page-header {
	background-color: var(--wp--preset--color--ink-bg);
	padding: 64px 80px 72px;
}

.gri-page-header h1 {
	color: var(--wp--preset--color--white);
	font-weight: 400;
	line-height: 1.14;
	/*
	 * Was 18px 0 0 -- spacing for the eyebrow that used to sit above the H1 in
	 * every .gri-page-header. All six page headers had their eyebrow removed
	 * as redundant (it just repeated the H1 text -- "EVENTS" over "Events",
	 * etc.) at Youssef's request (2026-09-09), so nothing needs that gap
	 * reserved above the heading any more. .gri-event-header's H1 is a
	 * different class and was never affected by this rule.
	 */
	margin: 0;
}

.gri-page-header__lede {
	font-family: var(--wp--preset--font-family--heading);
	font-style: italic;
	font-size: 22px;
	line-height: 1.45;
	/* Figma paints the lede `mid`; 5.08:1 on ink, passes AA. */
	color: var(--wp--preset--color--caption-text);
	max-width: 760px;
	margin: 20px 0 0;
}

/*
 * Secondary paragraph inside a page-header band, for copy that follows the lede
 * but is instructional rather than descriptive (the Get Involved pitch process).
 * Body font, not the lede's italic serif, so it reads as instructions.
 */
.gri-page-header__note {
	max-width: 760px;
	margin: 18px 0 0;
}

/*
 * Body section on a light ground. Block markup cannot express a breakpoint, so
 * sections use this class instead of inline horizontal padding — that is the
 * only way their gutters can track .gri-page-header's at 1024px and 640px.
 * Never set padding-left/right inline on a section: it wins over these rules.
 */
.gri-section {
	padding-left: 80px;
	padding-right: 80px;
}

/* ---------------------------------------------------------------------------
 * 4b. Single event header
 *
 * The ink band on a single event carries everything about the occasion: the
 * eyebrow, title and date on the left, the practical detail (location, format)
 * beneath them, and the registration call to action held top-right. Below 780px
 * the aside drops under the main column rather than squeezing beside it.
 * ------------------------------------------------------------------------ */

.gri-event-header {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	justify-content: space-between;
	gap: 24px 40px;
}

.gri-event-header__main {
	flex: 1 1 520px;
	min-width: 0;
}

.gri-event-header__aside {
	flex: 0 0 auto;
	padding-top: 8px;
}

/* Location / format pairs sitting under the date line. */
.gri-event-meta {
	display: flex;
	flex-wrap: wrap;
	gap: 14px 48px;
	margin: 26px 0 0;
}

.gri-event-meta__item {
	max-width: 420px;
	/*
	 * A flex item's default min-width is min-content, which for a pasted URL is
	 * the whole URL — so the item refuses to shrink and the header pushes past
	 * the viewport on a phone however the text is told to wrap. min-width:0 lets
	 * it shrink; `anywhere` (not `break-word`) is what makes the break count
	 * towards min-content sizing. Venue and location are free text, so this is a
	 * when-not-if guard.
	 */
	min-width: 0;
	overflow-wrap: anywhere;
}

.gri-event-meta__label {
	display: block;
	font-family: var(--wp--preset--font-family--body);
	font-weight: 700;
	font-size: 11px;
	letter-spacing: 1.32px;
	text-transform: uppercase;
	color: var(--wp--preset--color--brand-accent);
	margin: 0 0 4px;
}

.gri-event-meta__value {
	display: block;
	font-family: var(--wp--preset--font-family--body);
	font-size: 14px;
	line-height: 1.5;
	color: var(--wp--preset--color--footer-link);
	margin: 0;
}

@media (max-width: 780px) {
	.gri-event-header__aside {
		padding-top: 0;
	}
}

/* ---------------------------------------------------------------------------
 * 5. Index cards
 *
 * paper fill, 1px rule border, 14px radius — sitting on a warm-bg (surface)
 * section ground.
 * ------------------------------------------------------------------------ */

.gri-card {
	background-color: var(--wp--preset--color--white);
	border: 1px solid var(--wp--preset--color--divider);
	border-radius: 14px;
	overflow: hidden;
}

.gri-card--feature {
	border-radius: 20px;
}

.gri-card__title {
	display: -webkit-box;
	-webkit-line-clamp: 3;
	line-clamp: 3;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

.gri-card__abstract {
	white-space: pre-line;
}

.gri-card__abstract:empty {
    display: none;
}

/* Hairline rule inside a card or section. */
.gri-rule {
	height: 1px;
	background-color: var(--wp--preset--color--divider);
	border: 0;
	margin: 0;
}

.gri-rule--on-dark {
	background-color: var(--wp--preset--color--footer-divider);
}

/* ---------------------------------------------------------------------------
 * 5b. Empty state
 *
 * Shown inside every core/query-no-results block. This site launches with an
 * empty database, so this is the first thing most visitors will see — it is a
 * designed state, not an error. Same shell as the list cards; no icon, no
 * warning colour, no border emphasis.
 * ------------------------------------------------------------------------ */

.gri-empty-state {
	background-color: var(--wp--preset--color--white);
	border: 1px solid var(--wp--preset--color--divider);
	border-radius: 14px;
	padding: 64px 32px;
	text-align: center;
}

.gri-empty-state__message {
	font-family: var(--wp--preset--font-family--heading);
	font-size: 20px;
	line-height: 1.4;
	color: var(--wp--preset--color--secondary-text);
	margin: 0;
}

.gri-empty-state__sub {
	font-family: var(--wp--preset--font-family--body);
	font-size: 14px;
	line-height: 1.6;
	color: var(--wp--preset--color--caption-text);
	margin: 10px 0 0;
}

.gri-empty-state__link {
	color: var(--wp--preset--color--accent-deep);
	text-decoration: underline;
	text-underline-offset: 3px;
}

@media (max-width: 640px) {
	.gri-empty-state {
		padding: 44px 20px;
	}
}

/* ---------------------------------------------------------------------------
 * 5c. Core block quirks inside cards
 *
 * The lists became Query Loops, so several card parts are now core blocks with
 * opinions of their own. These rules put them back to the drawn design.
 * ------------------------------------------------------------------------ */

/*
 * core/post-title renders its anchor as inline-block, and -webkit-line-clamp
 * cannot count lines through an atomic inline-block child — which silently
 * disabled the 3-line clamp on every card title.
 */
.gri-card__title a {
	display: inline;
}

/*
 * core/post-terms always links each term. The kickers and chips are drawn as
 * plain small-caps text, so the link inherits the block's colour and drops the
 * underline until hover.
 */
.gri-card .wp-block-post-terms a,
.gri-card--feature .wp-block-post-terms a,
.gri-chip a,
.gri-label a {
	color: inherit;
	text-decoration: none;
}

.gri-card .wp-block-post-terms a:hover,
.gri-card .wp-block-post-terms a:focus-visible,
.gri-chip a:hover,
.gri-chip a:focus-visible {
	text-decoration: underline;
}

/* A featured image standing in for a placeholder box must fill it. */
.gri-media-placeholder > .wp-block-post-featured-image {
	width: 100%;
	margin: 0;
}

/*
 * Homepage up-next stack: Figma paints the first card on the ink ground and the
 * rest light. A Query Loop renders one post template per result, so "the first
 * one is different" cannot come from block markup — it has to be :first-child.
 * !important is unavoidable: core's own has-*-color preset classes are !important.
 */
.gri-events-stack .wp-block-post-template > li:first-child .gri-card {
	background-color: var(--wp--preset--color--ink-bg);
	border-color: var(--wp--preset--color--ink-bg);
}

.gri-events-stack .wp-block-post-template > li:first-child .gri-card .gri-label {
	color: var(--wp--preset--color--brand-accent) !important;
}

.gri-events-stack .wp-block-post-template > li:first-child .gri-card .gri-chip {
	color: var(--wp--preset--color--white) !important;
	border-color: var(--wp--preset--color--caption-text);
}

.gri-events-stack .wp-block-post-template > li:first-child .gri-card .wp-block-post-title,
.gri-events-stack .wp-block-post-template > li:first-child .gri-card .wp-block-post-title a {
	color: var(--wp--preset--color--white) !important;
}

/*
 * core/post-title renders as a <p> here, so it also matched the generic paragraph
 * rule below — which, carrying one more :not(), out-specified the title rule and
 * painted the title muted instead of white. Excluded explicitly.
 */
.gri-events-stack .wp-block-post-template > li:first-child .gri-card p:not(.gri-label):not(.gri-chip):not(.wp-block-post-title) {
	color: var(--wp--preset--color--caption-text) !important;
}

/* ---------------------------------------------------------------------------
 * 6. Event date numerals — the ONLY place Instrument Serif is used
 * ------------------------------------------------------------------------ */

.gri-date-numeral {
	font-family: var(--wp--preset--font-family--display);
	font-size: 56px;
	line-height: 1;
	color: var(--wp--preset--color--heading-text);
}

/* Calendar-row numerals sit below the featured card's. */
.gri-date-numeral--sm {
	font-size: 38px;
}

/* ---------------------------------------------------------------------------
 * 7. Placeholder media boxes
 *
 * Used wherever a pattern expects an image that has not been supplied yet
 * (faculty headshots, dispatch images, hero photography). These are decorative
 * boxes, not images — they carry no alt text and are hidden from assistive
 * technology so they are never announced as meaningful content.
 * ------------------------------------------------------------------------ */

.gri-media-placeholder {
	background-color: var(--wp--preset--color--placeholder-fill);
	display: flex;
	align-items: flex-end;
	overflow: hidden;
	min-height: 200px;
	border-radius: 14px;
}

.gri-media-placeholder--tall {
	min-height: 420px;
}

.gri-media-placeholder--square {
	aspect-ratio: 1 / 1;
	min-height: 0;
}

.gri-media-placeholder--circle {
	border-radius: 999px;
	aspect-ratio: 1 / 1;
	min-height: 0;
}

/* 3:2 landscape slot. Not currently used by any pattern — the Student
   Voices dispatch card moved to the small 64px round-image convention
   patterns/research-publications.php uses — kept for a future wide-image
   placement. */
.gri-media-placeholder--wide {
	aspect-ratio: 3 / 2;
	min-height: 0;
	border-radius: 10px;
}

.gri-media-placeholder img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/* 16:9 embed used by the Student Voices dispatch card's optional
 * video_link field (patterns/student-voices-dispatches.php,
 * gri_dispatch_video_slot() in functions.php). Same rounding convention as
 * the other small media slots on that card. */
.gri-video-embed {
	position: relative;
	width: 100%;
	aspect-ratio: 16 / 9;
	border-radius: 10px;
	overflow: hidden;
	background-color: var(--wp--preset--color--placeholder-fill);
	margin-bottom: 16px;
}

.gri-video-embed iframe {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	border: 0;
}

.gri-media-placeholder__label {
	font-family: var(--wp--preset--font-family--body);
	font-weight: 600;
	font-size: 9px;
	letter-spacing: 0.6px;
	text-transform: uppercase;
	color: var(--wp--preset--color--secondary-text);
	padding: 9px;
}

/* ---------------------------------------------------------------------------
 * 8. Footer
 * ------------------------------------------------------------------------ */

.gri-footer {
	padding-left: 80px;
	padding-right: 80px;
}

.gri-footer .wp-block-separator.gri-footer__rule {
	margin: 0;
	border: 0;
	height: 1px;
	opacity: 1;
}

.gri-footer a {
	color: var(--wp--preset--color--footer-link);
	text-decoration: none;
}

.gri-footer a:hover,
.gri-footer a:focus {
	color: var(--wp--preset--color--brand-accent);
	text-decoration: underline;
}

.gri-footer-nav .wp-block-navigation-item__content {
	text-decoration: none;
}

/* ---------------------------------------------------------------------------
 * 9. People grids (People page)
 *
 * CSS Grid rather than the Columns block so the count per row can change at
 * each breakpoint — Columns only has one "stack on mobile" jump.
 * ------------------------------------------------------------------------ */

.gri-people-grid {
	display: grid;
	gap: 40px;
}

/*
 * Contact line at the foot of a person's card. It reuses .gri-label so it reads
 * as a sibling of the role line at the top of the card rather than as a new
 * treatment — same 11px uppercase brass. This class only adds the separation
 * that makes it read as the card's footer, and the link styling it will need
 * once real addresses replace the placeholder text.
 *
 * accent-deep at 11px is below WCAG AA on a light ground (3.54:1) — the same
 * open item that already covers every small-caps label on the site. Fixing it
 * is a palette decision, not a per-rule one. See Brand Tokens in CLAUDE.md.
 */
.gri-person-email {
	/*
	 * The cards set every other line to margin 0, so they sit flush; this one
	 * needs its own gap to read as the card's footer rather than a fourth line
	 * of the bio. The block therefore carries NO inline margin — an inline
	 * margin-top:0 would win over this rule.
	 */
	margin: 12px 0 0;
	text-transform: none;
	letter-spacing: 0.2px;
}

.gri-person-email a {
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 2px;
}

/*
 * "Show email" trigger for the click-to-reveal contact line (see
 * gri_theme_email_reveal_script() in functions.php). Styled to read as the
 * same footer link .gri-person-email a already is, just spelled as a
 * <button> so it works with no href before the real address exists.
 */
.gri-email-reveal {
	display: inline;
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	font: inherit;
	color: inherit;
	text-decoration: underline;
	text-underline-offset: 2px;
	cursor: pointer;
}

.gri-email-reveal:hover,
.gri-email-reveal:focus-visible {
	color: var(--wp--preset--color--accent-deep);
}

.gri-people-grid--professors {
	grid-template-columns: repeat(3, 1fr);
}

.gri-people-grid--students {
	grid-template-columns: repeat(3, 1fr);
	gap: 24px;
}

/* Faculty rows: portrait beside a bio column. */
.gri-faculty-row {
	display: grid;
	grid-template-columns: 220px 1fr;
	gap: 40px;
	align-items: start;
	padding: 40px 0;
	border-top: 1px solid var(--wp--preset--color--divider);
}

/* Rules sit BETWEEN rows — the first row opens the section, it doesn't close one. */
.gri-faculty-row:first-child {
	border-top: 0;
	padding-top: 0;
}

/*
 * Faculty names link out to each person's home-institution profile. The name keeps
 * the heading colour rather than turning accent-deep — a 30px block of brass would
 * fight the design — but it carries a persistent underline so the link is
 * distinguishable by more than colour, which colour alone would not satisfy.
 */
.gri-faculty-row h3 a {
	color: inherit;
	text-decoration: underline;
	text-decoration-thickness: 1px;
	text-underline-offset: 5px;
	text-decoration-color: var(--wp--preset--color--accent-deep);
}

.gri-faculty-row h3 a:hover,
.gri-faculty-row h3 a:focus-visible {
	color: var(--wp--preset--color--accent-deep);
}

/*
 * Square portrait slot. `height` must stay `auto`, not 100%: these sit inside CSS
 * grid items that stretch to the tallest card in the row, and 100% would stretch
 * the portrait with them — aspect-ratio alone gives the square.
 */
/*
 * The portrait slot is a core Image block on any card built from
 * gri-theme/person-card-*, so it arrives wrapped in a <figure class="wp-block-image">.
 * Core's block stylesheet gives that figure `margin: 0 0 1em`, which would sit
 * on top of the card's own blockGap and push the name away from the photo --
 * every other line in these cards is explicitly margin 0 for the same reason.
 * Zeroed here rather than inline on the block, so a photo swapped in later
 * can't lose it.
 */
.gri-faculty-row .wp-block-image,
.gri-people-grid .wp-block-image {
	margin: 0;
}

.gri-people-photo,
.gri-people-photo img {
	aspect-ratio: 1 / 1;
	object-fit: cover;
	object-position: center top;
	width: 100%;
	height: auto;
	display: block;
	border-radius: 14px;
}

.gri-people-role {
	font-family: var(--wp--preset--font-family--body);
	font-size: 13px;
	font-weight: 500;
	color: var(--wp--preset--color--accent-deep);
	margin: 4px 0 12px;
}

.gri-people-card--student .gri-people-role {
	font-size: 12px;
	margin: 4px 0 0;
}

.gri-people-card--student h3 {
	font-size: 15px;
}

/* ---------------------------------------------------------------------------
 * 10. Hero
 * ------------------------------------------------------------------------ */

.gri-hero {
	position: relative;
	overflow: hidden;
	background-color: var(--wp--preset--color--ink-bg);
}

.gri-hero__globes {
	position: absolute;
	right: 50px;
	top: 50%;
	margin: 0;
	transform: translateY(-50%);
	pointer-events: none;
	user-select: none;
	max-width: 33vw;
}

/* Video band — decorative brass orb bleeding off the left edge. */
.gri-video {
	position: relative;
	overflow: hidden;
}

.gri-video__orb {
	position: absolute;
	left: -98px;
	bottom: -114px;
	width: 280px;
	height: 280px;
	border-radius: 999px;
	background-color: var(--wp--preset--color--brand-accent);
	pointer-events: none;
}

/* ---------------------------------------------------------------------------
 * 11. Responsive
 * ------------------------------------------------------------------------ */

@media (max-width: 1024px) {
	.gri-header,
	.gri-footer,
	.gri-page-header {
		padding-left: 40px;
		padding-right: 40px;
	}

	.gri-section {
		padding-left: 40px;
		padding-right: 40px;
	}

	/*
	 * Tighten the nav so the header holds a single row well below 1024. Core's
	 * mobile overlay only takes over under 600px, so without this the five
	 * links wrap into ragged rows through the whole tablet band.
	 */
	.gri-header {
		column-gap: 24px;
	}

	.gri-nav .wp-block-navigation__container {
		gap: 22px;
	}

	.gri-nav .wp-block-navigation-item__content {
		font-size: 15px;
	}

	.gri-page-header {
		padding-top: 48px;
		padding-bottom: 52px;
	}

	.gri-people-grid--professors {
		grid-template-columns: repeat(2, 1fr);
	}

	/*
	 * No students rule here: the desktop default is already 3-up, so this
	 * 1024px step would just repeat it. The next real step down is 2-up,
	 * set at 900px below.
	 */

	.gri-faculty-row {
		grid-template-columns: 160px 1fr;
		gap: 28px;
	}

	.gri-hero__globes {
		right: 24px;
		max-width: 34vw;
	}

	.gri-footer__top {
		row-gap: 40px;
	}
}

@media (max-width: 640px) {
	.gri-header,
	.gri-footer,
	.gri-page-header {
		padding-left: 20px;
		padding-right: 20px;
	}

	.gri-section {
		padding-left: 20px;
		padding-right: 20px;
	}

	.gri-logo-slot,
	.gri-logo-slot--footer {
		--gri-logo-mark: 38px;
		--gri-logo-word: 24px;
		--gri-logo-tagline: 8px;
	}

	.gri-page-header h1 {
		font-size: 34px;
	}

	.gri-page-header__lede {
		font-size: 18px;
	}

	/* The globes motif hides rather than colliding with the headline. */
	.gri-hero__globes {
		display: none;
	}

	.gri-people-grid--professors {
		grid-template-columns: 1fr;
	}

	.gri-people-grid--students {
		grid-template-columns: repeat(2, 1fr);
		gap: 16px;
	}

	.gri-faculty-row {
		grid-template-columns: 1fr;
		gap: 20px;
	}

	.gri-media-placeholder--tall {
		min-height: 260px;
	}

	.gri-date-numeral {
		font-size: 40px;
	}

	.gri-date-numeral--sm {
		font-size: 32px;
	}

	.gri-video__orb {
		display: none;
	}
}

/*
 * Long unbroken strings are the classic cause of a page that suddenly scrolls
 * sideways on a phone, and this site invites them: event venue and location are
 * free-text meta, and a registration URL can be pasted anywhere. Measured
 * before this rule — a 60-character URL in .gri-event-meta__value pushed the
 * document to 1044px inside a 375px viewport.
 *
 * overflow-wrap inherits, so one declaration on body covers every descendant.
 * This is the actual fix; the overflow-x rule below is only a net.
 */
body {
	overflow-wrap: break-word;
}

/*
 * A net, not a fix. Nothing measured currently overflows at any width, so if
 * this ever starts doing real work something upstream has broken — find that
 * instead of widening this rule.
 */
@media (max-width: 1024px) {
	html,
	body {
		overflow-x: hidden;
	}
}

/* ---------------------------------------------------------------------------
 * 12. Touch targets and small-screen stacking
 *
 * Measured at 375 / 768 / 1080 in the browser, not inferred. The layout itself
 * held up at every width — no horizontal overflow anywhere, Columns stack at
 * core's 782px breakpoint, the faculty rows stack at 640 — so everything below
 * is about things a finger has to hit, plus the one grid that stayed too tight.
 *
 * The 44px floor is the WCAG 2.5.5 / iOS HIG target size. Where the design's
 * type is deliberately small (the 11px small-caps cues), the TARGET grows via
 * padding while the text stays exactly the size it was drawn at — no type
 * sizes change here.
 * ------------------------------------------------------------------------ */

@media (max-width: 782px) {

	/*
	 * The hamburger core renders is a bare 24x24 SVG button. Padding it out to
	 * 44 keeps the icon identical and just enlarges what you can hit; the
	 * negative margin stops the bigger box from pushing the header taller.
	 *
	 * Do NOT set `display` here — core owns the toggle's visibility, and
	 * forcing it visible puts a hamburger next to an already-expanded menu.
	 */
	.gri-header .wp-block-navigation__responsive-container-open,
	.gri-header .wp-block-navigation__responsive-container-close {
		padding: 10px;
		margin: -10px;
	}

	/* Pills: the header CTA measured 35px, the hero pair 39px. */
	.gri-pill .wp-block-button__link,
	.wp-block-button__link,
	.gri-button {
		min-height: 44px;
		display: inline-flex;
		align-items: center;
		justify-content: center;
	}

	/*
	 * Standalone small-caps calls to action — MORE ABOUT GRI, ALL DISPATCHES,
	 * FULL CALENDAR, CONTINUE READING, MORE INFO, Read more. They render as
	 * 11-12px inline text, so the hit box was ~12-18px tall. Only the anchor is
	 * padded, never .gri-label itself: the role lines on the People page use the
	 * same class and are not links.
	 */
	.gri-label a,
	.wp-block-read-more,
	.gri-empty-state__link {
		display: inline-block;
		padding-block: 13px;
		min-height: 44px;
		box-sizing: border-box;
	}

	/* Nav rows in the open overlay, and the two footer link lists. */
	.gri-nav .wp-block-navigation__responsive-container.is-menu-open
		.wp-block-navigation-item__content,
	.gri-footer-nav .wp-block-navigation-item__content {
		display: inline-flex;
		align-items: center;
		min-height: 44px;
	}

	/*
	 * Research filter tabs measured 30px tall and wrapped to three rows, which
	 * put three 30px targets 12px apart. Taller targets and a real row gap.
	 */
	.gri-filter-tabs {
		row-gap: 10px;
	}

	.gri-filter-tab {
		min-height: 44px;
		display: inline-flex;
		align-items: center;
	}
}

@media (max-width: 640px) {

	/*
	 * The header wraps to two rows on a phone (logo, then hamburger + CTA),
	 * which is the right shape — it just does not need 149px of height to do
	 * it.
	 */
	.gri-header {
		padding-top: 10px;
		padding-bottom: 10px;
		row-gap: 10px;
	}

	.gri-header__right {
		column-gap: 16px;
	}
}

/*
 * Student associates drop to two columns at the same 900px the nav collapses
 * at, rather than staying three-up through the whole tablet band. Three-up put
 * a square headshot, a name, a role line and a full bio into 211px at 768.
 */
@media (max-width: 900px) {
	.gri-people-grid--students {
		grid-template-columns: repeat(2, 1fr);
	}
}

@media (max-width: 520px) {

	/*
	 * And one-up on a phone — at 375 the two columns were 159px each, which is
	 * narrower than the headshot wants to be. The faculty rows already stacked
	 * at 640.
	 */
	.gri-people-grid--students {
		grid-template-columns: 1fr;
		gap: 32px;
	}
}

/*
 * The Get Involved pill is an inner block of core/navigation, not a sibling of
 * it. core/buttons is on the Navigation block's allowedBlocks list precisely so
 * a call to action can ride inside the responsive container: inline beside the
 * links on desktop, and inside the overlay panel once the nav collapses —
 * one button, one href, no duplicate hidden copy to keep in sync.
 *
 * Two adjustments follow from being inside the block. The nav sets a 32px
 * blockGap that would otherwise apply to the pill as just another item, and the
 * overlay stacks its children centred in a column, where a right-justified
 * buttons row looks accidental.
 */
.gri-nav .gri-header__cta {
	margin: 0;
}

/*
 * The overlay stacks its children right-aligned (the Navigation block's own
 * justifyContent), and the pill inherits that, which is what keeps it reading
 * as part of the same list rather than a stray element. It only needs air above
 * it — centring it instead would break that alignment, and setting
 * justify-content on the buttons block does nothing anyway: the block is
 * shrink-to-fit, so its own main axis has no slack. Alignment there comes from
 * the container's align-items.
 */
.gri-nav .wp-block-navigation__responsive-container.is-menu-open .gri-header__cta {
	margin-top: 8px;
}

/*
 * Measured: every overlay row ended at exactly x = viewport width — the links
 * were touching the right edge of the screen. Give the open panel the same 20px
 * gutter the header and sections use on a phone.
 */
.gri-nav .wp-block-navigation__responsive-container.is-menu-open {
	padding-left: 20px;
	padding-right: 20px;
}

/*
 * The nav collapses to the overlay at 900px, not core's 600px.
 *
 * `overlayMenu: "mobile"` hands core a fixed 600px breakpoint. Five links plus
 * the Get Involved pill do not fit beside the logo much below 900: measured at
 * 768 the nav wrapped to two rows and the header grew to 211px, which is worse
 * than a hamburger, not better. Overriding the two visibility rules moves the
 * switch without touching the block's markup or its overlay styling.
 *
 * Change the 900px here (and nowhere else) to move the switch point.
 */
@media (max-width: 900px) {
	.gri-nav.is-responsive .wp-block-navigation__responsive-container:not(.is-menu-open) {
		display: none;
	}

	.gri-nav.is-responsive .wp-block-navigation__responsive-container-open:not(.always-shown) {
		display: flex;
	}
}
