/* ==========================================================================
   Locations screen
   Contents: hero map · filter bar · location cards · help card
   ========================================================================== */


/* Illustrated map (hero + Map view)
   ========================================================================== */

/*
 * One coordinate system for everything on the map: the drawing's own units, the
 * projection in template-parts/components/locations-map.php. Each piece carries
 * its position in those units (--x, --y); the map carries the area that must
 * always be in view (--map-x, --map-y, --map-w, --map-h); and --map-scale turns
 * units into pixels. Fitting that area instead of stretching it keeps the
 * coastline true at every shape the map is given (1.2:1 in the hero at 1100px,
 * 3:1 at 2560px), and the drawing reaches far enough past the pins to fill
 * whatever the box leaves over.
 *
 * --map-scale is the largest scale at which the area fits, up to 1.25px per unit.
 * Past that the pins would only spread further apart while their markers and
 * labels stop growing (both are capped below), so a wider box shows more of the
 * map instead. --map-align says where the room left over goes, across: centred
 * by default; the hero puts it all on the right (see its rule).
 *
 * The map is a size container, so cqw and cqh below are its own width and
 * height, and the pins and labels are sized in cqw so they grow with the map,
 * not the viewport — the map appears at very different widths, and fixed-px
 * labels collided as soon as it got narrower. These custom properties are only
 * recipes here: container units resolve on the element that uses them, and
 * every element that uses them is inside the map.
 */
.locations-map {
	--map-align: 0.5;
	--map-scale: min(100cqw / var(--map-w), 100cqh / var(--map-h), 1.25px);
	--map-left: calc((100cqw - var(--map-w) * var(--map-scale)) * var(--map-align));
	--map-top: calc((100cqh - var(--map-h) * var(--map-scale)) / 2);

	container-type: size;
	position: relative;
	overflow: hidden;
	aspect-ratio: 5 / 2;
	background: #b8dcfa; /* the drawing's water, for the moment before it arrives */
}

.locations-map__art,
.locations-map__place,
.locations-map__pin {
	position: absolute;
	top: calc(var(--map-top) + (var(--y) - var(--map-y)) * var(--map-scale));
	left: calc(var(--map-left) + (var(--x) - var(--map-x)) * var(--map-scale));
}

.locations-map__art {
	--x: var(--art-x);
	--y: var(--art-y);

	width: calc(var(--art-w) * var(--map-scale));
	max-width: none;
	height: calc(var(--art-h) * var(--map-scale));
}

/*
 * The sea past the drawing's east edge.
 *
 * The drawing ends 1,300 units east (off Little Compton, Rhode Island), which is
 * 1,575px right of the map's left edge at the largest scale. The hero's map runs
 * to the window's edge, 56% of it, so from about 2,810px wide the box is wider
 * than the drawing: measured at 3,268 it left 255px of the plain fallback blue,
 * with a visible seam where the drawing stopped. What lies past that edge, in
 * the strip the hero shows, is open Atlantic, so instead of drawing more map
 * this continues the drawing's own water under it, three drawings wide.
 *
 * It is the SVG's gradient re-expressed in CSS. There it runs from the top-left
 * corner along (0.3, 1) of the drawing's box, #c2e2fc to #a8d4f8, and holds the
 * last colour past the end. On a box of the same height and three times the
 * width, starting at the same corner: the same direction is 169deg (11° off
 * vertical, from 0.3 x 960/1480), and the last colour lands at 1.09/1.9 = 57.4%
 * of the line instead of at its end. Change one, change both: COLORS and the
 * gradient in tools/build-locations-map.py.
 */
.locations-map::before {
	position: absolute;
	top: calc(var(--map-top) + (var(--art-y) - var(--map-y)) * var(--map-scale));
	left: calc(var(--map-left) + (var(--art-x) - var(--map-x)) * var(--map-scale));
	width: calc(var(--art-w) * 3 * var(--map-scale));
	height: calc(var(--art-h) * var(--map-scale));
	content: "";
	background: linear-gradient(169deg, #c2e2fc 0%, #a8d4f8 57.4%);
}

/* Centred on their point and set in the theme's typeface, like the pin labels. */
.locations-map__place {
	font-family: var(--font-heading);
	font-size: clamp(0.625rem, 2.1cqw, 1.25rem);
	font-weight: 600;
	line-height: 1;
	letter-spacing: 0.2em;
	color: #a8bdd3;
	text-transform: uppercase;
	white-space: nowrap;
	transform: translate(-50%, -50%) rotate(-9deg);
}

.locations-map__pin {
	transform: translate(-50%, -92%);
}

/*
 * The map view on a phone: under 460px wide, "Long Island" no longer fits
 * between Hauppauge and the map's edge, and gets cut. The pin labels are
 * already hidden at that size (see the map view's rules below).
 */
@container (max-width: 459.98px) {
	.locations-map__place {
		display: none;
	}
}

/*
 * The OpenStreetMap credit the drawing's licence asks for: small and out of
 * the way, but on the map itself, where the licence wants it.
 */
.locations-map__credit {
	position: absolute;
	bottom: 0;
	left: 0;
	margin: 0;
	padding: 0.125rem 0.375rem;
	font-size: 0.6875rem;
	line-height: 1.4;
	color: var(--color-muted);
	background: rgb(255 255 255 / 0.7);
	border-top-right-radius: var(--radius-sm);
}

.locations-map__credit a {
	color: inherit;
}

/*
 * The pin itself is the flex box, not only the link inside it.
 *
 * The label is absolutely positioned, so where it lands vertically is its
 * STATIC position — and for an absolutely positioned child of a flex container
 * that is decided by `align-items`. When the only flex box was the <a>, the
 * illustrated map (whose pins stopped being links on 22 September) lost it: the
 * label fell back to normal flow, which puts it a whole marker-height below the
 * pin instead of beside it. That is the diagonal gap Robert spotted, and the
 * live site did not have it because there every pin is still a link.
 */
.locations-map__pin,
.locations-map__pin a {
	display: flex;
	gap: 0.2rem;
	align-items: center;
}

.locations-map__pin a {
	color: inherit;
	pointer-events: auto;
}

.locations-map__marker {
	width: clamp(16px, 3.2cqw, 28px);
	height: auto;
	/*
	 * --color-link, the same token as the pins on the live map below and the
	 * marker on a location's own page (assets/js/location-map.js), so a dot
	 * marking a location looks the same wherever it appears — Robert's call on
	 * 23 September 2026. Before that this was #0b5bd9, a hand-typed blue that
	 * belonged to no token: measured, it sat between --color-link and
	 * --color-primary and 11.6 away from the brand blue it was described as.
	 */
	color: var(--color-link);
	filter: drop-shadow(0 3px 4px rgb(10 40 100 / 0.3));
}

.locations-map__label {
	position: absolute;
	left: 100%;
	padding-left: 0.3rem;
	font-family: var(--font-heading);
	font-size: clamp(0.625rem, 1.55cqw, 0.8125rem);
	font-weight: 600;
	color: var(--color-ink);
	white-space: nowrap;
	text-shadow: 0 0 4px #fff, 0 0 8px #fff;
}

/*
 * Where two pins are close enough for their labels to overlap, one label goes
 * on another side: "Label position" on the location's edit screen.
 */
.locations-map__pin--left,
.locations-map__pin--left a {
	flex-direction: row-reverse;
}

.locations-map__pin--left .locations-map__label {
	right: 100%;
	left: auto;
	padding-right: 0.3rem;
	padding-left: 0;
}

.locations-map__pin--above .locations-map__label,
.locations-map__pin--below .locations-map__label {
	left: 50%;
	padding-left: 0;
	transform: translateX(-50%);
}

.locations-map__pin--above .locations-map__label {
	bottom: 100%;
	padding-bottom: 0.15rem;
}

.locations-map__pin--below .locations-map__label {
	top: 100%;
	padding-top: 0.1rem;
}

.page-hero--locations .page-hero__media {
	height: auto;
	-webkit-mask-image: none;
	mask-image: none;
}

.page-hero--locations .page-hero__subtitle {
	margin-top: var(--space-3);
}

@media (max-width: 991.98px) {
	.page-hero--locations {
		flex-direction: column-reverse;
	}

	.page-hero--locations .page-hero__inner {
		padding-top: var(--space-8);
		padding-bottom: var(--space-4);
	}

	/*
	 * Stacked, the map became a second band under the hero text: too small to read ten
	 * pins, and with its note already hidden. The "View as" switch below opens the same
	 * map full width on demand, so the hero drops it here rather than shrinking it.
	 * Scoped to the hero — the map view further down the page is untouched.
	 */
	.page-hero--locations .page-hero__media {
		display: none;
	}
}

@media (min-width: 992px) {
	.page-hero--locations {
		min-height: 395px; /* mockup hero height */
	}

	/*
	 * A short fade: a longer one swallowed the westernmost pins and their labels.
	 * Capped in pixels too, for the same reason: at 2560px 7% is 100px, and the
	 * map's largest scale leaves Midtown's label 97px from the edge.
	 *
	 * And all the way to the window's edge, where from 1400 a photograph stops
	 * at the content's (.page-hero__media in main.css). That rule exists because
	 * cover cropped the photographs harder the wider the screen; the map is never
	 * cropped — a wider box shows more of the map at the same scale — so it has
	 * nothing to fix here, and the extra coastline is the better use of the room.
	 * Past the drawing's east edge it shows the drawing's sea (::before above).
	 * The card note counts on that edge too: it sits inside this block and is
	 * placed from its right with --container-edge, which is measured on the hero.
	 */
	.page-hero--locations .page-hero__media {
		--map-fade: min(7%, 80px);

		right: 0;
		width: 56%;
		-webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 var(--map-fade));
		mask-image: linear-gradient(90deg, transparent 0%, #000 var(--map-fade));
	}

	/*
	 * Left-aligned: on a screen wide enough for the map to reach its largest
	 * scale (about 1530px and up), the room left over goes to the right. The
	 * handwritten note stops at the page container's edge, which on those screens
	 * is well inside the map: centred, the south-shore pins ran underneath it.
	 * This way the note sits over the open water east of them. The cap above is
	 * what keeps it clear: measured with the wider Spanish note, Merrick's label
	 * keeps 20px or more from it at every width.
	 */
	.page-hero--locations .locations-map {
		--map-align: 0;

		position: absolute;
		inset: 0;
		aspect-ratio: auto;
	}

	/* Clear of the fade, which would otherwise swallow the first words. */
	.page-hero--locations .locations-map__credit {
		left: var(--map-fade);
		border-top-left-radius: var(--radius-sm);
	}
}


/* Filter bar
   ========================================================================== */

.filter-bar {
	display: flex;
	flex-wrap: wrap;
	gap: var(--space-4) var(--space-6);
	align-items: center;
	justify-content: space-between;
}

.view-switch {
	display: flex;
	gap: var(--space-2);
	align-items: center;
	font-size: 0.9375rem;
}

.view-switch__label {
	margin-right: var(--space-2);
	color: var(--color-muted);
}

.view-switch__option {
	display: inline-flex;
	gap: var(--space-2);
	align-items: center;
	min-height: 40px;
	padding: 0.4rem 0.9rem;
	color: var(--color-text);
	border-radius: var(--radius-sm);
}

.view-switch__option.is-active {
	font-weight: 600;
	color: var(--color-primary);
	background: var(--color-surface-chip);
}

/*
 * Map view really is the map: the cards step aside and the pins become the links. The
 * help card stays — it is a call to action, not a result. The cards are only hidden, not
 * removed, so the area filter still reports how many locations match.
 */
.locations-directory[data-view="map"] .location-card {
	display: none;
}

/* Only the help card is left in there, so the card grid stops being a grid. */
.locations-directory[data-view="map"] .location-grid {
	display: block;
}

/*
 * Below 992px the map is too narrow for ten labels at once — they land on top of each other
 * however they are placed. The map still shows every marker (each one a link carrying its
 * location name and address), and the names come back as soon as the filter narrows the set to
 * a handful, which is when they fit. data-matches is written by filterable.js.
 */
@media (max-width: 991.98px) {
	.locations-directory[data-view="map"] .locations-map--panel .locations-map__label {
		display: none;
	}
}

/* Measured: five labels fit from 576px, three on a phone. */
@media (min-width: 576px) and (max-width: 991.98px) {
	.locations-directory[data-view="map"]:is([data-matches="1"], [data-matches="2"], [data-matches="3"], [data-matches="4"], [data-matches="5"]) .locations-map--panel .locations-map__label {
		display: block;
	}
}

@media (max-width: 575.98px) {
	.locations-directory[data-view="map"]:is([data-matches="1"], [data-matches="2"], [data-matches="3"]) .locations-map--panel .locations-map__label {
		display: block;
	}
}

.locations-map-view {
	margin-top: var(--space-5);
	overflow: hidden;
	border-radius: var(--radius-lg);
	box-shadow: var(--shadow-sm);
}


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

.location-grid {
	display: grid;
	gap: var(--space-5);
	margin-top: var(--space-5);
}

.location-card {
	display: flex;
	flex-direction: column;
	overflow: hidden;
	scroll-margin-top: var(--space-6);
	background: var(--color-white);
	border: 1px solid #e6edf5;
	border-radius: var(--radius);
	box-shadow: var(--shadow-sm);
}

/*
 * The "this is the one" highlight when you arrive at a location by its anchor.
 *
 * It was --color-sky-strong, which measures 1.96:1 against the card's white: the
 * highlight was there, but you could not see it. That light blue was chosen for
 * the dark navy — over the home page's panel it gives 4.84:1, and there it does
 * its job — not for a white card. --color-primary gives 8.13:1 on white, the same
 * value the global focus ring took when it was fixed on 22 September 2026.
 *
 * It does not carry the white band the focus ring does: that exists for when the
 * surface is dark, and this card is always white.
 */
.location-card:target {
	outline: 3px solid var(--color-primary);
	outline-offset: 2px;
}

.location-card__media {
	position: relative;
}

.location-card__image {
	width: 100%;
	aspect-ratio: 2.4 / 1;
	object-fit: cover;
}

/*
 * Badge row over the photo: distance (when ranked) then region, right-aligned.
 * Measured from 320 to 1440px with the widest pair there can be ("Westchester"
 * plus a two-digit distance): 168px, inside a 236px row at the narrowest card
 * (a 576px window, the first with two columns), so it never wraps.
 */
.location-card__badges {
	position: absolute;
	right: var(--space-3);
	bottom: var(--space-3);
	left: var(--space-3);
	display: flex;
	flex-wrap: wrap;
	gap: 0.35rem;
	justify-content: flex-end;
}

/* Compact card rhythm measured on the Locations mockup. */
.location-card__body {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: 0.5rem;
	padding: 0.625rem 1rem 0.75rem;
}

.location-card__title {
	font-size: 1.25rem;
	color: var(--color-navy);
}

/*
 * Distance, written by assets/js/locations-search.js only while the grid is
 * ranked. Filled navy against the white region badge: it is the answer to what
 * the visitor just asked, not another category label, so it reads as a distinct
 * object rather than a second chip of the same kind. Sentence case, because a
 * measurement is not a label — the badge's own uppercase and letter-spacing are
 * cancelled here.
 */
.location-card__distance {
	font-size: 0.75rem;
	letter-spacing: 0;
	text-transform: none;
	color: var(--color-white);
	background: var(--color-navy);
	/*
	 * White hairline so the pill keeps an edge on any photo. The text itself is
	 * 13.2:1 either way, but the pill's SHAPE was disappearing: measured against
	 * the ten real photos, navy against the photo behind it runs from 6.64:1
	 * (Merrick, a bright awning) down to 1.07:1 and 1.01:1 (Brooklyn and Jamaica
	 * Estates, dark doorways) — invisible. The two defences are complementary:
	 * on a dark photo the white ring carries the edge (13.3:1 on the darkest of
	 * them), on a bright one the navy body does.
	 */
	box-shadow: 0 0 0 1.5px rgb(255 255 255 / 0.92), var(--shadow-sm);
}

.detail-list {
	display: grid;
	gap: 0.45rem;
	font-size: var(--fs-sm);
	line-height: 1.4;
}

.detail-list__item {
	display: grid;
	grid-template-columns: 18px minmax(0, 1fr);
	gap: var(--space-2);
	align-items: start;
}

/* Street and city: exactly one line each, so every card keeps a two-line address. */
.detail-list__line {
	display: block;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.detail-list__item .icon {
	margin-top: 1px;
	color: var(--color-muted);
}

.location-card__actions {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: var(--space-2);
	margin-top: auto;
	padding-top: 0.25rem;
}

.location-card__actions .btn {
	min-height: 36px;
	padding-inline: 0.5rem;
}


/* Help card
   ========================================================================== */

/*
 * The card asks about its own width, not the viewport's: from 600px it is a banner (image
 * right, copy left) — two cards wide in the grid, full width under the map — and below that,
 * on a phone, it stacks. 600px is a card wide enough for the banner; below it the copy would
 * run under the photo.
 */
.help-card {
	container-type: inline-size;
	position: relative;
	display: flex;
	flex-direction: column;
	overflow: hidden;
	background: linear-gradient(90deg, #eaf3fd 0%, #deebf9 100%);
	border-radius: var(--radius-lg);
}

.help-card__image {
	width: 100%;
	aspect-ratio: 16 / 9;
	object-fit: cover;
	-webkit-mask-image: linear-gradient(180deg, #000 60%, transparent 100%);
	mask-image: linear-gradient(180deg, #000 60%, transparent 100%);
}

.help-card__content {
	position: relative;
	padding: var(--space-6);
}

.help-card__title {
	font-size: clamp(1.5rem, 1.1rem + 1.1vw, 2rem);
	line-height: 1.15;
	color: var(--color-ink);
}

.help-card__text {
	max-width: 19rem; /* first line ends at "most", as in the mockup */
	margin-block: var(--space-3) var(--space-5);
}

/*
 * Narrow (a phone): photo on top, copy centred under it, like the info banner. The image is
 * the only thing that moves out of flow when the card gets wide, so the card itself never
 * needs a different display value.
 */
@container (max-width: 599.98px) {
	.help-card__content {
		text-align: center;
	}

	.help-card__text {
		margin-inline: auto;
	}

	.help-card .button-group {
		justify-content: center;
	}
}

/* Wide: the mockup's banner — photo bleeding in from the right, copy on the left. */
@container (min-width: 600px) {
	.help-card__image {
		position: absolute;
		inset: 0 0 0 auto;
		width: 55%;
		height: 100%;
		aspect-ratio: auto;
		-webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 35%);
		mask-image: linear-gradient(90deg, transparent 0%, #000 35%);
	}

	/* Wide enough for both regular-size buttons side by side, as in the mockup (they wrap if there is no room). */
	.help-card__content {
		min-height: 320px;
		max-width: 73%;
		padding: 1.75rem 0 1.75rem 2rem;
	}

	.help-card .button-group {
		gap: 0.625rem;
	}
}

/*
 * Two columns from 576, not 768, so the directory breaks in the same place the
 * blog does. Below 768 this grid was a single column, and on a 700px screen
 * that means a **668px card**: one branch per screenful, out of ten, in a card
 * stretched to fill the row rather than sized by its contents.
 *
 * The help card's full-width rule travels in the same block on purpose — with
 * two columns and no rule of its own it would sit in half a row.
 */
@media (min-width: 576px) {
	.location-grid {
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}

	.help-card {
		grid-column: 1 / -1;
	}
}

@media (min-width: 992px) {
	/*
	 * Equal rows: every card (and the help card) takes the height of the
	 * tallest row. So when the banner is the taller one, every card opens a
	 * gap above its buttons. With the service tags gone (15 October 2026),
	 * that is what the Spanish did: its button no longer fitted beside the
	 * phone and its line ran to three, and at a 1440px window the banner
	 * measured 410px against 323 for a card. Both translations were tightened
	 * ("Contáctanos", "Te ayudamos a encontrar…") and it measures 328, as the
	 * English does. New copy for this banner has to fit the same way.
	 */
	.location-grid {
		grid-template-columns: repeat(3, minmax(0, 1fr));
		grid-auto-rows: 1fr;
	}

	/* With nothing found there are no cards to match, and equal rows would only stretch
	   the help card's row for no reason. Keyed off the section's own count now that
	   the notice lives outside this grid. */
	.locations-directory[data-matches="0"] .location-grid {
		grid-auto-rows: auto;
	}

	/* …and with no cards beside it, a two-column card hugging the left edge reads as a
	   layout accident, so it takes the row. */
	.locations-directory[data-matches="0"] .help-card {
		grid-column: 1 / -1;
	}

	.help-card {
		grid-column: span 2;
	}

	/* The grid already gives the row its height; the banner's own minimum would only add to it. */
	.help-card__content {
		min-height: 0;
	}
}

/* Narrowest three-column range: a little less side padding keeps the longest street on one line. */
@media (min-width: 992px) and (max-width: 1199.98px) {
	.location-card__body {
		padding-inline: 0.875rem;
	}
}

/*
 * Four columns from xxl — and the blog grid takes its fourth at the same place,
 * so the two directories break together and a visitor moving from one to the
 * other never sees the density change for no reason they can name.
 *
 * It sat at 1400 before, the comment explaining it by "a one-line street and
 * two tags per row". On 23 September 2026 it was lowered to 1200 to match the
 * blog, and Robert caught the result at a 1214px window: the cards grew a void
 * of nearly 300px above their buttons. Restored, with what the measurement
 * actually found. The service tags were the smaller reason, and they left the
 * card on 15 October 2026; the one that decides it is still here:
 *
 *   - **The help banner.** It spans two columns, so four narrow columns leave
 *     it 565px wide — and its own container query switches at 600px. Below
 *     that it uses the narrow layout written for a phone: photograph on top,
 *     copy centred under it. That layout is about twice as tall — 635px
 *     against 328, measured again on 15 October 2026 — and since
 *     grid-auto-rows: 1fr keeps a row's cards equal, the banner drags the
 *     location cards up with it and opens a void of over 300px above their
 *     buttons. Robert saw it at a 1214px window.
 *
 * So the banner is not at fault and there is nothing in it to fix: it behaves
 * exactly as designed, and it stays two cards wide, which is Robert's rule. The
 * question is only where four columns may begin, and the arithmetic answers it:
 * the banner reaches its 600px at a container of 1220, a window of 1284.
 *
 * 1400 is the next breakpoint the theme's scale defines above that, and the
 * ~116px of margin is deliberate — the banner's title, paragraph and buttons are
 * editable text, so a threshold set to the millimetre of today's copy would
 * break the day someone rewrites it. It already would: at 1284 the English
 * buttons do not fit side by side, and the banner is 383px tall against 317 for
 * a card.
 */
@media (min-width: 1400px) {
	.location-grid {
		grid-template-columns: repeat(4, minmax(0, 1fr));
	}
}


/* -------------------------------------------------------------------------
 * "Use my current location"
 *
 * Sits under the hero search field, aligned to it. Ships hidden and is shown
 * by JavaScript once the browser's geolocation API is confirmed, so it never
 * appears as a control that cannot do anything.
 * ---------------------------------------------------------------------- */

/* Query container for the search field and the control under it. */
.location-search {
	container: location-search / inline-size;
}

/*
 * No scripts, no row. The button cannot do anything without them, and the status
 * line has nothing to say, so the whole thing goes — including the margin an
 * empty paragraph would otherwise still push (measured: 12px of dead space).
 *
 * The `js` class comes from an inline script in <head> (inc/assets.php), so this
 * is settled before the first paint. Revealing the button from JavaScript
 * instead, as this used to, works only while the script wins the race against
 * painting: measured on quantum.local it ran at 84ms against a first paint at
 * 124ms, but a deferred script does not block painting, so on a slow connection
 * the button would appear late and push everything under it down 22px.
 */
html:not(.js) .location-geo {
	display: none;
}

.location-geo {
	--location-geo-icon: 16px;
	--location-geo-gap: 0.4rem;
	/* Lines the message up with the button's label rather than with its icon. */
	--location-geo-indent: calc(var(--location-geo-icon) + var(--location-geo-gap));
	/* Starts under the field's placeholder, not under the pill's edge. Named so
	   the desktop rule below can offset by exactly the same amount. */
	--location-geo-pad: calc(var(--pill-text-inset) - var(--location-geo-icon) - var(--location-geo-gap));

	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--space-2) var(--space-3);
	max-width: 600px;
	margin-top: var(--space-3);
	padding-left: var(--location-geo-pad);
}

.location-geo__button {
	display: inline-flex;
	align-items: center;
	gap: var(--location-geo-gap);
	padding: 0;
	font: inherit;
	font-size: var(--fs-sm);
	font-weight: 600;
	color: var(--color-link);
	cursor: pointer;
	background: none;
	border: 0;
}

.location-geo__button:hover {
	color: var(--color-link-hover);
	text-decoration: underline;
}

.location-geo__button[aria-busy="true"] {
	cursor: progress;
	opacity: 0.7;
}

/*
 * Beside the button, wrapping under it only where the field itself stacks.
 *
 * The tightest case is the narrowest container that does not stack — just above
 * 460px — where about 244px are left beside the button. Every message is kept at
 * or under 210 to leave room for a translation and for fonts that render wider on
 * other platforms. That budget is a constraint on the strings, not a suggestion:
 * a longer one wraps below and brings back the 30.4px jump it was measured
 * causing, which is what two Spanish strings did until 28 September 2026. See
 * "Message widths" in the README before editing or translating any of them.
 */
.location-geo__status {
	font-size: var(--fs-sm);
	color: var(--color-text);
	/*
	 * If a message ever exceeds the budget it wraps under the button, which is
	 * ugly but readable and overlaps nothing — measured. One case was not
	 * graceful: a single unbreakable run of characters (a bad translation, a
	 * pasted URL) escaped the row by 92px instead of wrapping. This makes it
	 * break inside the word rather than leave the layout.
	 */
	overflow-wrap: anywhere;
}

/*
 * Nothing at all until there is something to say.
 *
 * An empty live region is still a flex item: on its own row it left a row's gap
 * of dead space under the button, which showed up as the stacked layouts (995px
 * and 571px) sitting lower than the ones beside them, and beside the button it
 * left a phantom column gap that pushed the button off centre once the row
 * centres. Reserving a line instead removes the jump when a message appears, but
 * spends that space on every visit for a message most visitors never see — the
 * jump is the better trade, and it only ever happens right after a press.
 */
.location-geo__status:empty {
	display: none;
}

/*
 * Red is for the three messages that mean the position could not be had. The
 * one for a visitor who just chose "Block" stays grey: nothing failed, they
 * exercised a preference one second earlier, and its sentence points at the
 * field instead. Colour is not carrying the meaning here — each sentence says
 * what happened — so WCAG 1.4.1 is satisfied without an icon, which would add
 * exactly the attention this is trying not to take.
 */
.location-geo__status--error {
	color: var(--color-error);
}

/*
 * Centred exactly when the field above it stacks, and never before.
 *
 * The two are one decision: while the field is a single row this lines up with
 * its placeholder; once the field becomes a stacked block, the whole thing reads
 * as centred. A viewport rule at 768px centred it while the field was still a
 * row — wrong at 693px — and left it aligned while the field was stacked in the
 * 992-1030px squeeze.
 *
 * Written twice because the field stacks under either of two conditions, and CSS
 * cannot ask "is that element stacked?". Both must be mirrored here: the
 * container rule for a squeezed column, the viewport rule for a phone. Change
 * one of them in main.css and this has to follow.
 */
@container location-search (max-width: 460px) {
	.location-geo {
		justify-content: center;
		/* Centred now, so the indents that aligned it with the placeholder would push it off. */
		--location-geo-indent: 0px;
		--location-geo-pad: 0px;
		padding-left: 0;
		text-align: center;
	}

	/* No room beside the button here, so it goes under it instead. */
	.location-geo__status {
		flex-basis: 100%;
	}
}

@media (max-width: 575.98px) {
	.location-geo {
		justify-content: center;
		--location-geo-indent: 0px;
		--location-geo-pad: 0px;
		padding-left: 0;
		text-align: center;
	}

	/* Mirrors the container rule above; see the note there. */
	.location-geo__status {
		flex-basis: 100%;
	}
}

/* -------------------------------------------------------------------------
 * Interactive map (MapLibre + OpenFreeMap), map view only
 *
 * Sits inside the same panel as the illustrated map and replaces it only once
 * assets/js/locations-map.js has a real map drawn — so if anything fails, or
 * JavaScript never runs, the panel keeps the SVG it was served with.
 * ---------------------------------------------------------------------- */

/*
 * Stacked on top of the illustrated map rather than below it, and invisible
 * until it has drawn. That is what keeps a failed upgrade invisible: while the
 * library, the style and the first render are still in flight — up to ten
 * seconds before the watchdog gives up — the visitor sees the illustrated map
 * and nothing else, instead of an empty box growing under it.
 */
.locations-map-view {
	position: relative;
}

/*
 * Scoped through the panel on purpose. MapLibre's own stylesheet is injected at
 * boot, so it loads after this file, and its `.maplibregl-map { position: relative }`
 * has the same specificity as a bare `.locations-live-map` — it would win on
 * order alone, collapse the container to zero height and leave the map sized to
 * nothing. Two classes beat one regardless of load order.
 */
.locations-map-view .locations-live-map {
	position: absolute;
	inset: 0;
	opacity: 0;
	pointer-events: none;
	transition: opacity 240ms ease;
}

.locations-map-view.is-live .locations-live-map {
	opacity: 1;
	pointer-events: auto;
}

/*
 * The panel's height comes from the illustrated map's 5:2 aspect, which is a
 * hero strip — on a phone that is around 130px, unusable for a real map you pan and
 * zoom. Only the live state gets a floor; the illustrated fallback keeps the
 * proportions it was designed for.
 */
.locations-map-view.is-live {
	min-height: 340px;
}

@media (min-width: 576px) {
	.locations-map-view.is-live {
		min-height: 400px;
	}
}

/*
 * visibility, not display: the illustrated map keeps defining the panel's height
 * (the live map fills it absolutely), while leaving the tab order and the
 * accessibility tree, so its pins are not a duplicate set of links.
 */
.locations-map-view.is-live .locations-map {
	visibility: hidden;
}

@media (prefers-reduced-motion: reduce) {
	.locations-map-view .locations-live-map {
		transition: none;
	}
}

/* Pin: the same token as the illustrated map's markers, so switching views does
   not change what a location looks like. The hover below turns it brand orange,
   which is what says "this one, the one under your pointer". */
.locations-live-map__marker {
	display: block;
	padding: 0;
	color: var(--color-link);
	cursor: pointer;
	background: none;
	border: 0;
	filter: drop-shadow(0 3px 4px rgb(10 40 100 / 0.35));
	transition: transform 120ms ease, color 120ms ease;
}

.locations-live-map__marker:hover {
	color: var(--color-orange);
	transform: scale(1.12);
}

.locations-live-map__marker:focus-visible {
	outline: 3px solid var(--color-primary);
	outline-offset: 3px;
	border-radius: 4px;
}

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

	.locations-live-map__marker:hover {
		transform: none;
	}
}

/* Hidden by the area chips, which filter the pins and the cards together. */
.locations-live-map__marker.is-filtered-out,
.maplibregl-marker.is-filtered-out {
	display: none;
}

.map-popup {
	display: grid;
	gap: 0.4rem;
	padding: 0.15rem 0.1rem;
	font-size: var(--fs-sm);
	line-height: 1.45;
}

.map-popup__title {
	font-size: 1.0625rem;
	color: var(--color-navy);
}

.map-popup__address,
.map-popup__hours {
	color: var(--color-text);
}

/*
 * The phone number has no rule here: it is the shared .link-phone (main.css),
 * like the card's. A popup rule of its own had drifted — link blue at 600
 * where the card is accent-deep at 500 — and, being a single class, it lost to
 * the global a:hover, so the number also darkened on hover where the card's
 * only underlines.
 */

/* The same pair of actions the card offers, stacked because the popup is narrow. */
.map-popup__actions {
	display: grid;
	gap: 0.4rem;
	margin-top: 0.15rem;
}

.map-popup__actions .btn {
	justify-content: center;
}

/*
 * The place that was searched for, or the visitor's own position. Deliberately
 * not a pin: it is not a location, and a second pin shape would read as an
 * eleventh location.
 */
.locations-live-map__origin {
	display: flex;
	align-items: center;
	gap: 0.3rem;
	white-space: nowrap;
}

.locations-live-map__origin-dot {
	flex: none;
	width: 16px;
	height: 16px;
	background: var(--color-orange);
	border: 3px solid var(--color-white);
	border-radius: 50%;
	box-shadow: 0 0 0 1px rgb(12 43 111 / 0.25), 0 2px 6px rgb(16 42 92 / 0.35);
}

/* Small and quiet: it explains the dot, it is not competing with the locations. */
.locations-live-map__origin-label {
	padding: 0.15rem 0.45rem;
	font-family: var(--font-heading);
	font-size: var(--fs-xs);
	font-weight: 600;
	color: var(--color-navy);
	background: rgb(255 255 255 / 0.92);
	border-radius: var(--radius-pill);
	box-shadow: var(--shadow-sm);
}

/* MapLibre's own controls, brought closer to the theme. */
.locations-live-map .maplibregl-ctrl-group {
	border-radius: var(--radius-sm);
	box-shadow: var(--shadow-sm);
}

/*
 * Attribution: styled, never hidden. The line is the site's map credit,
 * quantum_map_credit() in inc/assets.php, which assets/js/map-style.js writes
 * over the tile host's own, so it reads the same as on the illustrated map.
 * MapLibre renders it in pure black, which fights the palette; this reads it as
 * the credit line it is.
 *
 * Restyling is explicitly allowed. The OSM Foundation's attribution guidelines
 * say the credit "must be legible and understandable" and to "consider the
 * font, size, colour, contrast, positioning", recommending WCAG — so the
 * obligation is legibility, not a particular colour. --color-text is 7.5:1 on
 * this background, above AA and AAA for normal text, and every link stays live.
 *
 * What is NOT allowed: hiding it, or dropping either name from it. And not
 * `customAttribution`, which only adds to the line: the tile host's own would
 * still be there beside ours.
 */
.locations-live-map .maplibregl-ctrl-attrib {
	background: rgb(255 255 255 / 0.85);
}

.locations-live-map .maplibregl-ctrl-attrib-inner,
.locations-live-map .maplibregl-ctrl-attrib-inner a {
	color: var(--color-text);
}

.locations-live-map .maplibregl-ctrl-attrib-inner a:hover {
	color: var(--color-link);
}

.locations-live-map .maplibregl-popup-content {
	padding: 0.85rem 1rem;
	border-radius: var(--radius);
	box-shadow: var(--shadow);
}

/*
 * The "fit everything back" button, added as a custom control so it sits in the
 * same group as the zoom buttons. MapLibre's control buttons carry their icon as
 * a background image; ours is a real SVG child, so it needs centring.
 */
.locations-live-map .quantum-map-reset {
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--color-ink);
}

/*
 * Close button: MapLibre's own is 19x22px with a 12px glyph, under the 24x24
 * that WCAG 2.5.8 asks of any target — small enough that the first person to
 * see it did not notice it was there. 32x32 clears that with room to spare and
 * is a comfortable touch target, without a 44px control dominating a popup that
 * is only 270px wide at its widest.
 *
 * The hover fill covering the whole button is right and stays: it is what shows
 * where the target actually ends. Only its shape changes — a circle inset from
 * the corner, instead of a square sharing the popup's rounded corner.
 *
 * The mark inside is the theme's `x` icon, swapped in by locations-map.js: a
 * character cannot be centred in a circle, only its line box can, which left it
 * 2px low. `font-size` below is what the fallback character uses if the sprite
 * is ever missing.
 */
.locations-live-map .maplibregl-popup-close-button {
	top: 4px;
	right: 4px;
	display: flex;
	align-items: center;
	justify-content: center;
	width: 32px;
	height: 32px;
	padding: 0;
	font-size: 20px;
	line-height: 1;
	color: var(--color-muted);
	border-radius: 50%;
	transition: color var(--transition), background-color var(--transition);
}

.locations-live-map .maplibregl-popup-close-button:hover {
	color: var(--color-navy);
	background: var(--color-surface-chip);
}

/* The title must not run under it. */
.map-popup__title {
	padding-right: 1.75rem;
}


/*
 * Nothing matched: no map.
 *
 * An empty map is 500px of grey saying nothing, and in map view it sat between
 * the search and the one line explaining the result. Hiding it puts that line
 * where the visitor is already looking. The map returns, and resizes itself,
 * as soon as anything matches again.
 */
.locations-directory[data-view="map"][data-matches="0"] .locations-map-view {
	display: none;
}

/*
 * Hiding the status line at a real zero — so it does not repeat the notice
 * below it — started here and now lives in main.css, on [data-filterable]:
 * the FAQ reached the same state the day filterable.js began announcing a
 * filter that arrived in the URL.
 */

/*
 * The notice's own spacing used to live here, because this screen hit the
 * problem first: hiding the status line at zero matches (the rule above) left
 * the notice flush against the filter bar. Two things then came out of Robert
 * reporting the same gap on the Blog. The Blog had no rule of its own at all,
 * so the margin belongs to .filter-empty in main.css, shared. And the guard
 * that went with it here, .locations-results > .filter-empty:first-child, was
 * cancelling this screen's own margin on every render rather than only when
 * nothing is published — the notice is printed before the card grid (see the
 * template), so it is always that element's first child. Both rules now live in
 * main.css, with the guard written against what is actually above the notice.
 */
