/*
Theme Name: Japan Matsuri Event Navi
Theme URI: https://japanmatsuri.com/nav/
Author: Tsutomu Ishida
Author URI: https://tsutomuishida.com
Description: Event navigation site for Japan Matsuri 2026. Mobile-optimized companion site for use at the venue.
Version: 1.0.0
Requires at least: 6.0
Tested up to: 6.6
Requires PHP: 7.4
License: Proprietary
Text Domain: nav
*/

/* -----------------------------------------------------------------------
   Variables
----------------------------------------------------------------------- */
:root {
	--color-red: #E20213;
	--color-blue: #0086CA;
	--color-white: #ffffff;
	--color-text: #333333;

	/* News section only (template-parts/news-item.php, front-page.php's
	   News block) — kept separate from --color-blue/--color-red so it
	   isn't reused for status labels like UP NEXT/NOW ON STAGE. */
	--color-yellow: #FCEE04;

	/* Shared corner radius between the hero image's bottom edge and the
	   sponsor block's top edge (front-page.php) — the one place to
	   change it so the two stay matched. */
	--block-radius: 16px;

	--header-height: 56px;
	--tab-bar-height: 60px;
	--area-tab-bar-height: 44px;

	/* Sponsor logos grid (template-parts/sponsor-logos.php) column count
	   — the one place to change it, .sponsor-grid reads it back. */
	--sponsor-cols: 3;

	/*
	 * Area colours — the Stalls Map accordion headers (.area-accordion--*
	 * below), matched to each area's own fill on the map SVG. A separate
	 * palette from the stall category colours (nav_get_stall_categories(),
	 * inc/stall-map.php) on purpose, even where a pair lands close in hue
	 * (North Terrace/Food & Drink, South East/Food Products) — the two
	 * are different meaning systems and must stay visually distinguishable
	 * as such, not just individually distinct.
	 */
	--area-north-terrace: #EB8B36;
	--area-west-square:   #C2C558;
	--area-east-square:   #5EA7D2;
	--area-south-east:    #67A851;
}

/* -----------------------------------------------------------------------
   Fonts
----------------------------------------------------------------------- */
@font-face {
	font-family: 'Brandon';
	src: url('fonts/Brandon_light.woff2') format('woff2'),
	     url('fonts/Brandon_light.woff') format('woff');
	font-weight: 300;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: 'Brandon';
	src: url('fonts/Brandon_reg.woff') format('woff');
	font-weight: 400;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: 'Brandon';
	src: url('fonts/Brandon_med.woff2') format('woff2'),
	     url('fonts/Brandon_med.woff') format('woff');
	font-weight: 500;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: 'Brandon';
	src: url('fonts/Brandon_bld.woff2') format('woff2'),
	     url('fonts/Brandon_bld.woff') format('woff');
	font-weight: 700;
	font-style: normal;
	font-display: swap;
}

@font-face {
	font-family: 'Brandon';
	src: url('fonts/Brandon_blk.woff2') format('woff2'),
	     url('fonts/Brandon_blk.woff') format('woff');
	font-weight: 900;
	font-style: normal;
	font-display: swap;
}

/* -----------------------------------------------------------------------
   Reset
----------------------------------------------------------------------- */
*,
*::before,
*::after {
	box-sizing: border-box;
}

html,
body,
h1, h2, h3, h4, h5, h6,
p, ul, ol, li,
figure, figcaption {
	margin: 0;
	padding: 0;
}

ul, ol {
	list-style: none;
}

img {
	max-width: 100%;
	height: auto;
	display: block;
}

a {
	color: inherit;
	text-decoration: none;
}

button {
	font: inherit;
	color: inherit;
	background: none;
	border: 0;
	cursor: pointer;
}

/* -----------------------------------------------------------------------
   Base
----------------------------------------------------------------------- */
body {
	font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Hiragino Sans', 'Noto Sans JP', sans-serif;
	font-size: 14px;
	line-height: 1.5;
	color: var(--color-text);
	background-color: var(--color-white);
	padding-bottom: calc(var(--tab-bar-height) + env(safe-area-inset-bottom));
	-webkit-text-size-adjust: 100%;
}

body.site-nav-open {
	overflow: hidden;
}

/* Brandon is reserved for headings and logo-like brand elements only. */
h1, h2, h3, h4, h5, h6,
.site-header__brand,
.home-menu__label {
	font-family: 'Brandon', -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
	font-weight: 700;
	text-transform: uppercase;
}

.screen-reader-text {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

.skip-link.screen-reader-text:focus {
	position: fixed;
	top: 0;
	left: 0;
	z-index: 10000;
	width: auto;
	height: auto;
	clip: auto;
	padding: 12px 16px;
	background-color: var(--color-white);
	color: var(--color-text);
}

/*
 * The two icon boxes whose contents may be artwork instead of a dashicon
 * glyph: .site-tab-bar__icon (footer.php, 22px) and .home-menu__icon
 * (front-page.php, 32px). Both hold stage-icon.svg; the tab bar's Donate
 * item also holds donate-icon.png, which is a PNG rather than an inline
 * SVG because it is fixed red artwork with nothing to tint (see
 * nav_inline_svg() in functions.php for why the rest are inlined).
 * Sizing lives with each component; only the "fill the box you were
 * given" part is shared, so it sits here rather than being written out
 * twice and left to drift.
 *
 * Dashicons take their size from font-size, artwork from width/height, so
 * 100% is what puts the two kinds of icon in identical boxes. Both files
 * are square, so there is no aspect ratio left to preserve — 100% on each
 * axis cannot distort them.
 *
 * display: block is the part that matters. Left inline, the <svg>/<img>
 * sits on a text baseline and the line box reserves descender space
 * beneath it, pushing the artwork off the bottom of the fixed height and
 * out of line with the dashicon icons beside it.
 */
.site-tab-bar__icon > svg,
.site-tab-bar__icon > img,
.home-menu__icon > svg {
	display: block;
	width: 100%;
	height: 100%;
}

/* -----------------------------------------------------------------------
   Splash screen (header.php, all pages — first access per tab session)
----------------------------------------------------------------------- */
.nav-splash {
	position: fixed;
	inset: 0;
	z-index: 9999;
	background: var(--color-blue);
	display: flex;
	align-items: center;
	justify-content: center;
	animation: nav-splash-out 0.4s ease 1.2s forwards;
}

@keyframes nav-splash-out {
	to {
		opacity: 0;
		visibility: hidden;
	}
}

.nav-splash__badge {
	width: 42vw;
	max-width: 200px;
	min-width: 120px;
	aspect-ratio: 1 / 1;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 50%;
	background-color: var(--color-white);
	overflow: hidden;
}

.nav-splash__logo {
	width: 72%;
	height: 72%;
	object-fit: contain;
}

/* Set on <html> by the inline script in header.php's <head> — repeat
   visit within the same tab session, skip straight to the hidden state
   instead of animating in and out again. */
html.nav-splash-skip .nav-splash {
	display: none;
}

/* JS enhancement only (assets/js/nav.js) — fades the overlay out as soon
   as the page finishes loading, ahead of the fixed 1.2s delay above.
   Removal itself still happens via the same CSS animation, just started
   sooner; JS never sets opacity/display directly. */
.nav-splash.nav-splash--dismiss {
	animation: nav-splash-out 0.3s ease forwards;
}

@media (prefers-reduced-motion: reduce) {
	.nav-splash {
		animation: none;
		opacity: 0;
		visibility: hidden;
	}
}

/* -----------------------------------------------------------------------
   Header
----------------------------------------------------------------------- */

/*
 * 875 puts the header between a sheet's overlay (.map-sheet-overlay, 850)
 * and the sheet itself (.map-sheet, 900). Below the overlay — which is
 * where it was at 500 — the hamburger was covered on the stage programme
 * page for as long as a programme was open: the overlay is inset: 0, so a
 * tap at (30, 28) landed on it and closed the sheet instead of opening the
 * menu. There was no way to reach the menu at all from there.
 *
 * The cost is deliberate and worth stating: tapping outside the sheet
 * closes it (assets/js/bottom-sheet.js), and the header strip is now
 * outside that rule. Tapping the header no longer closes the sheet — it
 * does what the header says it does. Everywhere else the overlay is
 * untouched and still closes on tap, and the × inside the sheet was always
 * the primary way out. Losing 56px of an incidental affordance is the
 * price of the menu being reachable at all.
 *
 * Below the sheet, not above it: the sheet is what the visitor is reading,
 * and a sheet tall enough to reach the top of the screen should cover the
 * header rather than be sliced by it. Neither sheet is that tall today
 * (measured at 375x812: the stall sheet starts at y=406, the stage sheet at
 * y=242, both well clear of the 56px header), so the two do not currently
 * overlap at all — this only settles which way it goes if one ever grows.
 *
 * Still below .site-nav-overlay (1000), so the menu's own overlay covers
 * the header once the drawer is open, exactly as before.
 */
.site-header {
	position: sticky;
	top: 0;
	z-index: 875;
	display: flex;
	align-items: center;
	height: var(--header-height);
	padding: 0 8px;
	padding-top: env(safe-area-inset-top);
	background-color: var(--color-red);
}

.site-header__toggle {
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
	gap: 5px;
	width: 44px;
	height: 44px;
}

.site-header__toggle-bar {
	display: block;
	width: 30px;
	height: 3px;
	background-color: var(--color-white);
}

.site-header__brand {
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	font-size: 16px;
	letter-spacing: 0.08em;
	color: var(--color-white);
	white-space: nowrap;
	pointer-events: none;
}

/* -----------------------------------------------------------------------
   Nav drawer
----------------------------------------------------------------------- */

/*
 * Above the bottom sheet, not below it. The sheet layer (850/900, see
 * .map-sheet-overlay and .map-sheet) is the highest thing a page puts up
 * on its own, and the drawer is the one control that has to stay reachable
 * whatever a page is showing — measured at 375x812 with a stall sheet open
 * and the drawer at 700, the sheet covered everything below y=406 and took
 * Stalls Map / News / Donate / Venue Info out of reach.
 *
 * The overlay carries the same jump as the drawer, which is what stops the
 * sheet being operated through it: inset: 0 over the whole viewport at
 * 1000 puts it above the sheet's 900, so taps land on the overlay (and
 * close the menu) rather than on the sheet behind. Nothing else moves —
 * closing the drawer leaves the sheet exactly as it was.
 *
 * Still below .nav-splash (9999) and the skip link (10000), which are the
 * boot screen and an accessibility affordance, not page content.
 */
.site-nav-overlay {
	position: fixed;
	inset: 0;
	z-index: 1000;
	background-color: rgba(0, 0, 0, 0.4);
	opacity: 0;
	visibility: hidden;
	transition: opacity 0.3s ease;
}

.site-nav-overlay.is-open {
	opacity: 1;
	visibility: visible;
}

.site-nav-drawer {
	position: fixed;
	top: 0;
	left: 0;
	bottom: 0;
	/* One above its own overlay, for the same reason as that 1000. */
	z-index: 1100;
	width: min(80%, 320px);
	overflow-y: auto;
	background-color: var(--color-blue);
	transform: translateX(-100%);
	transition: transform 0.3s ease;
	padding: 16px 24px calc(24px + env(safe-area-inset-bottom));
}

.site-nav-drawer.is-open {
	transform: translateX(0);
}

.site-nav-drawer__close {
	display: flex;
	justify-content: flex-end;
	width: 100%;
	font-size: 36px;
	line-height: 1;
	color: var(--color-white);
	padding: 0;
}

.site-nav-drawer__logo {
	display: flex;
	justify-content: center;
	margin: 8px 0 32px;
}

.site-nav-drawer__logo img {
	max-width: 140px;
}

.site-nav-drawer__menu {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 24px;
}

.site-nav-drawer__menu a {
	font-size: 15px;
	font-weight: bold;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	color: var(--color-white);
}

.site-nav-drawer__menu li.is-current a {
	color: rgba(255, 255, 255, 0.5);
}

/* -----------------------------------------------------------------------
   Bottom tab bar
----------------------------------------------------------------------- */
.site-tab-bar {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 400;
	display: flex;
	height: var(--tab-bar-height);
	padding-bottom: env(safe-area-inset-bottom);
	background-color: var(--color-white);
	box-shadow: 0 -1px 4px rgba(0, 0, 0, 0.15);
	overflow: visible;
}

.site-tab-bar__item {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 2px;
	flex: 1;
	color: #000;
}

.site-tab-bar__item.is-current {
	color: var(--color-red);
}

.site-tab-bar__icon {
	font-size: 22px;
	width: 22px;
	height: 22px;
}

/*
 * nowrap: the longest label ("STALLS MAP") measures 73.45px against the
 * 75px each of the five items gets at a 375px viewport — 1.56px spare.
 * Left to wrap, a label that overshoots goes to two lines and pushes its
 * icon 8.25px up, so that one tab sits visibly higher than the other
 * four. A label overflowing sideways is the lesser of the two.
 */
.site-tab-bar__label {
	font-size: 11px;
	text-transform: uppercase;
	letter-spacing: 0.03em;
	font-weight: 600;
	white-space: nowrap;
}

.site-tab-bar__item--home {
	position: relative;
}

.site-tab-bar__home-block {
	position: absolute;
	left: 50%;
	bottom: 0;
	transform: translateX(-50%);
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 4px;
	width: 68px;
	height: 60px;
	border-radius: 0;
	background-color: var(--color-blue);
	color: #fff;
}

.site-tab-bar__home-block .site-tab-bar__icon {
	width: 24px;
	height: 24px;
	font-size: 24px;
}

.site-tab-bar__home-block .site-tab-bar__label {
	font-size: 11px;
}

/* -----------------------------------------------------------------------
   Hero (front page)
----------------------------------------------------------------------- */
#main-content.front-page {
	background-color: var(--color-yellow);
}

.hero {
	position: relative;
}

.hero__image {
	position: relative;
	aspect-ratio: 5 / 4;
	background-color: var(--color-text);
	overflow: hidden;
	border-bottom-left-radius: var(--block-radius);
	border-bottom-right-radius: var(--block-radius);
}

.hero__image img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.hero__overlay {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	padding: 48px 16px 16px;
	background: linear-gradient(to top, rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0) 100%);
}

.hero__label {
	display: inline-block;
	padding: 6px 14px;
	margin-bottom: 8px;
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.05em;
	text-transform: uppercase;
}

.hero__label--live {
	color: #fff;
	background-color: #E20213;
}

.hero__label--next {
	color: #fff;
	background-color: var(--color-blue);
}

.hero__title {
	font-size: 20px;
	color: var(--color-white);
	margin-bottom: 4px;
	line-height: 1.3;
}

.hero__japanese {
	font-size: 14px;
	color: rgba(255, 255, 255, 0.85);
}

.hero__time {
	margin-top: 8px;
	font-size: 13px;
	font-weight: 600;
	color: var(--color-white);
}

/*
 * Hero with nothing on stage — before the first act and after the last.
 * Same 5/4 box and same bottom corners as .hero__image so the yellow below
 * meets the same shape in all three states and the block does not resize
 * under someone standing there when the stage is called on.
 */
.hero__closed {
	position: relative;
	aspect-ratio: 5 / 4;
	background-color: var(--color-text);
	overflow: hidden;
	border-bottom-left-radius: var(--block-radius);
	border-bottom-right-radius: var(--block-radius);
}

.hero__closed-bg {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * The same scrim .hero__overlay puts under the programme details, for the
 * same reason: the photograph is a bright, busy crowd and white text alone
 * would not hold against it.
 */
.hero__closed-message {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	padding: 48px 16px 16px;
	font-size: 15px;
	font-weight: 700;
	line-height: 1.4;
	text-align: center;
	color: var(--color-white);
	background: linear-gradient(to top, rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0) 100%);
}

/* -----------------------------------------------------------------------
   Stage archive
----------------------------------------------------------------------- */
.stage-archive__heading {
	padding: 16px;
	font-size: 20px;
	text-align: center;
}

.stage-archive__empty {
	padding: 0 16px;
}

.stage-list {
	display: flex;
	flex-direction: column;
}

/*
 * The slot each run of acts belongs to, standing in the list above them.
 * The printed programme leads on these rather than on the acts, and the
 * schedule only commits to the slot anyway — so it is set to be read first
 * and hard to mistake for a card: reversed out of the body colour, full
 * bleed, where every card is a light row with a thumbnail on it.
 */
.stage-list__slot {
	padding: 5px 16px;
	font-size: 15px;
	font-weight: 700;
	letter-spacing: 0.02em;
	color: var(--color-white);
	background-color: var(--color-text);
}

.stage-card {
	border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.stage-card:nth-child(even) {
	background-color: #f5f5f5;
}

.stage-card__trigger {
	position: relative;
	display: flex;
	align-items: center;
	gap: 12px;
	width: 100%;
	padding: 12px 16px;
	text-align: left;
}

.stage-card__thumb {
	position: relative;
	flex: 0 0 auto;
	width: 84px;
	height: 84px;
	overflow: hidden;
	border-radius: 6px;
	background-color: rgba(0, 0, 0, 0.06);
}

.stage-card__thumb img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

.stage-card__thumb-label {
	position: absolute;
	left: 0;
	right: 0;
	bottom: 0;
	height: 25%;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0 4px;
	font-size: 10px;
	font-weight: 700;
	letter-spacing: 0.02em;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.stage-card__thumb-label--live {
	color: #fff;
	background-color: #E20213;
}

.stage-card__thumb-label--next {
	color: #fff;
	background-color: var(--color-blue);
}

.stage-card__thumb-ended {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 100%;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.05em;
	color: #fff;
	background-color: #666;
}

.stage-card__info {
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}

.stage-card__time {
	font-size: 14px;
	font-weight: 600;
	color: var(--color-text);
}

.stage-card__title {
	font-size: 16px;
	font-weight: 700;
	line-height: 1.2;
}

.stage-card__japanese {
	font-size: 12px;
	color: #767676;
	margin-top: 3px;
}

.stage-card.stage-card--live {
	background-color: rgba(226, 2, 19, 0.06);
	border-left: 4px solid var(--color-red);
}

.stage-card--live .stage-card__trigger {
	padding-left: 12px;
}

.stage-card.stage-card--ended .stage-card__info {
	opacity: 0.5;
}

/* -----------------------------------------------------------------------
   Home menu (front page) — Stage Programme / Stalls Map shortcuts,
   between the yellow "Latest News" section and Sponsor logos below.
   Takes over the "white block resting on the yellow section" treatment
   (background + top --block-radius) that used to sit directly on
   .sponsor-logos, since this is now the first white block after yellow;
   .sponsor-logos in turn drops its own copy of that same treatment so
   the two read as one continuous white surface (see comment there).
----------------------------------------------------------------------- */
.home-menu {
	display: flex;
	flex-direction: column;
	gap: 12px;
	padding: 24px 16px;
	background-color: var(--color-white);
	border-top-left-radius: var(--block-radius);
	border-top-right-radius: var(--block-radius);
}

/*
 * Stacked (flex-direction: column above), never side by side, per spec.
 * Bigger than .site-tab-bar__item, this theme's other icon+label tap
 * target (footer.php) — that one is a 22px icon in a fixed-height tab
 * bar; this is a full-width row with generous padding, so both the
 * tappable area and the icon itself (32px, .home-menu__icon below) read
 * as clearly larger, not just visually similar at a bigger crop.
 */
.home-menu__item {
	display: flex;
	align-items: center;
	gap: 16px;
	padding: 20px;
	background-color: #f5f5f5;
	border-radius: var(--block-radius);
	color: var(--color-text);
}

.home-menu__icon {
	flex: 0 0 auto;
	font-size: 32px;
	width: 32px;
	height: 32px;
}

.home-menu__label {
	font-size: 15px;
}

/* -----------------------------------------------------------------------
   Sponsor logos
----------------------------------------------------------------------- */
.sponsor-logos {
	padding: 32px 16px;
	background-color: var(--color-white);
	text-align: center;
}

.sponsor-logos__group + .sponsor-logos__group {
	margin-top: 32px;
}

/* Brandon/uppercase/bold already come from the sitewide h1-h6 rule
   (base.css above) — this only adds the section-heading-specific size,
   spacing and centering. letter-spacing: 0.08em matches
   .site-header__brand, this theme's other wide-tracked uppercase brand
   text, rather than inventing a new value. */
.sponsor-logos__heading {
	font-size: 16px;
	text-align: center;
	letter-spacing: 0.08em;
	color: var(--color-text);
	margin-bottom: 16px;
}

/*
 * flex, not grid: grid's column tracks are shared by every row, so a
 * short last row can't be centered independently of the full rows
 * above it without either JS or PHP knowing the column count (the
 * previous approach — computed filler cells — needed exactly that, a
 * second, easy-to-forget place to keep in sync with --sponsor-cols).
 * flex-wrap + justify-content: center centers ANY incomplete row on
 * its own by construction, with .sponsor-grid__item's flex-basis below
 * the only thing that still needs --sponsor-cols.
 */
.sponsor-grid {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 14px 10px;
}

/*
 * calc() subtracts the (cols - 1) column gaps the row-of-N-items
 * actually has (matching the 10px column-gap above) before dividing,
 * so N items plus their gaps still sum to exactly 100% — without that
 * subtraction, N equal (100/N)% items plus (N-1) more gaps would
 * overflow past the container's right edge.
 */
.sponsor-grid__item {
	flex: 0 0 calc((100% - (var(--sponsor-cols) - 1) * 10px) / var(--sponsor-cols));
	/* min-width: auto is the flex-item default — without overriding it,
	   a logo's own content width can stop this item shrinking to its
	   flex-basis above, the same overflow bug the grid version had. */
	min-width: 0;
	aspect-ratio: 3 / 2;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 0 4px;
}

.sponsor-grid__item img {
	max-width: 100%;
	max-height: 100%;
	width: auto;
	height: auto;
	object-fit: contain;
}

.sponsor-logos__view-all {
	margin: 24px 0 48px;
}

.sponsor-logos__view-all .dashicons {
	font-size: 16px;
	width: 16px;
	height: 16px;
	margin-left: 6px;
	vertical-align: middle;
}

/* -----------------------------------------------------------------------
   Area tabs (Level 2 — switch between area maps directly)
----------------------------------------------------------------------- */
.area-tabs {
	display: flex;
	align-items: stretch;
	height: var(--area-tab-bar-height);
	background-color: var(--color-white);
	border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

.area-tabs__item {
	display: flex;
	flex: 1 1 0;
	align-items: center;
	justify-content: center;
	padding: 0 8px;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.02em;
	color: var(--color-text);
	text-align: center;
	border-right: 1px solid rgba(0, 0, 0, 0.08);
}

.area-tabs__item:last-child {
	border-right: none;
}

.area-tabs__item.is-current {
	background-color: var(--color-blue);
	color: var(--color-white);
}

/* -----------------------------------------------------------------------
   Area map (Level 2 — stall map)
----------------------------------------------------------------------- */
/*
 * svh, not vh — the reason for every svh in this file, so the other three
 * point back here rather than repeat it.
 *
 * vh is defined as equal to lvh: the viewport with the browser's own UI
 * retracted, i.e. the LARGEST it ever gets. On a phone with the address bar
 * showing it therefore measures more screen than there is. Measured on an
 * iPhone 13 Pro in Chrome, 100vh resolves to 844px against roughly 703px of
 * actually visible page — so #map-sheet's half-screen came out at 60% of
 * what the reader could see, and this box overhangs the fold by the same
 * ~141px, putting its bottom edge behind the tab bar.
 *
 * svh is the opposite end: the viewport with that UI fully expanded, the
 * smallest it gets. Content sized to it always fits. The cost is that when
 * the bar does retract, the extra strip goes unused.
 *
 * dvh — which tracks the current size — would use every pixel, and is
 * rejected on purpose. It changes value mid-scroll (MDN warns of exactly
 * this), and this box feeds Panzoom: fitStage() derives the stage size and
 * minScale from its clientHeight, so a height that moves means either
 * destroying and rebuilding Panzoom on every address-bar twitch, or leaving
 * clampOffset()'s bounds disagreeing with the box they are meant to bound.
 * A stable wrong-by-a-strip beats an unstable right. svh also leaves
 * visibleMapHeight() and clampOffset() working exactly as they do now.
 *
 * The vh line above each svh line is the fallback: a browser that cannot
 * parse svh drops that declaration and keeps the vh one, which is today's
 * behaviour. Support starts at iOS Safari 15.4 (and so at that iOS version
 * for every browser on the platform, Chrome included), Chrome 108, Firefox
 * 101 — 94.97% of traffic per caniuse.
 */
.area-map {
	position: relative;
	height: calc(100vh - var(--header-height) - var(--area-tab-bar-height) - var(--tab-bar-height) - env(safe-area-inset-bottom));
	height: calc(100svh - var(--header-height) - var(--area-tab-bar-height) - var(--tab-bar-height) - env(safe-area-inset-bottom));
	overflow: hidden;
	background-color: #F1EFE8;
}

.area-map__viewport {
	position: absolute;
	inset: 0;
	overflow: hidden;
}

.area-map__stage {
	position: absolute;
	top: 0;
	left: 0;
	background-color: #F1EFE8;
	visibility: hidden;
}

.area-map__stage-image {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: fill;
	pointer-events: none;
}

.area-map__svg {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	display: block;
}

.area-map__stall {
	cursor: pointer;
}

.area-map__stall-rect {
	stroke: #fff;
	stroke-width: 3;
}

.area-map__stall:focus {
	outline: none;
}

.area-map__stall:focus .area-map__stall-rect {
	stroke: var(--color-text);
	stroke-width: 4;
}

/*
 * Dims every stall except the highlighted one (?stall=ID from the
 * Stalls Map list, single-area.php) while a highlight is active —
 * :has() lets this stay driven entirely by .is-highlighted already
 * being on the target's own rect (below) rather than needing a second
 * class added to its .area-map__stall group separately.
 */
.area-map__svg.has-stall-highlight .area-map__stall {
	opacity: 0.35;
}

.area-map__svg.has-stall-highlight .area-map__stall:has(.is-highlighted) {
	opacity: 1;
}

/*
 * White outline only — fill is deliberately untouched, since it's the
 * stall's category colour (nav_get_stall_category_color()) and
 * changing it would make that category unreadable for exactly the
 * stall someone's trying to find. paint-order: stroke paints the
 * stroke before the fill, so the fill covers the stroke's inner half
 * and only the outer half — outside the rect's actual edge — stays
 * visible: this reads as the outline growing outward, not the
 * category-coloured fill area shrinking inward.
 */
.area-map__stall-rect.is-highlighted {
	stroke: #fff;
	stroke-width: 9;
	paint-order: stroke;
}

/*
 * A second, separate <rect> (assets/js/stall-map.js creates/removes it
 * per highlight, matching the target's own x/y/width/height/rx) — fill:
 * none so the category-coloured rect underneath keeps reading normally
 * through it, just an expanding-and-fading ring on top. transform-origin
 * is set explicitly per-instance in JS (inline style, since it depends
 * on that stall's own center) rather than left at the CSS default —
 * browsers don't agree on SVG's default transform-origin (some use
 * (0,0), some the element's own bounding-box center), so without it
 * the scale() below could expand from the wrong point depending on
 * browser.
 */
.area-map__stall-pulse {
	fill: none;
	stroke: #000;
	stroke-width: 2.5;
	pointer-events: none;
	animation: nav-stall-pulse 1.5s ease-out infinite;
}

@keyframes nav-stall-pulse {
	0% {
		opacity: 0.8;
		transform: scale(1);
	}

	100% {
		opacity: 0;
		transform: scale(1.6);
	}
}

/* Reduced-motion: keep the white outline as the sole highlight cue,
   drop the pulsing ring entirely rather than merely freezing it. */
@media (prefers-reduced-motion: reduce) {
	.area-map__stall-pulse {
		display: none;
	}
}

/*
 * Fixed at 48 SVG user-space units for every area — NOT scaled per area's
 * own stall data. (Previously derived per-area from that area's smallest
 * stall height, which put North Terrace — its stalls all a uniform 6%
 * height — visibly larger than the other three areas, whose stalls mix in
 * some shorter ones and so pulled their own per-area size down. A single
 * fixed size everywhere reads as consistent instead.) Zoom is not
 * counter-scaled against: the number simply grows/shrinks with its stall
 * under the shared viewBox + Panzoom transform, same as the stall rect
 * itself.
 */
.area-map__stall-label {
	font-family: 'Brandon', -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
	font-size: 42px;
	font-weight: 700;
	fill: #fff;
	pointer-events: none;
	text-anchor: middle;
}

/*
 * Facility stalls (ACF 'stall_type' === 'facility', e.g. Japan Matsuri
 * Info) show a single letter (ACF 'stall_label') instead of a number —
 * serif rather than the sans-serif above, since a sans-serif capital "I"
 * is just a thin vertical stroke at this size, while a serif face's
 * top/bottom strokes keep it legible as a letter even alone. System serif
 * only — no webfont added for one letter.
 */
.area-map__stall-label--facility {
	font-family: Georgia, "Times New Roman", serif;
	font-weight: 700;
	/* Georgia's cap-height is visibly shorter than the sans-serif number
	   font's at the same font-size (measured ~41px vs ~51.5px tall glyph
	   bounding box on real stall data) — scaled up so a facility's letter
	   reads as the same size as an exhibitor's number, not smaller.
	   Deliberately NOT re-derived from the 42px above: there are only a
	   handful of facilities and each is a single letter, so they carry a
	   full-width map at a size the crowded stall numbers no longer can. */
	font-size: 60px;
}

/*
 * The map's two top corners — the area name card (left) and the Stall List
 * button (right) — look nothing alike, but they must hang from the same top
 * edge at the same inset, so that much is shared and can't drift apart.
 *
 * position: absolute, unlike the fixed .area-map__legend / .area-map__zoom
 * below. Those are fixed because they anchor upwards from the tab bar, which
 * isn't .area-map's own bottom edge (see the comment there). Anchoring to
 * the TOP has no such problem — .area-map's top edge is exactly where these
 * belong — and fixed would actively break it, measuring 12px from the
 * browser viewport and landing them under the site header and area tabs.
 * Absolute still doesn't move with pan/zoom: that transform is on
 * #area-map-stage, inside .area-map__viewport, and these are siblings of
 * the viewport rather than children of the stage.
 */
.area-map__list-btn,
.area-map__area-label {
	position: absolute;
	top: 12px;
	z-index: 10;
}

.area-map__list-btn {
	right: 12px;
	display: inline-flex;
	align-items: center;
	gap: 6px;
	min-height: 44px;
	padding: 0 18px;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.03em;
	color: var(--color-white);
	background-color: var(--color-blue);
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
	border-radius: 999px;
}

/*
 * Bare white text straight onto the map — no card, no plate behind it. The
 * legibility therefore comes entirely from what the map happens to put under
 * this corner, so a change to the map image is a change to this label too.
 */
.area-map__area-label {
	left: 12px;
	/* Leaves the Stall List button opposite its own room. A long area name
	   ("South East Corner") wraps to a second line — growing downwards, into
	   map — rather than sliding under the button. Proportional rather than
	   "100% minus the button's width" so it doesn't silently break the day
	   that button is relabelled. */
	max-width: 50%;
	/* A label, not a control: nothing happens on tap, so it shouldn't eat
	   the drag either. Without this it would be a dead zone for panning in
	   the map's top-left corner, which the Stall List button gets away with
	   only because tapping it genuinely does something. */
	pointer-events: none;
	/* Set here rather than on the two children because text-shadow inherits,
	   so one declaration covers both and they can't drift apart. Kept soft
	   and barely offset on purpose — it should read as the white glyphs
	   holding their own edge against the pale khaki and green the map puts
	   under this corner (measured 1.7-2.8:1 against white across the four
	   areas, all below the WCAG minimum), not as a shadow anyone notices.
	   Strength lives in the alpha rather than the blur or the offset: a
	   wider blur turns into a visible grey halo and a bigger offset starts
	   to look embossed, while a darker core just firms up the edge. */
	text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
}

/* Also the page's <h1> — hence the margin reset, which is undoing the
   theme's heading default rather than decoration. text-transform is stated
   rather than inherited from that same default: this label's case is a
   deliberate choice, not something to change quietly the day the site's
   headings stop being uppercase. */
.area-map__area-label-name {
	margin: 0;
	text-transform: uppercase;
	font-size: 28px;
	font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Hiragino Sans', 'Noto Sans JP', sans-serif;
	font-weight: 700;
	line-height: 1.2;
	color: #fff;
}

.area-map__area-label-count {
	margin: 3px 0 0;
	font-size: 16px;
	font-weight: 700;
	line-height: 1.2;
	color: #fff;
}

.area-map__list-btn .dashicons {
	font-size: 18px;
	width: 18px;
	height: 18px;
}

/*
 * Legend and zoom controls sit 12px above the tab bar, not 12px above
 * .area-map's own bottom edge. Those are the same place only while nothing
 * else is in the flow above the map: .area-map's height subtracts the
 * header, area tabs and tab bar from 100svh, so any extra in-flow block
 * (today: the dev-only .nav-test-time-banner) pushes its bottom edge down
 * *past* the tab bar and takes these two with it. Anchoring to the viewport
 * and measuring up from --tab-bar-height instead pins them to the thing
 * they must never cover, whatever precedes the map. Same 12px as their
 * left/right insets below; safe-area added for the home-indicator gap the
 * tab bar itself already pads for.
 */
.area-map__legend {
	position: fixed;
	left: 12px;
	bottom: calc(var(--tab-bar-height) + env(safe-area-inset-bottom) + 12px);
	z-index: 10;
	display: flex;
	flex-direction: column;
	/* Tighter than the 6px this was set at when the legend always listed
	   four categories — it can now run to six (nav_get_area_legend()). */
	gap: 3px;
	padding: 10px 12px;
	background-color: rgba(255, 255, 255, 0.92);
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
	border-radius: 10px;
}

.area-map__legend-item {
	display: flex;
	align-items: center;
	gap: 6px;
	font-size: 11px;
	color: var(--color-text);
}

.area-map__legend-swatch {
	flex: 0 0 auto;
	width: 10px;
	height: 10px;
	border-radius: 3px;
}

/* Anchored to the tab bar for the same reason as .area-map__legend above. */
.area-map__zoom {
	position: fixed;
	right: 12px;
	bottom: calc(var(--tab-bar-height) + env(safe-area-inset-bottom) + 12px);
	z-index: 10;
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.area-map__zoom-btn {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	font-size: 22px;
	line-height: 1;
	color: var(--color-text);
	background-color: rgba(255, 255, 255, 0.85);
	border: 1px solid rgba(0, 0, 0, 0.15);
	border-radius: 50%;
	box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}

.area-map__zoom-btn--reset {
	font-size: 18px;
}

/* -----------------------------------------------------------------------
   Map page (Level 1 — whole-venue overview, no pan/zoom)
----------------------------------------------------------------------- */
.map-page {
	display: flex;
	flex-direction: column;
	/* svh with a vh fallback — see .area-map above for why. */
	height: calc(100vh - var(--header-height) - var(--tab-bar-height) - env(safe-area-inset-bottom));
	height: calc(100svh - var(--header-height) - var(--tab-bar-height) - env(safe-area-inset-bottom));
	overflow: hidden;
	background-color: #F1EFE8;
}

/*
 * Takes whatever height .stall-list below doesn't claim — flex: 1 1 auto
 * lets it both grow (accordions collapsed, list short) and shrink (one
 * open, list pushing against its own max-height) as that sibling's height
 * changes. The <svg> inside is fitted to whatever it gets, so the map is
 * smaller while a list is open and full size again once it is shut.
 *
 * min-height: 0 overrides the flex item default of min-height: auto, which
 * would otherwise keep this box from shrinking below its own content's
 * natural size — its only content is an <svg> told to fill 100% of it, so
 * that natural size is essentially unbounded and without this the frame
 * would refuse to give up any space to the list at all.
 *
 * position: relative is the containing block for .map-overlay below.
 */
.map-frame {
	position: relative;
	flex: 1 1 auto;
	min-height: 0;
	overflow: hidden;
}

/*
 * Wraps the map so that inert has somewhere to go while .map-overlay is
 * up — see page-map.php for why it cannot go on the <svg> or on
 * .map-frame. Needs its own height because .map-svg below asks for 100%
 * of its parent, and a percentage against an auto-height box resolves to
 * auto.
 */
.map-canvas {
	width: 100%;
	height: 100%;
}

.map-svg {
	width: 100%;
	height: 100%;
	display: block;
}

/*
 * Covers the map while the list underneath is expanded, and brings it back
 * when tapped. At that point the frame is 94px tall at 375x667 and meet
 * has fitted the whole venue into it, so the map is a thumbnail too small
 * to read — dimmed and labelled it reads as put away, undimmed it reads as
 * broken.
 *
 * Black at 0.5. The label crosses the middle of that thumbnail, which is
 * the pale yellow of Trafalgar Square: compared in the browser at 375x667
 * with an accordion open, 0.35 left it bright enough to compete with the
 * words and barely darkened the grey letterbox either side, 0.6 was
 * legible but took the map down to a silhouette. 0.5 holds the label and
 * still leaves the shape of the venue recognisable.
 *
 * The [hidden] rule is not specificity escalation over another author's
 * selector: display on the element itself outranks the UA stylesheet's
 * own [hidden] { display: none }, so a flex box that wants to hide has to
 * say so. One state, the hidden attribute, drives both this and the
 * inert on .map-canvas (assets/js/nav.js).
 */
.map-overlay {
	position: absolute;
	inset: 0;
	z-index: 1;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	font-size: 15px;
	font-weight: 700;
	letter-spacing: 0.02em;
	color: var(--color-white);
	background-color: rgba(0, 0, 0, 0.5);
	border: none;
	cursor: pointer;
}

.map-overlay[hidden] {
	display: none;
}

.map-hit {
	fill: transparent;
}

/*
 * Keyboard focus lands on the wrapping <a> (SVG <a href> is natively
 * focusable), not on .map-hit itself, so that case is matched via the
 * parent instead — :active still applies directly since .map-hit is what
 * actually receives the pointer-down.
 */
.map-svg a:focus-visible .map-hit,
.map-hit:active {
	fill: rgba(255, 255, 255, 0.35);
}

/*
 * Sized by its own content, and .map-frame above takes what is left. Shut,
 * that content is just the accordion headings — 205px for By Area's four,
 * 257px for By Category's five — so the map keeps most of the page and no
 * cap is needed at all.
 *
 * There is deliberately no max-height here. The old 60% applied in both
 * states, which meant a collapsed list on a tall phone was held below its
 * natural height for no reason. The cap now arrives with .is-expanded
 * below, only when there is something to cap.
 */
.stall-list {
	flex: 0 0 auto;
	min-height: 0;
	overflow-y: auto;
	-webkit-overflow-scrolling: touch;
	background-color: var(--color-white);
}

/*
 * Put on by assets/js/nav.js whenever the visible list is showing more
 * than its headings: an accordion open in By Area or By Category, or the
 * A-Z tab, which has no accordion to shut and would otherwise stand at its
 * full 3737px. The list stops at three quarters of the page and scrolls
 * the rest inside itself; the map above keeps the last quarter, dimmed by
 * .map-overlay, so it is still there to go back to.
 *
 * 75% rather than the 60% this page started with: at that point the list
 * was capped even when collapsed, so 60% was a compromise between two
 * states. This value only ever applies to the open one, where the list is
 * what the visitor is reading.
 *
 * A class rather than an inline height because the two numbers that decide
 * it — three quarters, and of what — belong in the stylesheet with the
 * rest of this page's proportions; the script's job is only to say which
 * of the two states the list is in.
 */
.stall-list.is-expanded {
	max-height: 75%;
}

/*
 * By Area / By Category / All, above the three panels. Same even split
 * and same selected treatment as .area-tabs (Level 2) — a visitor meets
 * that bar one tap later, and the two should not look like different
 * mechanisms. Its own rule rather than a shared class because this one
 * sits inside the flex column of .map-page and has to be told not to
 * grow; the tabs divide the width between them, so a fourth would need
 * nothing here beyond fitting its label.
 */
.stall-list-tabs {
	flex: 0 0 auto;
	display: flex;
	align-items: stretch;
	height: var(--area-tab-bar-height);
	background-color: var(--color-white);
	border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.stall-list-tabs__tab {
	display: flex;
	flex: 1 1 0;
	align-items: center;
	justify-content: center;
	padding: 0 8px;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.02em;
	color: var(--color-text);
	text-align: center;
	background-color: transparent;
	border: none;
	border-right: 1px solid rgba(0, 0, 0, 0.08);
	cursor: pointer;
}

.stall-list-tabs__tab:last-child {
	border-right: none;
}

.stall-list-tabs__tab.is-current {
	background-color: var(--color-blue);
	color: var(--color-white);
}

/* The A-Z tab's own line above its rows, in place of the headings the
   other two tabs carry their counts on. Same size and grey as
   .stall-list__area-count below, since it is doing the same job. */
.stall-list__all-count {
	padding: 12px 16px 4px;
	font-size: 13px;
	color: #767676;
}

/* Same padding the accordion bodies give their rows
   (.stall-list__area-body), minus that rule's border-top — there is no
   coloured heading above this list to be separated from. */
.stall-list__all-body {
	padding: 8px 16px;
}

.stall-list__area {
	border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.stall-list__area:first-child {
	border-top: none;
}

/* Also reused, unstyled, as a plain <div> header for a 0-stall area (see
   .stall-list__area--empty) — nothing below to disclose, so it's never a
   <summary> there and these interactive/marker rules just don't apply. */
.stall-list__area-header {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 14px 16px;
	cursor: pointer;
	list-style: none;
}

.stall-list__area-header::-webkit-details-marker {
	display: none;
}

.stall-list__area-name {
	flex: 1;
	min-width: 0;
	font-size: 15px;
	font-weight: 700;
}

.stall-list__area-count {
	font-size: 13px;
	color: #767676;
}

.stall-list__area-icon {
	flex: 0 0 auto;
	font-size: 20px;
	width: 20px;
	height: 20px;
	color: var(--color-text);
	transition: transform 0.2s ease;
}

.stall-list__area[open] > .stall-list__area-header .stall-list__area-icon {
	transform: rotate(180deg);
}

/* Horizontal padding only — .map-sheet__list-item (reused unchanged below)
   normally gets its own from .map-sheet__body in the bottom-sheet context;
   this replaces that here without touching the shared class itself.
   border-top is the "clear separator" between the (area-coloured)
   header and the (always white) stall rows below it — deliberate, so
   an area colour and a stall's own category colour immediately below
   it never read as touching/related. */
.stall-list__area-body {
	padding: 8px 16px 8px;
	background-color: var(--color-white);
	border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.stall-list__area--empty {
	opacity: 0.5;
}

.stall-list__area--empty .stall-list__area-header {
	cursor: default;
}

/*
 * Area colour on the accordion header only — never the stall rows in
 * .stall-list__area-body, which stay plain white regardless (see the
 * --area-* custom properties' own comment, :root above, for why the
 * two colour systems must stay visually separate). Background comes
 * from the per-area modifier classes below; black text is shared here
 * since it's required for all 4 (see those classes for the contrast
 * numbers) rather than repeated per modifier.
 */
.area-accordion > .stall-list__area-header,
.area-accordion > .stall-list__area-header .stall-list__area-count,
.area-accordion > .stall-list__area-header .stall-list__area-icon {
	color: #000;
}

/*
 * Matched to each area's own fill on the map SVG (--area-* custom
 * properties, :root above). Black text throughout — measured against
 * all 4, white text's contrast ratio comes out under 3:1 on every one
 * (the WCAG minimum for large/bold UI text), so black is the only
 * option that reads reliably on all of them at once rather than
 * special-casing text colour per area.
 */
.area-accordion--north-terrace > .stall-list__area-header {
	background-color: var(--area-north-terrace);
}

.area-accordion--west-square > .stall-list__area-header {
	background-color: var(--area-west-square);
}

.area-accordion--east-square > .stall-list__area-header {
	background-color: var(--area-east-square);
}

.area-accordion--south-east-corner > .stall-list__area-header {
	background-color: var(--area-south-east);
}

/*
 * By Category uses the same accordion, with the stall category palette
 * from nav_get_stall_categories() (inc/stall-map.php) painted inline on
 * each heading — those five colours are not CSS custom properties the way
 * the four area colours above are, so there is nothing here to read back.
 * Only the text colour lives in CSS, because it is the one part that has
 * to be decided per category rather than per stall.
 *
 * Unlike the areas above, one text colour does not serve all five. The
 * heading is 15px bold, which WCAG counts as normal text (large starts at
 * 18.66px bold), so it needs 4.5:1. Measured against each background:
 *
 *   Food & Drink      #E8792B   black 7.20:1   white 2.92:1
 *   Food Products     #4A9D5F   black 6.28:1   white 3.34:1
 *   Crafts & Goods    #7B5EA7   black 4.00:1   white 5.25:1
 *   Travel & Tourism  #C13E7E   black 4.26:1   white 4.93:1
 *   Info & Experience #2B9CAD   black 6.46:1   white 3.25:1
 *
 * Black passes on three and fails on the two purple-to-magenta ones;
 * white is the mirror image of that. So black is the default here, as it
 * is for the areas, and the two that black cannot carry are named below.
 */
.category-accordion > .stall-list__area-header,
.category-accordion > .stall-list__area-header .stall-list__area-count,
.category-accordion > .stall-list__area-header .stall-list__area-icon {
	color: #000;
}

.category-accordion--crafts-goods > .stall-list__area-header,
.category-accordion--crafts-goods > .stall-list__area-header .stall-list__area-count,
.category-accordion--crafts-goods > .stall-list__area-header .stall-list__area-icon,
.category-accordion--travel-tourism > .stall-list__area-header,
.category-accordion--travel-tourism > .stall-list__area-header .stall-list__area-count,
.category-accordion--travel-tourism > .stall-list__area-header .stall-list__area-icon {
	color: var(--color-white);
}

/* -----------------------------------------------------------------------
   News (archive page-news.php + front-page.php preview block; shared
   row markup lives in template-parts/news-item.php)
----------------------------------------------------------------------- */
.news-archive__empty {
	padding: 0;
}

.news-list {
	display: flex;
	flex-direction: column;
}

.news-item {
	display: flex;
	align-items: baseline;
	gap: 14px;
	padding: 12px 12px;
	border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.news-item:first-child {
	border-top: none;
}

/* Same grey as .stage-card:nth-child(even) — no grey custom property
   exists to reuse instead, so this matches that literal value. */
.news-item:nth-child(odd) {
	background-color: #f5f5f5;
}

.news-item__time {
	flex: 0 0 auto;
	font-size: 14px;
	font-weight: 600;
	color: var(--color-text);
}

/* No fixed height/line-clamp: the title always wraps to show its full
   text, never truncated. */
.news-item__title {
	flex: 1;
	min-width: 0;
	font-size: 16px;
	font-weight: 600;
	line-height: 1.3;
	overflow-wrap: break-word;
}

/* Archive (page-news.php) shares this section-level treatment with the
   front-page preview — same yellow band, same padding, same centering —
   so .news-archive is listed alongside .news-preview rather than kept as
   a separate white-page style. */
.news-preview,
.news-archive {
	padding: 16px;
	text-align: center;
	background-color: var(--color-yellow);
}

/* letter-spacing 0.08em matches .sponsor-logos__heading, this theme's
   other centered section-heading treatment. The front-page preview used to
   share this; it now runs headingless, so the archive is the only user. */
.news-archive__heading {
	font-size: 20px;
	text-align: center;
	letter-spacing: 0.08em;
	color: #000;
	margin-bottom: 16px;
}

/*
 * Shared by .sponsor-logos__view-all below — a single "view all" button
 * style, so the two can never drift apart. inline-block (not the flex
 * layout these used before) so the button sizes to its own content
 * instead of stretching full-width; centering comes from the ancestor
 * section's text-align: center (.news-preview / .sponsor-logos) instead
 * of an on-element flex justify-content. .info-page__programme-cta
 * (page-info.php) joins the group too — its own ancestor sets the same
 * text-align: center (.info-page__section--programme, style.css below).
 * .map-sheet__link (stage programme bottom sheet, archive-stage_program.php)
 * joins for the same weight/tracking/colors/radius, but is sized down
 * separately below (.map-sheet__link override, "Bottom sheet" section) —
 * a row of several of these sits inside the sheet's tighter width, so
 * the full-size padding/font-size here would wrap too eagerly.
 */
.news-preview__view-all,
.sponsor-logos__view-all,
.donate-page__cta,
.info-page__programme-cta,
.map-sheet__link {
	display: inline-block;
	padding: 10px 32px;
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.03em;
	color: var(--color-white);
	background-color: var(--color-blue);
	border-radius: 50px;
}

.news-preview__view-all {
	margin-top: 24px;
}

/*
 * Card look shared by both the front-page preview and the full archive
 * (page-news.php) — both put this modifier on their .news-list wrapper.
 * Base .news-item above (zebra rows, border-top separators) is now dead
 * code for real pages, kept only as the pre-modifier fallback rather
 * than removed. Modifier lives on the list wrapper rather than a second
 * template, per news-item.php's own doc comment: one row markup, looks
 * switched by an ancestor class.
 */
.news-list--front {
	gap: 12px;
}

.news-list--front .news-item {
	align-items: center;
	background-color: var(--color-white);
	border-top: none;
	border-radius: 8px;
}

.news-list--front .news-item__time {
	color: var(--color-red);
	font-size: 20px;
	font-weight: 400;
}

.news-list--front .news-item__title {
	color: #000;
	text-align: left;
}

/* -----------------------------------------------------------------------
   Donate page
----------------------------------------------------------------------- */
.donate-page {
	padding: 24px 16px 40px;
}

/* Campaign logo between the appeal copy and the button it leads into. No
   .nav-splash__badge white circle behind it — that badge exists to lift the
   logo off the splash's blue, and this page is already white.
   The margins are dictated by the file, which is a 360px square holding
   172x253 of artwork: it brings its own transparent padding, rendering here
   as 23.6px above the mark and 24px below. That bottom padding is left to
   supply the gap down to the button, since it already matches the 24px step
   .donate-page__content uses. The top one, however, lands on top of that
   same 24px and read as a 48px hole once the logo moved in here, so it is
   cancelled: margin-top -24px against .donate-page__content's margin-bottom
   collapses the pair to 0 and leaves the file's padding as the only gap, so
   the mark sits 24px clear on both sides. The padding is also why the box is
   160px rather than something nearer the text — it only ever shows 76x112 of
   actual logo. Every number here is measured off this artwork; if the year's
   file arrives trimmed differently, measure it again. */
.donate-page__logo {
	display: block;
	width: 160px;
	height: 160px;
	margin: -24px auto 0;
	object-fit: contain;
}

.donate-page__heading {
	font-size: 20px;
	text-align: center;
	margin-bottom: 16px;
}

.donate-page__content {
	margin-bottom: 24px;
	font-size: 15px;
	line-height: 1.6;
}

.donate-page__content p {
	margin-bottom: 1em;
}

/* Same pill as .news-preview__view-all / .sponsor-logos__view-all above,
   plus the icon layout those two don't need (this button also carries a
   heart + "opens in a new tab" external-link icon). display: flex (not
   inline-flex) + width: fit-content + margin auto centers the button
   itself within .donate-page, since .donate-page__content above it is
   left-aligned body copy rather than a centered ancestor these two could
   otherwise lean on. */
.donate-page__cta {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	width: fit-content;
	margin: 0 auto;
}

.donate-page__cta .dashicons {
	flex: 0 0 auto;
	font-size: 18px;
	width: 18px;
	height: 18px;
}

.donate-page__cta--disabled {
	color: rgba(0, 0, 0, 0.4);
	background-color: rgba(0, 0, 0, 0.08);
	cursor: not-allowed;
}

/* -----------------------------------------------------------------------
   Bottom sheet (shared: stall detail / stall list)
----------------------------------------------------------------------- */
.map-sheet-overlay {
	position: fixed;
	inset: 0;
	z-index: 850;
	background-color: rgba(0, 0, 0, 0.5);
	opacity: 0;
	transition: opacity 0.28s ease;
}

.map-sheet-overlay.is-open {
	opacity: 1;
}

/*
 * Level 2 only: the stall sheet gets no dimming layer behind it. The map
 * stays lit and live while the sheet is up, which is the point of a sheet
 * that covers 50svh rather than the screen — someone reading about a stall
 * can pan and zoom the map underneath to see where it actually is.
 *
 * The element stays in the markup (template-parts/area-map.php) rather than
 * being deleted: NavBottomSheet needs all three of sheet/overlay/body to
 * exist and returns null — no sheet at all — when any is missing
 * (assets/js/bottom-sheet.js). display: none takes it out of both the paint
 * and hit-testing, so it can neither dim the map nor swallow a drag meant
 * for it. Opacity or pointer-events alone would only do one of those.
 *
 * Closing is the × button alone from here on. Tapping the overlay was the
 * other way and goes with it; that is deliberate, not an oversight.
 *
 * The stage programme sheet keeps its overlay (#stage-sheet-overlay,
 * archive-stage_program.php) — the list behind it is a plain scrolling page
 * with nothing to pan, so there is nothing there to uncover.
 */
#map-sheet-overlay {
	display: none;
}

.map-sheet {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 900;
	/* svh with a vh fallback — see .area-map above for why. */
	max-height: 75vh;
	max-height: 75svh;
	overflow-y: auto;
	padding-bottom: env(safe-area-inset-bottom);
	background-color: var(--color-white);
	border-radius: 16px 16px 0 0;
	box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.25);
	transform: translateY(100%);
	transition: transform 0.28s ease;
}

/*
 * The stall sheet alone gets a fixed height. The stage programme sheet
 * (#stage-sheet, archive-stage_program.php) carries the same .map-sheet
 * class and keeps the content-sized max-height above.
 *
 * Scoped by ID rather than by a modifier class because both sheets already
 * have an ID of their own (#map-sheet in template-parts/area-map.php,
 * #stage-sheet in archive-stage_program.php) — no new hook to add, and no
 * !important needed either, an ID simply outranking a class. The inherited
 * max-height: 75svh still applies here and does nothing, 50svh being under
 * its ceiling.
 *
 * Why only this one. A stall's sheet sized to its content ran from 257.5px
 * (no photo) to 512px (photo and a link) at 375x812, so the map behind it
 * was uncovered by a different amount on every stall — that jumping is what
 * this pins down. The stage programme sheet has no such spread: its 34
 * entries measured 558.2-609px, already within 51px of each other, and
 * 50svh (406px) would have shrunk all 34 of them while forcing the 3 that
 * still fitted whole into scrolling. It would be a fix for a problem that
 * sheet does not have.
 *
 * border-box (the reset at the top of this file) puts .map-sheet's
 * padding-bottom: env(safe-area-inset-bottom) INSIDE this height, so 50svh
 * is the sheet's real on-screen height on a notched phone too, not 50svh
 * plus the home-indicator gap. overflow-y: auto up there does the rest:
 * against a height rather than a max-height it now both holds the box open
 * on short content and scrolls the long content that used to grow it.
 */
#map-sheet {
	/* svh with a vh fallback — see .area-map above for why. This is the
	   declaration the 60%-of-the-screen report was about. */
	height: 50vh;
	height: 50svh;
}

.map-sheet.is-open {
	transform: translateY(0);
}

.map-sheet__handle {
	width: 36px;
	height: 4px;
	margin: 10px auto 0;
	border-radius: 2px;
	background-color: rgba(0, 0, 0, 0.15);
}

.map-sheet__close {
	position: sticky;
	top: 0;
	z-index: 1;
	display: flex;
	justify-content: flex-end;
	width: 100%;
	padding: 0 16px;
	font-size: 28px;
	line-height: 1;
	color: var(--color-text);
	background-color: var(--color-white);
}

.map-sheet__body {
	padding: 0 20px 24px;
}

.map-sheet__image {
	aspect-ratio: 4 / 3;
	margin: 0 -4px 12px;
	overflow: hidden;
	border-radius: 12px;
	background-color: rgba(0, 0, 0, 0.06);
}

.map-sheet__image img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}

/*
 * Level 2 only: the stall's photo whole, at whatever shape it was shot in,
 * instead of the 4:3 box above. Neither that box nor the 140px band that
 * replaced it could show a photo without cutting the top and bottom off it,
 * and these are single snapshots of a stall front — the thing worth seeing
 * is often the part a centred crop throws away.
 *
 * Affording it the room is what moving the photo below the description
 * bought (template-parts/area-map.php): nothing after it has to be on
 * screen when the sheet opens, so its height costs nothing.
 *
 * Scoped by ID, not by a new class: the stage programme sheet uses this same
 * .map-sheet__image (archive-stage_program.php) where the photo IS the
 * subject and the 4:3 box is right, and both sheets already carry an ID of
 * their own — so #map-sheet outranks the class with no !important and
 * nothing new to add to the markup.
 *
 * aspect-ratio: auto is stated rather than assumed, the base rule's 4 / 3
 * being inherited here otherwise. With no ratio and no height the box takes
 * the height of the img inside it, which is the whole point.
 *
 * height: auto on the img for the same reason, against the base rule's
 * 100%. A percentage height resolved against a parent that has no definite
 * height of its own is the sort of thing engines have historically differed
 * on; auto says outright that the width and the file's own proportions are
 * what decide it. It is what the img reset at the top of this file already
 * asks for — the base .map-sheet__image img outranks that reset, so getting
 * back to it takes a selector that outranks the base rule in turn.
 *
 * That reset also carries display: block, which is what keeps the box's own
 * grey placeholder from showing as a strip of descender space under every
 * photo — so it needs no restating here. object-fit: cover is likewise left
 * alone: the box and the image are now the same shape, so it crops nothing.
 */
#map-sheet .map-sheet__image {
	aspect-ratio: auto;
}

#map-sheet .map-sheet__image img {
	height: auto;
}

.map-sheet__title {
	font-size: 18px;
	margin-bottom: 4px;
	line-height: 1.3;
}

.map-sheet__heading {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-bottom: 4px;
}

.map-sheet__heading .map-sheet__title {
	margin-bottom: 0;
}

/* Same knocked-out-number-on-category-colour treatment as
   .map-sheet__list-number (the Stall List rows) — background set inline
   per stall (nav_get_stall_category_color(), area-map.php). */
.map-sheet__number {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	font-size: 13px;
	font-weight: 700;
	color: #fff;
	border-radius: 50%;
}

/* Same serif treatment as .area-map__stall-label--facility (style.css),
   and for the same reason — a single-letter facility label. */
.map-sheet__number--facility,
.map-sheet__list-number--facility {
	font-family: Georgia, "Times New Roman", serif;
	font-weight: 700;
}

.map-sheet__japanese {
	font-size: 14px;
	margin-bottom: 8px;
}

.map-sheet__time {
	font-size: 14px;
	font-weight: 600;
	color: var(--color-red);
	margin-bottom: 0px;
}

.map-sheet__performer {
	font-size: 13px;
	font-weight: 600;
	margin-bottom: 20px;
}

.map-sheet__category {
	font-size: 13px;
	color: #767676;
	margin-bottom: 8px;
}

.map-sheet__congestion {
	display: inline-block;
	padding: 3px 10px;
	margin-bottom: 12px;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.03em;
	color: #fff;
	border-radius: 999px;
}

.map-sheet__congestion--busy {
	background-color: #F5A623;
}

.map-sheet__congestion--very_busy {
	background-color: var(--color-red);
}

.map-sheet__content {
	font-size: 14px;
	line-height: 1.6;
}

.map-sheet__content p {
	margin-bottom: 0.8em;
}

/* Row of external-link buttons under the description (stage programme
   bottom sheet only — archive-stage_program.php). Wraps, since some
   programmes carry several links (up to 4). */
.map-sheet__links {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin-top: 16px;
}

/* Same pill as .donate-page__cta / .news-preview__view-all (shared group
   above), one size down: smaller padding/font-size to suit a row of
   several sitting inside the sheet's own width. */
.map-sheet__link {
	padding: 6px 18px;
	font-size: 12px;
}

.map-sheet__list-header {
	padding-top: 4px;
	margin-bottom: 12px;
}

.map-sheet__list-count {
	font-size: 13px;
	color: #767676;
}

.map-sheet__list {
	display: flex;
	flex-direction: column;
}

/*
 * flex-start, not center: a stall can carry several categories now
 * (inc/stall-map.php, nav_resolve_stall_category_display()), one per
 * line in .map-sheet__list-text below, so rows are no longer a uniform
 * height — center would float .map-sheet__list-number in the middle of
 * however tall that stack of lines ends up, rather than level with the
 * title's own first line. .map-sheet__congestion below keeps its own
 * center via align-self, since it's a single-line badge unaffected by
 * how many category lines its row has.
 */
/*
 * 8px, not the 12px this started at: the lists are long enough (63 stalls
 * in the All tab, 27 in North Terrace alone) that four pixels a row is
 * most of a screenful. The floor on how far this can go is the tap target,
 * not the look — these rows get tapped outdoors, walking. Measured at
 * 375x667, the shortest row left is the facility's, at 45px: one title
 * line and no category line under it, so 21 + 8 + 8 + the 1px border. Every
 * other row carries a category line and comes out at 56.5px or more.
 * Dropping to 6px would put that shortest row at 41px, under the 44px
 * minimum, so this is as tight as the padding goes.
 */
.map-sheet__list-item {
	display: flex;
	align-items: flex-start;
	gap: 12px;
	width: 100%;
	padding: 8px 0;
	text-align: left;
	border-top: 1px solid rgba(0, 0, 0, 0.08);
}

/*
 * A pill wide enough for the widest label there is, and that wide on every
 * row. List rows carry the number with its area's letter in front (N1,
 * SE12 — see nav_get_area_stalls()'s prefixed_label), so what goes in here
 * is one to four characters. Sized to the text alone, the badge ran from
 * 28px to 41.93px and every row started its stall name at a different
 * place; 42px is that measured maximum (SE10, the widest of the 64),
 * rounded up, so the names line up.
 *
 * min-width rather than width, with the padding kept, so this is a floor
 * and not a cap: a longer label than today's still gets its own room
 * rather than spilling out of the pill. border-radius is a large constant
 * rather than 50%, which on a box this shape would draw an ellipse.
 */
.map-sheet__list-number {
	flex: 0 0 auto;
	display: flex;
	align-items: center;
	justify-content: center;
	min-width: 42px;
	width: auto;
	height: 28px;
	padding: 0 6px;
	font-size: 12px;
	font-weight: 700;
	color: #fff;
	background-color: var(--color-text);
	border-radius: 999px;
}

.map-sheet__list-text {
	flex: 1;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
}

.map-sheet__list-title {
	font-size: 14px;
	font-weight: 600;
}

.map-sheet__list-category {
	font-size: 11px;
	font-weight: 600;
}

.map-sheet__list .map-sheet__congestion {
	flex: 0 0 auto;
	align-self: center;
	margin-bottom: 0;
}

/*
 * The chevron that says the row goes somewhere (page-map.php only — the
 * Level 2 sheet's rows are buttons that swap the sheet's contents, so they
 * don't get one). Grey rather than the title's black: it's a hint about the
 * row, not part of what the row says. align-self like the congestion badge
 * above, for the same reason — a row is as tall as its stack of category
 * lines, and flex-start would strand the arrow up by the title.
 */
.map-sheet__list-arrow {
	flex: 0 0 auto;
	align-self: center;
	font-size: 16px;
	width: 16px;
	height: 16px;
	color: #767676;
}

body.map-sheet-open {
	overflow: hidden;
}

/* -----------------------------------------------------------------------
   Venue Info page (page-info.php) — static Caution notice + Programme
   PDF link. No yellow band treatment like .news-archive/.news-preview:
   this is plain body content, closer to .donate-page.
----------------------------------------------------------------------- */
.info-page {
	padding: 24px 16px 40px;
}

.info-page__heading {
	font-size: 20px;
	text-align: center;
	margin-bottom: 16px;
}

.info-page__section {
	margin-bottom: 32px;
}

.info-page__section:last-child {
	margin-bottom: 0;
}

.info-page__section-heading {
	font-size: 16px;
	letter-spacing: 0.08em;
	margin-bottom: 12px;
}

.info-page__content {
	font-size: 15px;
	line-height: 1.6;
}

.info-page__content p {
	margin-bottom: 1em;
}

.info-page__content p:last-child {
	margin-bottom: 0;
}

.info-page__list {
	margin: 0 0 1em 1.2em;
	list-style: disc;
}

.info-page__list li {
	margin-bottom: 0.4em;
}

.info-page__conditions-link {
	color: var(--color-red);
	text-decoration: underline;
}

/* Centers the pill button — .info-page__programme-cta itself is the
   shared pill defined with .donate-page__cta above (style.css). */
.info-page__section--programme {
	text-align: center;
}

/* Photo credit at the foot of the page. A note rather than content, so the
   13px #767676 that .map-sheet__category and .map-sheet__list-count use for
   secondary lines, centred like the page heading and the Programme button.
   No margin of its own: its arrival means the last section — Programme, or
   Caution while NAV_PROGRAMME_PDF_URL is empty — is no longer :last-child,
   so it takes back the same 32px step every other section here has, and
   .info-page's 40px bottom padding sits below it. */
.info-page__credit {
	font-size: 13px;
	line-height: 1.6;
	color: #767676;
	text-align: center;
}