/* ============================================================
   Dot Experience — Base (applied design-system rules)
   ============================================================
   Ported from the visual-design brief extracted from aph-v27-next
   (the Next.js/Tailwind reference app this theme's visual identity
   is supposed to match). tokens.css defines the raw custom
   properties (color/radius/type-scale/motion); this file is where
   those tokens actually get applied as real, semantic CSS — the
   step this theme was missing before this pass (colors/tokens
   existed, but headings/buttons/inputs/borders/spacing still
   rendered as stock Kiosko).

   This is plain CSS, not a Tailwind port: the brief's utility-class
   vocabulary (`.display-1`, `.eyebrow`, `.shell`, pill buttons, …)
   is translated into real class names usable directly in block
   markup (templates/*.html, parts/*.html, ACF block render.php),
   not reproduced as literal Tailwind syntax.

   Load order: tokens.css → base.css → style.css (see functions.php).
   Keep tokens.css limited to custom-property definitions; anything
   that consumes a token as an actual applied rule belongs here (or,
   for existing WooCommerce-block-specific restyles, in the
   per-surface files that already exist — woocommerce-mini-cart.css,
   woocommerce-myaccount.css, and the search block rules already in
   style.css).

   Breakpoint note: this design language uses exactly 3 responsive
   tiers — sm (640px), md (768px), lg (1024px). xl/2xl are never
   used upstream and should not be introduced by later waves (block
   styling, header/footer, homepage composition). Only `md` (768px)
   is needed for the rules below; sm/lg remain available for later,
   more granular layout work.
   ============================================================ */

/* ============================================================
   1. Typography
   ============================================================
   Heading tags carry weight/line-height/tracking only — NEVER
   font-size. Size is always applied per-instance via one of the
   utility classes below (or, on WooCommerce/core blocks, via the
   block's own fontSize attribute). A heading with no size class
   renders at the browser's default size for its level, at weight
   800 — that's an accepted intermediate state until the block/
   content-styling wave assigns explicit size classes, not a bug.
   ============================================================ */

body {
	/* Belt-and-suspenders alongside theme.json's typography.fontFamily
	   (also set to the Inter preset) — see tokens.css's precedent for
	   this pattern with --font-scale/--letter-spacing-body. */
	font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

h1,
h2,
h3,
h4,
h5,
h6 {
	font-weight: 800;
	line-height: 1.04;
	letter-spacing: var(--letter-spacing-display);
}

/* Global text-wrap: prevent widowed/orphaned words on their own line.
   `balance` on headings evens out line lengths across the whole block —
   correct for short heading text, and the same convention already used
   deliberately in hero-brand/hero-ticket-a's own local heading rules.
   `pretty` on paragraphs targets widow prevention without `balance`'s
   multi-line rebalancing, which browsers cap and isn't intended for
   longer body copy. Both are progressive enhancement — unsupported
   browsers just ignore the property, no fallback needed. This is a
   global floor only; any more specific existing rule (e.g. the hero
   blocks above) still wins per normal cascade. */
h1,
h2,
h3,
h4,
h5,
h6 {
	text-wrap: balance;
}

p {
	text-wrap: pretty;
}

/* Global keyboard-focus affordance (brief globals.css:130-134) — a
   single rule so individual components don't each need their own
   focus treatment. Component-specific focus rules elsewhere in this
   theme (mini-cart, my-account, search) are more specific and simply
   layer on top where they already exist. */
:focus-visible {
	outline: 3px solid var(--focus);
	outline-offset: 2px;
	border-radius: 4px;
}

/* Skip-to-main-content link (parts/header.html, first child) — hidden off
   the top of the viewport until keyboard-focused, then pinned to the
   top-left corner. Uses this theme's own ink/bg tokens rather than core's
   default grey box so it reads as part of the theme, not a browser default. */
.dot-skip-link {
	position: absolute;
	top: -100%;
	left: 0;
	z-index: 100;
	padding: 0.75rem 1.25rem;
	background: var(--ink);
	color: var(--bg);
	font-weight: 700;
	border-radius: 0 0 var(--r-md) 0;
	text-decoration: none;
}

.dot-skip-link:focus-visible {
	top: 0;
	outline: 3px solid var(--focus);
	outline-offset: -3px;
}

.eyebrow {
	display: inline-block;
	font-size: var(--step--1);
	font-weight: 700;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--accent);
}

.lede {
	font-size: var(--step-1);
	font-weight: 400;
	line-height: 1.45;
	color: var(--muted);
}

/* Workhorse section heading — used far more than display-1/2. */
.display-3 {
	font-size: var(--step-3);
}

/* Secondary hero / authored-page hero H1. */
.display-2 {
	font-size: var(--step-4);
}

/* Primary homepage H1 only. */
.display-1 {
	font-size: var(--step-5);
}

/* Smaller section heading, between .display-3 and body-scale headings. */
.h-section {
	font-size: var(--step-2);
}

/* ============================================================
   2. Shell container
   ============================================================ */
.shell {
	max-width: 1240px;
	margin-inline: auto;
	padding-inline: clamp(1.25rem, 4vw, 3rem);
}

/* ============================================================
   3. Buttons — fully pill-shaped
   ============================================================
   Semantic classes for anything authored directly in block markup
   going forward (ACF blocks, patterns). WooCommerce's own button
   markup (add-to-cart, checkout, mini-cart) is restyled separately
   below via class-name targeting, per this theme's three-tier WC
   integration convention — its classes are not renamed to .btn-*.
   ============================================================ */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.5rem;
	font-weight: 500;
	border-radius: 9999px;
	white-space: nowrap;
	user-select: none;
	cursor: pointer;
	text-decoration: none;
	transition:
		transform var(--motion-med) var(--ease),
		background-color var(--motion-med) var(--ease),
		color var(--motion-med) var(--ease),
		border-color var(--motion-med) var(--ease);
}

/* Hover — a subtle 2px lift shared by every .btn variant (primary,
   outline, ghost) so the interaction reads identically sitewide. The
   lift is the only *motion* here, so it's gated behind
   prefers-reduced-motion (OS) + the data-motion="reduce" override at the
   end of this section (accessibility-preferences plugin). The
   outline/ghost fill below is a colour change, not motion, so it stays
   ungated. Declared before :active so the press (scale) still wins on
   mousedown. */
@media (prefers-reduced-motion: no-preference) {
	.btn:hover {
		transform: translateY(-2px);
	}
}

.btn:active {
	transform: scale(0.98);
}

.btn-primary {
	background: var(--ink);
	color: var(--bg);
	border: 1.5px solid var(--ink);
}

.btn-outline {
	background: var(--surface);
	color: var(--ink);
	border: 1.5px solid var(--ink);
}

.btn-ghost {
	background: var(--surface);
	color: var(--ink);
	border: 1px solid var(--line);
}

.btn-sm {
	padding: 0.5rem 1rem;
	font-size: 0.9rem;
}

.btn-md {
	padding: 0.625rem 1.25rem;
	font-size: 1rem;
}

.btn-lg {
	padding: 0.875rem 1.75rem;
	font-size: 1.05rem;
}

/* Secondary/outline (and ghost) fill solid on hover/focus — the same
   solid end-state as a primary button (standard outline-button
   convention). Uses --ink/--bg, which resolve identically to
   --accent/--accent-ink in all three theme variants, so the fill tracks
   the accessibility-preferences plugin's [data-theme]. Blocks that
   invert these buttons onto a dark (--ink) band — cta-band,
   hero-marquee — override this with a bg-coloured fill of their own so
   the pill stays visible against the dark background. */
.btn-outline:hover,
.btn-ghost:hover {
	background: var(--ink);
	color: var(--bg);
	border-color: var(--ink);
}

/* ---- WooCommerce's actual button markup (class-name targeting) ----
   Covers: core Buttons block (.wp-block-button__link, also used by
   the "elements.button" global in theme.json), classic single/loop
   add-to-cart (.single_add_to_cart_button / .add_to_cart_button /
   the generic .button class those carry), and WooCommerce Blocks'
   own Button component (.wc-block-components-button, used by the
   Cart "Proceed to Checkout" button, Checkout's "Place order", and
   the product-loop AddToCartButton). theme.json's border-radius for
   "elements.button" is also set to 9999px so native <button>/input
   submit elements match without needing this rule at all. */
.button,
.wp-element-button,
.wc-block-components-button,
.wc-block-components-product-button__button,
.add_to_cart_button,
.single_add_to_cart_button {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.5rem;
	font-weight: 500;
	border-radius: 9999px;
	white-space: nowrap;
	transition:
		transform var(--motion-med) var(--ease),
		background-color var(--motion-med) var(--ease),
		color var(--motion-med) var(--ease),
		border-color var(--motion-med) var(--ease);
	background: var(--accent);
	color: var(--accent-ink);
	border: 1.5px solid var(--accent);
}

/* Same shared lift as .btn:hover, so WooCommerce/core buttons (add-to-
   cart, checkout, place-order, the header "Buy Tickets"/"Gift Shop" core
   buttons) hover identically to the semantic .btn CTAs. Solid buttons
   keep their fill (theme.json's elements.button:hover already resolves to
   the same --ink/--accent); the outline variant's fill is asserted
   below. Gated for reduced motion; declared before :active so the press
   wins. */
@media (prefers-reduced-motion: no-preference) {
	.button:hover,
	.wp-element-button:hover,
	.wc-block-components-button:hover,
	.wc-block-components-product-button__button:hover,
	.add_to_cart_button:hover,
	.single_add_to_cart_button:hover {
		transform: translateY(-2px);
	}
}

.button:active,
.wp-element-button:active,
.wc-block-components-button:active,
.add_to_cart_button:active,
.single_add_to_cart_button:active {
	transform: scale(0.98);
}

/* Disabled state (color-contrast fix) — this theme declares block-theme
   support, so WooCommerce serves woocommerce-blocktheme.css (not the
   classic woocommerce.css). Its rule:

     .woocommerce button.button.disabled,
     .woocommerce button.button:disabled,
     ... { opacity: .5 }

   is specificity (0,3,1) — one class from its `.woocommerce` ancestor
   context, one type selector (`button`), two more classes (`.button`,
   `.disabled`) — higher than this file's own `.single_add_to_cart_button
   .disabled` etc. rules above at (0,2,0), so it always wins regardless of
   enqueue/DOM order (confirmed live: without `!important` below, the
   background/text-color overrides took, but `opacity: .5` kept winning).
   Same fight, same fix already used elsewhere in this theme for the same
   reason — see woocommerce-shop.css's product-collection grid rules and
   their comment ("!important to reliably win over Woo's own
   same-specificity class selector regardless of DOM/enqueue order").

   Diluting the *whole* pill toward the page background via opacity hits
   its near-black `--accent` fill hard while barely moving its near-white
   `--accent-ink` text (which already sits close to the page background
   it's blending toward) — collapsing contrast to ~3.61:1 on a variable
   product's "Add to cart" button before a variation is picked (e.g.
   /product/general-admission/, class list `... button alt
   wp-element-button disabled wc-variation-selection-needed`), short of
   WCAG AA's 4.5:1.

   Fix: cancel the opacity dilution and use solid, deliberately "muted"
   token colors instead — var(--muted) fill + var(--accent-ink) text —
   verified at 5.3:1 (default), 17.4:1 (high-contrast), 7.4:1 (dark), all
   comfortably above 4.5:1. This still reads as visually disabled (a
   distinctly duller fill than the enabled --accent black, plus
   cursor: not-allowed) without relying on a color-only signal that also
   happens to fail contrast. Scoped to the classic add-to-cart buttons
   only (same markup/CSS mechanism as the reported bug); WooCommerce
   Blocks' own `.wc-block-components-button:disabled` uses a different,
   unaffected mechanism (opacity on the inner text node against an
   unchanged solid background) and isn't part of this fix. */
.button.disabled,
.button:disabled,
.wp-element-button.disabled,
.wp-element-button:disabled,
.add_to_cart_button.disabled,
.add_to_cart_button:disabled,
.single_add_to_cart_button.disabled,
.single_add_to_cart_button:disabled {
	opacity: 1 !important;
	background: var(--muted) !important;
	border-color: var(--muted) !important;
	color: var(--accent-ink) !important;
}

/* Outline/secondary variant, where WooCommerce or a block author has
   marked the button as such. */
.wp-block-button.is-style-outline > .wp-block-button__link,
.wc-block-components-button.outlined,
.wc-block-components-button.is-style-outline {
	background: var(--surface);
	color: var(--ink);
	border: 1.5px solid var(--ink);
}

/* ...and the matching fill on hover, so WooCommerce/core outline buttons
   (the header "Gift Shop" pill, block outline buttons) fill solid like
   .btn-outline does. Higher specificity than the resting rule above so
   the fill wins; the lift comes from the family :hover lift declared
   earlier. */
.wp-block-button.is-style-outline > .wp-block-button__link:hover,
.wc-block-components-button.outlined:hover,
.wc-block-components-button.is-style-outline:hover {
	background: var(--ink);
	color: var(--bg);
	border-color: var(--ink);
}

/* Reduced motion — the plugin's explicit choice cancels the hover lift on
   every button variant, both systems, even when the OS allows motion.
   (The OS-level fallback is the prefers-reduced-motion: no-preference
   wrappers on the lifts above, plus tokens.css's global duration
   zeroing.) The fill/colour change is deliberately preserved — it isn't
   motion. */
html[data-motion="reduce"] .btn:hover,
html[data-motion="reduce"] .button:hover,
html[data-motion="reduce"] .wp-element-button:hover,
html[data-motion="reduce"] .wc-block-components-button:hover,
html[data-motion="reduce"] .wc-block-components-product-button__button:hover,
html[data-motion="reduce"] .add_to_cart_button:hover,
html[data-motion="reduce"] .single_add_to_cart_button:hover {
	transform: none !important;
}

/* ============================================================
   4. Form inputs — semantic pill-shaped input class
   ============================================================
   Not applied as a blanket `input[type=text]` rule: several
   WooCommerce composite controls (the mini-cart/cart quantity
   stepper, checkout field wrappers) already have their own compact,
   purpose-built layouts, and forcing every input into a full pill
   would collide with them. Use `.form-input` on anything newly
   authored (ACF block forms, etc.); the one existing real input
   surface this applies to — the site search field — is restyled in
   place in style.css's "Site search" section instead, since it
   already has its own dedicated ruleset there.
   ============================================================ */
.form-input {
	display: block;
	width: 100%;
	border-radius: 9999px;
	padding: 0.75rem 1rem;
	font-size: 0.95rem;
	border: 1px solid var(--line);
	background: var(--bg);
	color: var(--ink);
	outline: none;
}

/* ============================================================
   5. Borders not shadows
   ============================================================
   Default convention for card/surface elements going forward:
   1px solid var(--line) (already an 8%-opacity hairline — never
   harden it to a solid gray). Reserve box-shadow strictly for
   floating/overlay UI (modals, dropdowns, drawers, popovers,
   floating chat launchers) — never on resting cards, sections, or
   grid tiles. The four-tier shadow scale is defined as custom
   properties in tokens.css (--shadow-dropdown/-popover/-drawer/
   -modal); consuming rules (mini-cart drawer, search suggestions
   dropdown) reference those tokens directly. Reusable classes here
   for anything authored that needs one of these tiers directly.
   ============================================================ */
.shadow-dropdown {
	box-shadow: var(--shadow-dropdown);
}

.shadow-popover {
	box-shadow: var(--shadow-popover);
}

.shadow-drawer {
	box-shadow: var(--shadow-drawer);
}

.shadow-modal {
	box-shadow: var(--shadow-modal);
}

/* Plain-line divider, no radius concept applies — replacement for a
   bare <hr>. */
.hairline {
	border: none;
	border-top: 1px solid var(--line);
}

/* Top+bottom hairline pair — for banded sections (e.g. the masthead
   pattern) that need a frame without reaching for a heavy solid
   border. Replaces ad-hoc 4px solid borders with the same 8%-opacity
   convention as .hairline. */
.hairline-y {
	border: none;
	border-top: 1px solid var(--line);
	border-bottom: 1px solid var(--line);
}

/* ============================================================
   6. Radius scale — role, not decoration
   ============================================================
   --r-sm/md/lg/xl (12/20/28/40px, defined in tokens.css) each have a
   distinct job. Utility classes below document + enforce that role
   for later block-styling work rather than leaving radius choices
   arbitrary per component:
     .radius-sm  (12px) — inline thumbnails only (cart/order line-item
                  photos, small icon tiles). Never a full card or button.
     .radius-md  (20px) — mid-size elements: product/category thumbnails
                  inside cards, gallery thumbnail buttons, popovers.
     .radius-lg  (28px) — the standard card/panel radius: product cards,
                  fieldsets, modals, tab panels, gallery main image.
     .radius-xl  (40px) — hero-band / full-bleed feature panel radius:
                  inverted hero panels, oversized featured-product tiles.
   Buttons, chips, badges, and pills are always fully round (9999px)
   regardless of size — that's a separate, unconditional rule (see
   §3), not part of this role-based scale.
   ============================================================ */
.radius-sm {
	border-radius: var(--r-sm);
}

.radius-md {
	border-radius: var(--r-md);
}

.radius-lg {
	border-radius: var(--r-lg);
}

.radius-xl {
	border-radius: var(--r-xl);
}

/* ============================================================
   7. Section vertical rhythm
   ============================================================
   Three tiers, mobile-first, one step at the md (768px) breakpoint:
     .section-py       content section   — 3rem  → 4rem
     .section-py-lg     standard section  — 4rem  → 5rem
     .section-py-hero    hero section      — 4rem  → 6rem
   (The biggest poster-style heroes in the source go as high as
   7rem on desktop — if a later wave needs that, add a distinct
   modifier then rather than widening this tier's default.)
   ============================================================ */
.section-py {
	padding-block: 3rem;
}

.section-py-lg {
	padding-block: 4rem;
}

.section-py-hero {
	padding-block: 4rem;
}

@media (min-width: 768px) {
	.section-py {
		padding-block: 4rem;
	}

	.section-py-lg {
		padding-block: 5rem;
	}

	.section-py-hero {
		padding-block: 6rem;
	}
}

/* ============================================================
   8. Header & footer chrome
   ============================================================
   Two-tier header (utility strip + sticky main nav, brief §5
   "Header/nav") and the full-bleed dark footer band (brief §5
   "Footer"). Both live in parts/header.html and patterns/footer.php
   as ordinary WordPress block markup with custom classNames added
   for targeting — nothing here touches WooCommerce Blocks' own
   mini-cart/customer-account markup, only repositions/recolors it
   by class name, the same convention woocommerce-mini-cart.css
   already uses.
   ============================================================ */

/* ---- Utility strip: full-bleed jet bar, icon cluster only ---- */
.dot-utility-bar {
	background: var(--ink);
	color: var(--bg);
}

/* Cancel `.shell`'s own padding-inline (clamp 1.25rem→3rem) specifically
   in the header. `.dot-utility-bar`/`.dot-main-nav` are already
   `alignfull` groups carrying core's `has-global-padding` (the same
   root spacing|50 / 28px gutter the footer's `.dot-footer` and every
   other full-bleed section on this theme relies on — confirmed live via
   rendered markup, both carry `has-global-padding` already). `.shell`
   stacks its own separate padding-inline on top of that, inside the
   same 1240px cap, roughly doubling the edge inset versus the footer
   and CTA band (up to ~76px vs. their ~28px) — this is what read as
   "narrower/indented" against the rest of the page (Chris's screenshot
   feedback, 2026-07-12). Zeroing it here lets max-width:1240px +
   margin-inline:auto still center the row, with the already-present
   root padding supplying the only edge gutter, matching the footer's
   convention exactly at every breakpoint (mobile included, since root
   padding is a fixed value, not viewport-dependent). Scoped to these two
   header selectors only — `.shell` itself, and its use on
   archive-product.html, are untouched. */
.dot-utility-bar .shell {
	min-height: 44px;
	padding-inline: 0;
}

/* Search / account / cart icon buttons share one treatment: circular
   hit area, transparent at rest, opacity-fade only on hover/focus —
   no background swap (brief: UtilityBar icon buttons). */
.dot-utility-bar .wp-block-search__button,
.dot-utility-bar .wc-block-customer-account__link,
.dot-utility-bar .wc-block-mini-cart__button {
	background: transparent !important;
	border: none !important;
	color: var(--bg) !important;
	border-radius: 9999px;
	padding: 0.5rem;
	opacity: 1;
	transition: opacity var(--motion-med) var(--ease);
}

/* WooCommerce only sizes the account icon (height/width: 1.5em) when
   the block is auto-inserted via its Block Hooks path, which stamps
   an `iconClass="wc-block-customer-account__account-icon"` attribute
   onto the SVG. Placed manually (as it is here, in the utility bar),
   the icon carries the block's plain default `iconClass="icon"`
   instead, which WooCommerce's own stylesheet never sizes — the SVG
   renders at 0×0 without this rule. Pre-existing WooCommerce block
   behavior, not something introduced by this restyle. */
.wc-block-customer-account__link svg {
	width: 1.25rem;
	height: 1.25rem;
}

.dot-utility-bar .wp-block-search__button:hover,
.dot-utility-bar .wc-block-customer-account__link:hover,
.dot-utility-bar .wc-block-mini-cart__button:hover,
.dot-utility-bar .wp-block-search__button:focus-visible,
.dot-utility-bar .wc-block-customer-account__link:focus-visible,
.dot-utility-bar .wc-block-mini-cart__button:focus-visible {
	opacity: 0.7;
	background: transparent !important;
}

.dot-utility-bar .wc-block-mini-cart__badge {
	/* Overrides the Interactivity API's inline-styled badge colors
	   (see woocommerce-mini-cart.css header comment) for this bar's
	   dark background specifically. */
	background-color: var(--bg) !important;
	color: var(--ink) !important;
}

/* Expandable search field (buttonPosition: "button-only") — the
   input is hidden until core's own view.js toggles it open; when
   visible it still needs to read against the jet bar. */
.dot-utility-bar .wp-block-search__inside-wrapper {
	background: transparent;
	overflow: visible;
}

.dot-utility-bar .wp-block-search__input {
	color: var(--bg);
	background: transparent;
}

.dot-utility-bar .wp-block-search__input::placeholder {
	color: color-mix(in srgb, var(--bg) 65%, transparent);
}

/* ---- Main nav: sticky, frosted, hairline bottom border ----
   The sticky offset lives on the template part's own <header> tag
   (WP always wraps parts/header.html in <header class="wp-block-
   template-part">), not on .dot-main-nav itself. A position:sticky
   element is bounded by its immediate parent's box — and that parent
   here is only as tall as the utility bar + nav combined, with no
   slack below, so a plain `position:sticky; top:0` on .dot-main-nav
   would "run out of room" almost immediately and stop tracking on
   scroll (confirmed live: the nav scrolled away with the rest of the
   page instead of pinning). Making <header> itself the sticky
   element fixes this, since its parent (.wp-site-blocks) spans the
   full page height; `top: -44px` (the utility bar's own height)
   means the header can scroll up exactly far enough to tuck the
   utility bar out of view before it catches, so only the nav tier
   ends up pinned — matching the brief's "utility bar scrolls away,
   nav stays sticky" behaviour. */
header.wp-block-template-part {
	position: sticky;
	top: -44px;
	z-index: 40;
}

/* ---- Offset for the WP admin bar (logged-in viewport only) ----
   Confirmed against WP core, not guessed: wp-includes/admin-bar.php's
   wp_enqueue_admin_bar_bump_styles() fixes #wpadminbar to the viewport
   top and bumps <html> down with `margin-top: 32px` (46px at core's own
   `@media screen and (max-width: 782px)` breakpoint — see also the
   `--wp-admin--admin-bar--height` custom property switch at that same
   breakpoint in wp-includes/css/admin-bar.css). That bump only affects
   normal document flow; it does nothing for an element that is already
   *stuck* via position:sticky, so once scrolled, the toolbar (z-index
   99999) sat on top of/clipped this header instead of sitting above it.
   `body.admin-bar` is the class core's body_class() adds whenever
   is_admin_bar_showing() is true (wp-includes/post-template.php) — i.e.
   logged-in with the toolbar enabled, on the front end. Shift the same
   -44px utility-bar tuck-away by the toolbar's own height so the header
   lands just below the toolbar when stuck, instead of behind it. */
body.admin-bar header.wp-block-template-part {
	top: -12px; /* -44px utility-bar tuck-away + 32px admin bar height */
}

@media screen and (max-width: 782px) {
	body.admin-bar header.wp-block-template-part {
		top: 2px; /* -44px utility-bar tuck-away + 46px admin bar height */
	}
}

.dot-main-nav {
	background: color-mix(in srgb, var(--bg) 92%, transparent);
	backdrop-filter: saturate(140%) blur(10px);
	-webkit-backdrop-filter: saturate(140%) blur(10px);
	border-bottom: 1px solid var(--line);
}

.dot-main-nav__inner {
	min-height: 72px;
	padding-inline: 0; /* see .dot-utility-bar .shell comment above */
}

/* ---- Brand logo (home link) ----
   Replaces the plain-text Site Title in both the main nav and the footer
   (see patterns/site-logo.php). The inline SVG's paths use
   fill:currentColor, so the mark inherits the `color` set on .dot-logo
   per context below. Those colors resolve to the base/contrast design
   tokens, so the mark reflows automatically under the
   accessibility-preferences plugin's default / high-contrast / dark
   [data-theme] variants — never a hardcoded white, which would vanish on
   the light surfaces those variants produce (the footer band itself
   inverts to a light panel in dark mode). Heights are rem-based so the
   mark scales with the plugin's --font-scale profiles alongside the
   surrounding type. */
.dot-logo {
	display: inline-flex;
	align-items: center;
	line-height: 0;
	text-decoration: none;
}

.dot-logo__mark {
	display: block;
	width: auto;
	max-width: 100%;
}

/* Main nav: track the body ink (contrast) — dark on the light frosted
   nav in default/high-contrast, off-white on the jet nav in dark mode. */
.dot-main-nav .dot-logo {
	color: var(--wp--preset--color--contrast);
}

.dot-main-nav .dot-logo__mark {
	height: 2.5rem;
}

@media (min-width: 768px) {
	.dot-main-nav .dot-logo__mark {
		height: 2.75rem;
	}
}

/* Equal-width CTA pair: "Browse Shop" and "Buy Tickets" differ in label
   length, which rendered as visibly mismatched pill widths side by side
   (Chris's screenshot feedback, 2026-07-12). min-width matches "Buy
   Tickets"'s own natural rendered width (measured live at default
   font-scale via getBoundingClientRect: ~133px) plus a little slack;
   the shared .button/.wp-element-button rule already centers label
   text via justify-content:center, so the shorter "Browse Shop" label
   just centers within the wider pill rather than the two pills
   differing in size. rem-based so both scale together under the
   accessibility-preferences plugin's --font-scale profiles.

   Scoped to sm (640px) and up only — below that, the compact mobile
   treatment right below this rule takes over (see that comment for why:
   136px pills don't fit two-up next to the wordmark at phone widths). */
@media (min-width: 640px) {
	.dot-nav-cta .wp-block-button__link {
		min-width: 8.5rem;
	}
}

/* ---- Mobile (<640px): compact CTA pair, single row ----
   At phone widths the desktop-sized pills (136px min-width each, ~14px
   gap) don't fit next to the wordmark without wrapping — confirmed live
   at 400px width: cluster had ~226px available, the pair needed ~286px,
   so "Browse Shop"/"Buy Tickets" stacked into two full-width-looking
   rows instead of sitting side by side (Chris's screenshot feedback,
   2026-07-13). Below sm (640px, the narrowest tier this theme's
   breakpoint scheme reserves for exactly this kind of granular layout
   fix — see this file's header comment):
     - min-width is dropped entirely. The two labels are the same
       character count ("Browse Shop"/"Buy Tickets", 11 each) and render
       within ~5px of each other at this tighter padding, so they already
       read as a matched pair without forcing equal width — no need to
       spend extra pixels on it at the one tier where pixels are scarce.
     - padding and font-size are pulled in a notch from the fluid
       theme.json button defaults (which are already near their fluid
       *minimum* at these widths, so this is a deliberate tier below
       that floor, not fighting the fluid scale).
     - explicit flex-wrap: nowrap overrides core's own generic
       `.is-layout-flex { flex-wrap: wrap }` (same specificity, later in
       the cascade — confirmed via computed styles) so the pair can
       never fall back to stacking once it fits.
   Verified live down to 360px width (smallest common phone) with ~10px
   of clear slack next to the wordmark; still reads as two distinct,
   comfortably tappable pills, not a cramped afterthought. Height stays
   at/above the ~36px this padding produces — above WCAG 2.2's 24px
   (2.5.8) minimum target size. */
@media (max-width: 639px) {
	.dot-nav-cta {
		flex-wrap: nowrap;
		gap: 0.4rem;
	}

	.dot-nav-cta .wp-block-button__link {
		min-width: 0;
		padding: 0.5rem 0.65rem;
		font-size: 0.75rem;
	}
}

/* ---- Classic Menu block (`acf/classic-menu`) ----
   Replaces the Navigation block entirely (see docs/DECISION-LOG.md,
   "dot-experience theme structure") — main nav and the two footer
   columns now render via wp_nav_menu() (blocks/classic-menu/render.php),
   so this owns markup that's plain <nav>/<ul>/<li>/<a>, not the
   Navigation block's wp-block-navigation-* classes. Base reset + the
   primary (header) horizontal layout live here; the two footer columns'
   vertical layout is set in the Footer section below, next to the rest
   of the footer's own styling. */
.dot-classic-menu__list {
	display: flex;
	align-items: center;
	list-style: none;
	margin: 0;
	padding: 0;
}

.dot-classic-menu__list li {
	margin: 0;
}

.dot-classic-menu__list a {
	display: inline-block;
	color: inherit;
	text-decoration: none;
}

/* wp_nav_menu() sets aria-current="page" on the current item's <a>
   automatically (core's Walker_Nav_Menu, since WP 5.5) — no custom
   walker needed to keep this working. */
.dot-classic-menu__list a[aria-current] {
	text-decoration: underline;
	text-underline-offset: 0.18em;
}

.dot-classic-menu--primary .dot-classic-menu__list {
	flex-direction: row;
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: var(--wp--preset--spacing--50);
}

.dot-classic-menu--primary .dot-classic-menu__list a {
	font-size: 1rem;
	font-weight: 500;
	/* Was an instant color swap with no transition — out of step with
	   every other hover surface on the site once those were standardized
	   to a smooth 300ms (see .btn/.button, footer links, utility-bar
	   icons above). text-decoration's line (none→underline) still can't
	   animate — not an animatable CSS property — but the color change
	   now eases in step with the rest. */
	transition: color var(--motion-med) var(--ease);
}

.dot-classic-menu--primary .dot-classic-menu__list a:hover,
.dot-classic-menu--primary .dot-classic-menu__list a:focus-visible {
	color: var(--wp--preset--color--primary);
	text-decoration: underline;
}

/* ---- Mobile nav toggle: accessible hamburger + dropdown panel ----
   core/navigation's built-in mobile overlay goes away along with the
   block itself, so this is a small hand-built replacement: a disclosure
   button (see assets/js/nav-toggle.js for the interaction contract —
   Escape-to-close with focus return, outside-click/focus-out close,
   deliberately no focus trap since this is a disclosure, not a modal)
   that shows/hides a dropdown panel anchored under the toggle. Only
   `.dot-classic-menu--toggleable` instances (the primary header nav)
   get this; the footer columns render as plain always-visible lists. */
.dot-classic-menu--toggleable {
	position: relative;
}

.dot-classic-menu--toggleable .dot-nav-toggle {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 44px;
	height: 44px;
	padding: 0;
	background: transparent;
	border: none;
	border-radius: 9999px;
	cursor: pointer;
	color: inherit;
}

.dot-classic-menu--toggleable .dot-nav-toggle__box {
	display: flex;
	flex-direction: column;
	gap: 5px;
	width: 20px;
}

.dot-classic-menu--toggleable .dot-nav-toggle__bar {
	display: block;
	height: 2px;
	width: 100%;
	background: currentColor;
	border-radius: 1px;
	transition: transform var(--motion-fast) var(--ease), opacity var(--motion-fast) var(--ease);
}

/* Morph the three bars into an X while open — purely visual, driven by
   the button's own aria-expanded state so it can never drift out of
   sync with what's announced to assistive tech. */
.dot-classic-menu--toggleable .dot-nav-toggle[aria-expanded="true"] .dot-nav-toggle__bar:nth-child(1) {
	transform: translateY(7px) rotate(45deg);
}

.dot-classic-menu--toggleable .dot-nav-toggle[aria-expanded="true"] .dot-nav-toggle__bar:nth-child(2) {
	opacity: 0;
}

.dot-classic-menu--toggleable .dot-nav-toggle[aria-expanded="true"] .dot-nav-toggle__bar:nth-child(3) {
	transform: translateY(-7px) rotate(-45deg);
}

/* Below md (768px): the panel is a dropdown card, hidden until
   nav-toggle.js adds .is-open, and the list stacks vertically. */
.dot-classic-menu--toggleable .dot-classic-menu__panel {
	display: none;
	position: absolute;
	top: calc(100% + 0.75rem);
	right: 0;
	min-width: 220px;
	background: var(--bg);
	border: 1px solid var(--line);
	border-radius: var(--r-sm);
	box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
	padding: var(--wp--preset--spacing--40);
	z-index: 50;
}

.dot-classic-menu--toggleable .dot-classic-menu__panel.is-open {
	display: block;
}

.dot-classic-menu--toggleable .dot-classic-menu__list {
	flex-direction: column;
	align-items: flex-start;
	gap: var(--wp--preset--spacing--30);
}

/* At/above md: always-visible inline row, toggle hidden entirely (not
   just visually — display:none removes it from the accessibility tree,
   so its aria-expanded state is moot once it's not reachable). */
@media (min-width: 768px) {
	.dot-classic-menu--toggleable .dot-nav-toggle {
		display: none;
	}

	.dot-classic-menu--toggleable .dot-classic-menu__panel {
		display: block;
		position: static;
		background: transparent;
		border: none;
		border-radius: 0;
		box-shadow: none;
		padding: 0;
	}

	.dot-classic-menu--toggleable .dot-classic-menu__list {
		flex-direction: row;
		/* Restore the horizontal gap the `--primary` rule above already
		   declares (var:preset|spacing|50, 1.75rem) — but capped one
		   step lower, at var:preset|spacing|40 (0.875rem/14px), not the
		   full 50. Same specificity, later in the cascade, this
		   selector's own gap:30 (the mobile dropdown's tight vertical
		   stacking value) was silently winning at all breakpoints —
		   including desktop, where 8px between full-word links read as
		   "too close together" (Chris's screenshot feedback,
		   2026-07-12). Verified against real breakpoints before picking
		   the value: spacing|50 (28px) wraps "Group Request" onto its
		   own second line as low as 1024px wide (nav+wordmark+CTA no
		   longer fit one row); spacing|40 (14px) is the largest scale
		   step that still holds one row down to 1024px. (Tablet widths
		   at 900px and below already wrap to two rows even at today's
		   8px gap — a pre-existing header-layout ceiling unrelated to
		   this fix, not something this change should try to paper over.)
		   Scoping the override to this media query keeps the mobile
		   stacked-list gap at 30 untouched. */
		gap: var(--wp--preset--spacing--40);
	}
}

/* ---- Footer: full-bleed jet band ---- */
.dot-footer {
	/* WordPress's own has-*-background-color / has-*-color utility
	   classes (from the block's backgroundColor/textColor attrs)
	   already carry !important, so matching it here is required to
	   win — same reasoning as the mini-cart drawer overrides above. */
	background: var(--ink) !important;
	color: var(--bg) !important;
}

.dot-footer a {
	opacity: 0.82;
	transition: opacity var(--motion-med) var(--ease);
}

.dot-footer a:hover,
.dot-footer a:focus-visible {
	opacity: 1;
	/* Neutralizes theme.json's global link :hover color (a dark gray
	   meant for light backgrounds) which would otherwise go
	   near-invisible against this band's jet background. */
	color: inherit !important;
}

/* Footer brand logo: track base (the footer band's own text color, forced
   in the .dot-footer rule above), so the mark is light on the jet band in
   default/high-contrast and jet on the inverted light band in dark mode.
   Opt out of the .dot-footer a { opacity: 0.82 } dimming above — the brand
   mark anchors the footer and should read at full strength, not as a
   utility link. Slightly larger than the header lockup: the footer band
   has the vertical room and the mark is the column's lead element. */
.dot-footer .dot-logo {
	color: var(--wp--preset--color--base);
	opacity: 1;
}

.dot-footer .dot-logo:hover,
.dot-footer .dot-logo:focus-visible {
	opacity: 1;
}

.dot-footer .dot-logo__mark {
	height: 3rem;
}

@media (min-width: 768px) {
	.dot-footer .dot-logo__mark {
		height: 3.5rem;
	}
}

.dot-footer .wp-block-heading {
	font-size: 0.82rem;
	font-weight: 700;
	text-transform: uppercase;
	/* Was a hardcoded 0.12em — noticeably looser than the rest of the
	   theme's heading tracking (Chris's screenshot feedback, 2026-07-12:
	   "Location"/"Shop"/"Info" needed to "come in"). Reuse the same
	   --letter-spacing-display token the sitewide h1-h6 rule and the
	   footer's own site-title use, so these three column headings track
	   identically to the "The Dot Experience Shop" heading beside them
	   instead of an arbitrary one-off value. */
	letter-spacing: var(--letter-spacing-display);
	opacity: 0.6;
}

/* Footer "Shop"/"Info" nav columns (`dot-classic-menu--footer-shop` /
   `--footer-info`) — a plain vertical list, no toggle. Link color/hover
   opacity already comes from the `.dot-footer a` rules above; this only
   sets the stacked layout the two columns need. */
.dot-classic-menu--footer-shop .dot-classic-menu__list,
.dot-classic-menu--footer-info .dot-classic-menu__list {
	flex-direction: column;
	align-items: flex-start;
	gap: var(--wp--preset--spacing--30);
}

.dot-footer-divider.wp-block-separator {
	border: none;
	border-top: 1px solid rgba(255, 255, 255, 0.15);
	opacity: 1;
}

.dot-footer-legal {
	opacity: 0.6;
}

/* Fixes axe-core "link-in-text-block" (computed ~1.38:1 link-vs-adjacent-
   text contrast) on the "Designed with WordPress" link. Root cause: this
   selector previously duplicated the sitewide `.dot-footer a { opacity:
   0.82 }` value, which — because it's the SAME element's own opacity,
   not a second nested box — doesn't compound with it, it just reasserts
   0.82. The real bug was that 0.82 stacking with this container's own
   0.6 (a genuinely separate box, so THAT one always compounds) made the
   link render dimmer than the plain fine-print text right next to it,
   i.e. a link that's harder to see than its surroundings.
   Setting this to 1 cancels the link's own extra dimming, leaving only
   the container's 0.6 ceiling (unavoidable — ancestor opacity always
   compounds regardless of a descendant's own value), which verifies at
   ~6.9:1 against the footer's --ink background, comfortably over 3:1.
   At that ceiling the link is now the exact same rendered color as its
   surrounding text (1:1 contrast between the two), so color alone no
   longer distinguishes it — hence the underline, a non-color signal per
   WCAG 1.4.1, added here rather than relied on via contrast tuning. */
.dot-footer-legal a {
	opacity: 1;
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

/* ============================================================
   9. Group & Special Ticket Requests — 2-column request layout
   ============================================================
   Scoped to .dot-request (used only on the Group & Special Ticket
   Requests page's content). Mobile-first single column; at the lg
   breakpoint (1024px — the same tier the hero editorial-split uses)
   it becomes a 2-column grid: supporting content on the left, the
   Gravity Forms form on the right. Content comes first in the DOM, so
   screen-reader/tab order matches the left-to-right visual scan order
   in both layouts (content stacks above the form on mobile).

   The Gravity Forms styling itself lives in assets/css/gravity-forms.css;
   this section only positions the form column and styles the left
   column's supporting content — it never restyles the form.

   Container/column display + spacing use a doubled class
   (.wp-block-group.dot-request*, 0-2-0) so they win over WordPress's
   own is-layout-flow / is-layout-flex block-support rules (0-1-0)
   regardless of source order, without !important; the margin reset
   neutralizes core's block-gap margins so our flex/grid `gap` is the
   single source of spacing.
   ============================================================ */
.wp-block-group.dot-request {
	display: grid;
	gap: var(--wp--preset--spacing--60);
	align-items: start;
}

@media (min-width: 1024px) {
	.wp-block-group.dot-request {
		grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
		gap: clamp(2.5rem, 5vw, 4.5rem);
	}
}

.wp-block-group.dot-request__aside,
.wp-block-group.dot-request__form {
	display: flex;
	flex-direction: column;
}

.wp-block-group.dot-request__aside {
	gap: var(--wp--preset--spacing--50);
}

.wp-block-group.dot-request__form {
	gap: var(--wp--preset--spacing--40);
}

/* Core's block-gap margins would double with our flex/grid gap. */
.wp-block-group.dot-request > *,
.wp-block-group.dot-request__aside > *,
.wp-block-group.dot-request__form > *,
.wp-block-group.dot-request__contact > * {
	margin-block: 0;
}

/* Request types — an editorial hairline index rather than a bullet list
   (still a real <ul>/<li>, so it stays a list for assistive tech). */
.dot-request__types {
	list-style: none;
	margin: 0;
	padding: 0;
}

.dot-request__types li {
	padding-block: 0.65rem;
	border-top: 1px solid var(--line);
}

.dot-request__types li:last-child {
	border-bottom: 1px solid var(--line);
}

/* Contact card — the theme's standard hairline-bordered surface
   (base.css §5, "borders not shadows"), grounding the request in the
   real visiting facts used sitewide (see patterns/footer.php). */
.wp-block-group.dot-request__contact {
	display: flex;
	flex-direction: column;
	gap: 0.35rem;
	padding: clamp(1.25rem, 3vw, 1.75rem);
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
}

/* ============================================================
   10. Authored Page Hero — About / Contact
   ============================================================
   Shared by templates/page-about.html and templates/page-contact.html
   (both were falling through to the generic page.html — plain
   wp:post-title, no hero — before this pass). Written directly as
   core-block markup in each template (no new ACF block registered),
   matching the same pattern front-page.html already uses for its own
   plain "Plan Your Visit" band. Visual cues are drawn from the
   established hero vocabulary (blocks/hero-overprint,
   blocks/hero-groups): the same absolutely-positioned outlined
   watermark word device, the same eyebrow/title/reveal rhythm — but
   deliberately quieter (smaller watermark, shorter min-height) since
   base.css's own `.display-2` is documented as the "secondary hero /
   authored-page hero" size, one step down from the homepage's
   `.display-1`. The H1 is always the real wp:post-title (the page's
   own title), and the eyebrow text on each page is copied verbatim
   from a heading that already exists in that page's own authored
   content — no new marketing copy invented, per the redesign brief.
   ============================================================ */
.dot-page-hero {
	position: relative;
	isolation: isolate;
	overflow: hidden;
}

.dot-page-hero__shell {
	position: relative;
	z-index: 1;
	padding-block-start: clamp(2rem, 6vh, 3rem);
	padding-block-end: clamp(1.5rem, 4vh, 2rem);
	min-block-size: 34svh;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	justify-content: center;
	gap: 1.1rem;
}

/* Core's "is-layout-flow" block-gap margin (this is a core/group block
   with `layout.type: "default"`) would otherwise add its own
   margin-block-start on top of the flex `gap` above, doubling the
   spacing between the eyebrow/title/rule — same conflict, same fix as
   `.wp-block-group.dot-request` in §9. */
.dot-page-hero__shell > * {
	margin-block: 0;
}

.dot-page-hero__title {
	margin: 0;
	max-width: 16ch;
	line-height: 1.02;
}

/* Short accent rule under the title — a quiet editorial kicker rather
   than the CTA row the homepage heroes end on (these two pages have no
   ticket/shop action to push). Reuses the sitewide `.hairline` border
   treatment; opacity forced to 1 since core's separator block styles
   default to a dimmer value that read as barely-there against `.hairline`'s
   own already-subtle 8%-opacity line. */
.dot-page-hero__rule.hairline {
	width: 3.5rem;
	margin-block: 0.25rem 0;
	opacity: 1;
}

/* ---- Decorative watermark word ("About" / "Contact") ----
   Mobile: removed entirely, same as hero-overprint/hero-groups. md+:
   absolutely positioned, clipped by the section's overflow:hidden.
   Deliberately smaller and fainter than the homepage watermarks
   (hero-overprint's "Open", hero-groups' "Groups") — this is a
   secondary-page device, not the biggest visual moment on the site. */
.dot-page-hero__ghost {
	display: none;
}

@media (min-width: 768px) {
	.dot-page-hero__ghost {
		display: block;
		position: absolute;
		z-index: 0;
		right: clamp(-0.5rem, -1.5vw, -2rem);
		bottom: -0.06em;
		margin: 0;
		font-weight: 800;
		text-transform: uppercase;
		line-height: 0.75;
		letter-spacing: -0.03em;
		font-size: clamp(6rem, 18vw, 15rem);
		pointer-events: none;
		user-select: none;
		white-space: nowrap;
		color: color-mix(in srgb, var(--ink) 6%, transparent);
	}

	@supports ((-webkit-text-stroke: 1px black)) {
		.dot-page-hero__ghost {
			color: transparent;
			-webkit-text-stroke: 2px color-mix(in srgb, var(--ink) 12%, transparent);
		}
	}
}

/* ---- Motion — same blur-fade reveal convention as every other hero ---- */
/* Text elements (eyebrow/title/rule) animate transform + blur only, NOT
   opacity — confirmed via a direct Playwright probe against DDEV that an
   axe-core scan firing mid-animation (real risk: this runs on plain
   page.goto(), no wait for animation-end) reads the *live* computed
   opacity, which dips as low as ~0.045 during the fade-in. Since the
   text color itself is always var(--accent) (#0b0b0b, ~19:1 against
   var(--bg)), that transient low opacity — not a distinct hardcoded
   gray, there isn't one — is what axe reported as a handful of
   different failing grays per hero variant (SC 1.4.3, color-contrast).
   Blur alone reads as a legible "reveal" without ever touching alpha,
   so contrast now holds at every frame, not just at rest. Only the
   decorative, aria-hidden `-ghost` watermark still fades via opacity —
   it carries no text/meaning, so SC 1.4.3 doesn't apply to it. */
@media (prefers-reduced-motion: no-preference) {
	.dot-page-hero__reveal {
		transform: translateY(14px);
		filter: blur(6px);
		animation: dotPageHeroIn var(--motion-slow) var(--ease) both;
	}

	.dot-page-hero__reveal:nth-child(1) {
		animation-delay: 60ms;
	}
	.dot-page-hero__reveal:nth-child(2) {
		animation-delay: 150ms;
	}
	.dot-page-hero__reveal:nth-child(3) {
		animation-delay: 240ms;
	}

	.dot-page-hero__reveal-ghost {
		opacity: 0;
		transform: scale(1.04);
		animation: dotPageHeroGhostIn 900ms var(--ease) 120ms both;
	}
}

@keyframes dotPageHeroIn {
	to {
		transform: none;
		filter: none;
	}
}

@keyframes dotPageHeroGhostIn {
	to {
		opacity: 1;
		transform: none;
	}
}

html[data-motion="reduce"] .dot-page-hero__reveal,
html[data-motion="reduce"] .dot-page-hero__reveal-ghost {
	opacity: 1 !important;
	transform: none !important;
	filter: none !important;
	animation: none !important;
}

/* ============================================================
   11. Authored Page Content — About / Contact prose area
   ============================================================
   Scoped to `.dot-page-content` (a className added to the wp:post-content
   block in page-about.html/page-contact.html, replacing page.html's
   plain unstyled default). The block's own `layout.contentSize` (set in
   each template) already caps and centers every direct child at a
   readable measure — this section only adds the type scale/rhythm on
   top. 42rem/first-child lede pattern matches acf/content-section's
   own established prose treatment (see blocks/content-section/style.css)
   rather than inventing a different measure for these two pages.
   ============================================================ */

/* First block of the page's real content reads as its lede — larger,
   muted, lighter weight — identical convention to acf/content-section's
   own first-child rule. */
.dot-page-content > *:first-child {
	font-size: var(--step-1);
	font-weight: 400;
	line-height: 1.45;
	color: var(--muted);
}

.dot-page-content p {
	font-size: var(--step-0);
	line-height: 1.7;
}

.dot-page-content > * + * {
	margin-top: 1.5rem;
}

/* Section headings ("Our Mission", "Get In Touch", "Visit Us", …) get a
   hairline divider + generous top rhythm, so the authored copy reads as
   distinct editorial sections rather than one unbroken column of text. */
.dot-page-content h2 {
	margin-top: 3rem;
	padding-top: 2rem;
	font-size: var(--step-2);
	border-top: 1px solid var(--line);
}

.dot-page-content h2:first-child {
	margin-top: 0;
	padding-top: 0;
	border-top: none;
}

/* ============================================================
   12. Contact Info Panel — structured phone/hours/address display
   ============================================================
   Scoped to `.dot-contact-info`, a className applied to a core/list
   block directly in the Contact page's own authored content (not a new
   core/group layout — core/group is outside the Page-editor block
   allowlist, see inc/block-allowlist.php; core/list is already on it).
   Turns the page's real phone/hours/address text into a row of
   hairline-bordered cards instead of a single plain paragraph, without
   changing any of the actual values. Label/value styling matches
   blocks/stat-row's own established uppercase-label convention.
   ============================================================ */
.dot-contact-info {
	list-style: none;
	display: flex;
	flex-wrap: wrap;
	gap: 1rem;
	margin: 0;
	padding: 0;
}

.dot-contact-info li {
	flex: 1 1 14rem;
	margin: 0;
	padding: clamp(1.1rem, 3vw, 1.5rem);
	background: var(--surface);
	border: 1px solid var(--line);
	border-radius: var(--r-lg);
	font-size: 1.05rem;
	font-weight: 600;
	line-height: 1.5;
}

.dot-contact-info li strong {
	display: block;
	margin-bottom: 0.4rem;
	font-size: var(--step--1);
	font-weight: 700;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--muted);
}

/* ============================================================
   13. 404 Hero — editorial "wandered off the map" redesign
   ============================================================
   patterns/404.php, inserted via templates/404.html. Same hero
   vocabulary as §10 (Authored Page Hero) and the homepage's
   blocks/hero-overprint: an absolutely-positioned outlined watermark
   (aria-hidden, decorative only — meaning carried entirely by the
   visible copy) behind a solid display-2 headline. The watermark here
   is the numeral "404" rather than a word — literal, graphic, and the
   biggest/boldest use of the device on the site, since this page has
   one job (redirect a lost visitor) and can afford to be the most fun
   about it. CTAs reuse the exact .btn-primary/.btn-outline/.btn-lg
   pairing and copy ("Plan Your Visit" / "Explore the Shop") already
   used by every homepage hero, so a visitor who lands here sees the
   same two doors out they'd see anywhere else on the site. No search
   block (client does not want one here — the shopping-assistant
   widget already covers search sitewide); Home survives only as a
   quiet text link, not a button, since Tickets/Shop are the real ask.
   ============================================================ */
.dot-404-hero {
	position: relative;
	isolation: isolate;
	overflow: hidden;
}

.dot-404-hero__shell {
	position: relative;
	z-index: 1;
	min-block-size: 56svh;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: flex-start;
	gap: 1.1rem;
}

/* Core's "is-layout-flow" block-gap margin would otherwise stack on top
   of the flex `gap` above — same fix as `.dot-page-hero__shell > *`
   in §10. */
.dot-404-hero__shell > * {
	margin-block: 0;
}

.dot-404-hero__title {
	max-width: 15ch;
	line-height: 1.02;
}

.dot-404-hero__actions {
	display: flex;
	flex-wrap: wrap;
	gap: 0.75rem;
	margin-top: 0.35rem;
}

.dot-404-hero__home {
	margin-top: 0.25rem;
	font-size: 0.95rem;
}

.dot-404-hero__home a {
	color: var(--muted);
	text-decoration: underline;
	text-underline-offset: 0.15em;
}

.dot-404-hero__home a:hover {
	color: var(--ink);
}

/* ---- Decorative outlined watermark numeral ("404") ----
   Mobile: removed entirely, same as every other hero on the site. md+:
   absolutely positioned, bleeding off the right/bottom edges, clipped
   by the section's overflow:hidden. Sized between hero-overprint's
   "Open" and hero-colossus's monumental scale — this page can be the
   boldest use of the device since there's no competing content below
   the fold. */
.dot-404-hero__ghost {
	display: none;
}

@media (min-width: 768px) {
	.dot-404-hero__ghost {
		display: block;
		position: absolute;
		z-index: 0;
		right: clamp(-1.5rem, -3vw, -4rem);
		bottom: -0.14em;
		margin: 0;
		font-weight: 800;
		line-height: 0.72;
		letter-spacing: -0.03em;
		font-size: clamp(14rem, 38vw, 34rem);
		pointer-events: none;
		user-select: none;
		white-space: nowrap;
		/* Faint solid fallback where text-stroke is unsupported. */
		color: color-mix(in srgb, var(--ink) 7%, transparent);
	}

	@supports ((-webkit-text-stroke: 1px black)) {
		.dot-404-hero__ghost {
			color: transparent;
			-webkit-text-stroke: 2px color-mix(in srgb, var(--ink) 14%, transparent);
		}
	}
}

/* ---- Motion — same blur-fade reveal convention as every other hero ---- */
/* Text elements animate transform + blur only, NOT opacity — see the
   longer note on .dot-page-hero__reveal above (§10): a mid-animation
   axe scan reads the live, transiently-low opacity as a below-threshold
   color-contrast failure even though the text's actual color
   (var(--accent)) never changes. Only the decorative, aria-hidden
   `-ghost` watermark still fades via opacity. */
@media (prefers-reduced-motion: no-preference) {
	.dot-404-hero__reveal {
		transform: translateY(14px);
		filter: blur(6px);
		animation: dot404HeroIn var(--motion-slow) var(--ease) both;
	}

	.dot-404-hero .btn:hover {
		transform: translateY(-2px);
	}

	.dot-404-hero__reveal:nth-child(1) {
		animation-delay: 60ms;
	}
	.dot-404-hero__reveal:nth-child(2) {
		animation-delay: 150ms;
	}
	.dot-404-hero__reveal:nth-child(3) {
		animation-delay: 240ms;
	}
	.dot-404-hero__reveal:nth-child(4) {
		animation-delay: 330ms;
	}
	.dot-404-hero__reveal:nth-child(5) {
		animation-delay: 400ms;
	}

	.dot-404-hero__reveal-ghost {
		opacity: 0;
		transform: scale(1.04);
		animation: dot404HeroGhostIn 900ms var(--ease) 120ms both;
	}
}

@keyframes dot404HeroIn {
	to {
		transform: none;
		filter: none;
	}
}

@keyframes dot404HeroGhostIn {
	to {
		opacity: 1;
		transform: none;
	}
}

html[data-motion="reduce"] .dot-404-hero__reveal,
html[data-motion="reduce"] .dot-404-hero__reveal-ghost {
	opacity: 1 !important;
	transform: none !important;
	filter: none !important;
	animation: none !important;
}

html[data-motion="reduce"] .dot-404-hero .btn:hover {
	transform: none !important;
}
