/*!
 * Tower Grove Pride — festival vendor map
 * Presentation layer. Pairs with festival-map.js and vendored Leaflet 1.9.x.
 *
 * Everything is scoped under .tgp-map so it can be enqueued alongside an
 * arbitrary WordPress theme without leaking, and so theme rules that commonly
 * break Leaflet (img { max-width: 100% }, global button styling, box-sizing
 * resets) are defended against explicitly.
 */

.tgp-map {
  --tgp-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --tgp-ink: #1c1c1e;
  --tgp-ink-soft: #5b5b60;
  --tgp-surface: #ffffff;
  --tgp-surface-alt: #f4f4f6;
  --tgp-line: #dcdce1;
  --tgp-focus: #1e88e5;
  --tgp-radius: 12px;
  --tgp-shadow: 0 2px 10px rgba(0, 0, 0, 0.16);
  --tgp-tap: 44px;
  --tgp-panel-w: 310px;
  --tgp-panel-tab: 26px;
  --tgp-panel-peek: 48px;

  position: relative;
  display: flex;
  align-items: stretch;
  width: 100%;
  height: 70vh;
  min-height: 380px;
  font-family: var(--tgp-font);
  color: var(--tgp-ink);
  background: var(--tgp-surface-alt);
  border-radius: var(--tgp-radius);
  overflow: hidden;
  -webkit-text-size-adjust: 100%;
}

.tgp-map *,
.tgp-map *::before,
.tgp-map *::after {
  box-sizing: border-box;
}

/* Theme-proofing: these two rules are the usual cause of "the map is broken
   inside WordPress but fine standalone". */
.tgp-map .leaflet-container img {
  max-width: none !important;
  max-height: none !important;
  padding: 0 !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}

.tgp-map .leaflet-container {
  font-family: var(--tgp-font);
  background: var(--tgp-surface-alt);
}

.tgp-map button {
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
  color: inherit;
  text-transform: none;
  letter-spacing: normal;
}

/* The stage is everything that isn't the booth list: the Leaflet canvas plus
   all the floating chrome, which positions against the stage rather than the
   container. That's what keeps the zoom control and the attribution out from
   under the docked panel without hand-tuned offsets. */
.tgp-map__stage {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
}

.tgp-map__canvas {
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* ------------------------------------------------------------ booth list - */

/* Docked column by default; re-declared as a bottom sheet under 768px. */
.tgp-map__panel {
  position: relative;
  z-index: 1200; /* above the loading overlay (1100) and the search (1000) */
  flex: 0 0 var(--tgp-panel-w);
  width: var(--tgp-panel-w);
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--tgp-surface);
  border-right: 1px solid var(--tgp-line);
}

/* Collapsed: the panel gives all of its width back and only the edge tab —
   which hangs off the panel's right edge — is left on screen. */
.tgp-map__panel.is-collapsed {
  flex-basis: 0;
  width: 0;
  border-right-width: 0;
}

.tgp-map__panel.is-collapsed .tgp-panel__head,
.tgp-map__panel.is-collapsed .tgp-panel__body {
  display: none;
}

.tgp-panel__head {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: var(--tgp-panel-peek);
  padding: 8px 12px;
  border: 0;
  border-bottom: 1px solid var(--tgp-line);
  background: transparent;
  font-size: 14px;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
  /* The head is the sheet's drag handle: the browser must not try to scroll
     or pan while a drag is in progress. */
  touch-action: none;
}

.tgp-panel__grip {
  display: none;
}

/* Sheet-only. The docked panel is a permanent column with a visible title —
   nothing to discover, and "swipe up" would be a lie with a mouse. */
.tgp-panel__hint {
  display: none;
}

.tgp-panel__count {
  margin-left: auto;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--tgp-ink-soft);
  white-space: nowrap;
}

/* Collapse-all bar. Its own strip under the head rather than a control inside
   it — the head is a <button> and the sheet's drag handle, so nothing
   clickable may live in there. */
.tgp-panel__toolbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  padding: 4px 8px;
  border-bottom: 1px solid var(--tgp-line);
  background: var(--tgp-surface);
}

.tgp-panel__toolbar[hidden] {
  display: none;
}

.tgp-panel__allbtn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 28px;
  padding: 4px 8px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--tgp-ink-soft);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
}

.tgp-panel__allbtn:hover {
  background: var(--tgp-surface-alt);
  color: var(--tgp-ink);
}

/* Same caret idiom as a group header: pointing down means "these are open",
   rotated means "these are shut". */
.tgp-panel__allcaret {
  font-size: 9px;
  transition: transform 0.15s ease;
}

.tgp-panel__allbtn.is-collapsed .tgp-panel__allcaret {
  transform: rotate(-90deg);
}

.tgp-panel__tab {
  position: absolute;
  top: 50%;
  left: 100%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tgp-panel-tab);
  min-height: 64px;
  padding: 0;
  border: 1px solid var(--tgp-line);
  border-left: 0;
  border-radius: 0 8px 8px 0;
  background: var(--tgp-surface);
  box-shadow: 2px 0 6px rgba(0, 0, 0, 0.12);
  color: var(--tgp-ink-soft);
  cursor: pointer;
}

.tgp-panel__tab:hover,
.tgp-panel__tab:focus-visible {
  color: var(--tgp-ink);
}

.tgp-panel__chevron {
  font-size: 11px;
  line-height: 1;
  transition: transform 0.15s ease;
}

.tgp-map__panel.is-collapsed .tgp-panel__chevron {
  transform: rotate(180deg);
}

.tgp-panel__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* The list scrolls itself and stops there — it never chains a scroll into
     the page (or, on a phone, into the map behind the sheet). */
  overscroll-behavior: contain;
  touch-action: pan-y;
  padding-bottom: 8px;
}

.tgp-panel__grouphead {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 40px;
  padding: 8px 12px;
  border: 0;
  border-bottom: 1px solid var(--tgp-line);
  background: var(--tgp-surface); /* opaque: rows scroll under it */
  font-size: 13px;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}

.tgp-panel__grouphead:hover,
.tgp-panel__grouphead:focus-visible {
  background: var(--tgp-surface-alt);
}

.tgp-panel__zonedot {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.18);
}

.tgp-panel__grouplabel {
  flex: 0 0 auto;
}

.tgp-panel__groupmeta {
  min-width: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--tgp-ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tgp-panel__caret {
  margin-left: auto;
  padding-left: 6px;
  font-size: 9px;
  color: var(--tgp-ink-soft);
  transition: transform 0.15s ease;
}

.tgp-panel__group.is-collapsed .tgp-panel__caret {
  transform: rotate(-90deg);
}

.tgp-panel__group.is-collapsed .tgp-panel__rows {
  display: none;
}

.tgp-panel__rows {
  margin: 0;
  padding: 0;
  list-style: none;
}

.tgp-panel__row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 40px;
  padding: 5px 12px 5px 16px;
  border: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
}

.tgp-panel__row:hover,
.tgp-panel__row:focus-visible {
  background: var(--tgp-surface-alt);
}

.tgp-panel__row.is-active {
  background: rgba(30, 136, 229, 0.12);
}

/* Same dot language as the markers and the search results: solid = assigned,
   hollow ring = open. */
.tgp-panel__dot {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid currentColor;
  background: currentColor;
}

.tgp-panel__dot.is-open {
  background: var(--tgp-surface);
}

/* ---------------------------------------------------------- gap spaces --

   An organizer-designated empty pitch (`type: "gap"` on the feed). It is a real
   record — assignable, searchable, reachable — so nothing here changes what a
   gap can DO; it changes what a gap looks like, in the five places a space
   appears: the map circle, this row, the search row, the popup and the legend.

   THE RING SAYS WHAT KIND OF SPACE IT IS, THE FILL SAYS WHETHER ANYONE IS IN
   IT. So the dashed grey ring is on every gap, and an assigned gap still fills
   solid — which is why `.is-gap` only ever touches the border here and leaves
   `.is-open` to own the background. The two classes compose; neither overrides
   the other.

   Dashed rather than merely grey, because grey alone is one signal and this
   has to survive a greyscale print and a colour deficiency. The map marker
   carries a third (it is drawn smaller) — see markerStyle. */
.tgp-panel__dot.is-gap,
.tgp-search__dot.is-gap,
.tgp-legend__swatch.is-gap {
  border-style: dashed;
  /* 1.5px, not the 2px a solid ring uses: at 12–13px across, 2px dashes close
     up into a solid circle and the one signal that survives print is lost. */
  border-width: 1.5px;
}

/* An EMPTY gap: a wash rather than the plain white an empty bookable space
   gets, so the two are still apart when the ring is too small to show dashes.
   `.is-open` sets the white; this is the same 0.22 alpha the map circle's
   fillOpacity uses, so the row and the pin match. */
.tgp-panel__dot.is-gap.is-open,
.tgp-search__dot.is-gap.is-open {
  background: rgba(142, 148, 156, 0.22);
}

/* The legend swatch carries no `.is-open`, because the key is for the TYPE and
   not for one occupancy of it — an assigned gap is still a gap, and a key that
   showed the empty case only would be answering the wrong question. So it gets
   the wash unconditionally, which is the reading a dashed ring wants. */
.tgp-legend__swatch.is-gap {
  background: rgba(142, 148, 156, 0.22);
}

/* The row's words go quiet with it. Not hidden and not disabled — a gap is
   still pressable, still assignable, still the thing somebody may be looking
   for — just not competing with the bookable spaces either side of it. */
.tgp-panel__row.is-gap .tgp-panel__num,
.tgp-search__result.is-gap .tgp-search__num {
  color: var(--tgp-ink-soft);
  font-weight: 600;
}

/* "Gap space" where "Open" would have been. Upright, unlike `.is-open`'s
   italic: this is a designation the organizer made, not the absence of one.

   BOTH class names in the selector on purpose. `.is-gap` here only ever appears
   alongside `.is-open` (an assigned gap keeps this line for its vendor), and the
   italic rule it has to beat lives further down this file at equal specificity —
   which is a race the later rule wins. Two classes is (0,3,0) and settles it
   wherever either block ends up. */
.tgp-panel__vendor.is-open.is-gap,
.tgp-popup__vendor.is-open.is-gap {
  font-style: normal;
  color: var(--tgp-ink-soft);
}

/* The tag an ASSIGNED gap wears, inside the text block rather than as a fourth
   flex item on the row — the row already carries dot, text and needs pills, and
   a fourth is what starts eating vendor names at 375px. Only appears where the
   words cannot carry the fact themselves (an unassigned gap's line already
   reads "Gap space"), so it is never redundant with its own row. */
.tgp-panel__tag {
  display: inline-block;
  margin-right: 5px;
  padding: 0 4px;
  border: 1px dashed rgba(142, 148, 156, 0.9);
  border-radius: 4px;
  font-size: 9.5px;
  font-weight: 700;
  line-height: 14px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #6d737a;
  vertical-align: 1px;
}

.tgp-panel__text {
  min-width: 0;
  flex: 1 1 auto;
}

.tgp-panel__num {
  font-weight: 700;
  font-size: 13.5px;
}

.tgp-panel__vendor {
  display: block;
  font-size: 12.5px;
  color: var(--tgp-ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tgp-panel__vendor.is-open {
  font-style: italic;
}

/* Needs icons on a booth row — EDITOR ONLY; a public build never appends the
   node, so this rule matches nothing there. A third flex item of
   `.tgp-panel__row` at its natural width, which is what makes the vendor name
   the thing that ellipsises on a 375px phone rather than the icons the eye is
   scanning the column for. 2px gap is the same spacing the marker badges stack
   at, so a booth reads identically in the list and on the map. */
.tgp-panel__needs {
  display: flex;
  align-items: center;
  gap: 3px;
  flex: 0 0 auto;
  /* Breathing room from the vendor name's ellipsis, which otherwise ends right
     against the first disc. */
  margin-left: 8px;
}

/* Bigger than the 14px the map badge used to be, because the two are read at
   completely different distances: a map badge sits next to a booth you are
   already looking at, a sidebar disc is scanned down a 613-row column at arm's
   length. 21px is the largest disc that still clears the 51px row without
   touching its padding, so the row height budget is unchanged.

   Calmer ground, too: the white ring and drop shadow exist to hold a badge
   against a busy basemap, and on a flat row they are only noise. The border
   stays declared (transparent) so the disc keeps its counterpart's geometry. */
.tgp-panel__needs .tgp-need {
  width: 21px;
  height: 21px;
  border-color: transparent;
  box-shadow: none;
}

/* Percentages here resolve against the CONTENT box, which the disc's 1.5px
   border shrinks to 18px — so 66% was giving 12px, not 14. 78% lands the glyph
   at ~14px, which is the size that makes the flame read as a flame rather than
   as a red dot. The disc keeps its ring width so it still matches the popup
   chip beside it. */
.tgp-panel__needs .tgp-need svg {
  width: 78%;
  height: 78%;
}

.tgp-panel__note {
  margin: 0;
  padding: 14px 12px;
  font-size: 13.5px;
  color: var(--tgp-ink-soft);
}

.tgp-panel__note[hidden] {
  display: none;
}

/* ---- admin-only group (editor mode only) ---- */

/* The last group in the booth list, and deliberately not one of them: muted
   ground and a dashed top edge, the same "provisional / not public" motif the
   hidden markers and polygons carry on the map. */
.tgp-panel__group--admin {
  margin-top: 6px;
  border-top: 2px dashed var(--tgp-line);
  background: var(--tgp-surface-alt);
}

.tgp-admin__head {
  display: flex;
  align-items: stretch;
  gap: 4px;
  min-height: 40px;
}

/* The caret half. Takes the room the switch does not, so the label and count
   read as the group heading they are. */
.tgp-admin__headbtn {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 40px;
  padding: 8px 4px 8px 12px;
  border: 0;
  background: transparent;
  font-size: 13px;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}

.tgp-admin__headbtn:hover,
.tgp-admin__headbtn:focus-visible {
  background: var(--tgp-surface);
}

.tgp-admin__headlabel {
  flex: 0 0 auto;
  letter-spacing: 0.02em;
}

.tgp-admin__headmeta {
  min-width: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--tgp-ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tgp-panel__group--admin .tgp-panel__caret {
  margin-left: auto;
  padding-left: 6px;
}

/* The switch half. A real <label> around a real checkbox: the whole thing is
   the hit target and it stays keyboard- and screen-reader-correct without one
   ARIA attribute beyond the input's own name. */
.tgp-admin__switch {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  min-height: var(--tgp-tap);
  padding: 0 12px;
  margin: 0;
  font-size: 12.5px;
  color: var(--tgp-ink-soft);
  cursor: pointer;
  user-select: none;
}

.tgp-admin__checkbox {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--tgp-focus);
  cursor: pointer;
}

.tgp-admin__checkbox:disabled,
.tgp-admin__checkbox:disabled + .tgp-admin__switchtext {
  cursor: default;
  opacity: 0.6;
}

.tgp-admin__error {
  margin: 0;
  padding: 0 12px 8px;
  font-size: 12px;
  line-height: 1.4;
  color: #b3261e;
  overflow-wrap: anywhere;
}

.tgp-admin__error[hidden] {
  display: none;
}

.tgp-admin__rows {
  padding-bottom: 6px;
}

.tgp-admin__row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: var(--tgp-tap);
  padding: 5px 12px 5px 16px;
  border: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
}

.tgp-admin__row:hover,
.tgp-admin__row:focus-visible {
  background: var(--tgp-surface);
}

/* Dashed ring on the glyph disc, exactly like the marker on the map, so a row
   and the pin it flies to are recognisably the same object. The disc shape
   itself — round, white glyph — comes from the shared rule up in the facilities
   block, which this class is listed in. */
.tgp-admin__glyph {
  width: 20px;
  height: 20px;
  border: 1px dashed rgba(28, 28, 30, 0.45);
}

/* An area has no glyph; its map identity is a dashed outline in its type
   colour, so the row carries that instead. The fill is knocked back with
   opacity rather than a second colour value. */
.tgp-admin__swatch {
  flex: 0 0 auto;
  width: 18px;
  height: 14px;
  border: 1.5px dashed currentColor;
  border-radius: 3px;
  opacity: 0.55;
}

.tgp-admin__rowtext {
  min-width: 0;
}

.tgp-admin__name {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.tgp-admin__type {
  display: block;
  font-size: 12px;
  color: var(--tgp-ink-soft);
}

.tgp-admin__note {
  padding: 8px 12px 10px;
  font-size: 12.5px;
  color: var(--tgp-ink-soft);
}

/* Type sub-header inside the admin group. A plain label, not a control: these
   categories never collapse and a focus stop between every one of them would
   cost keyboard users more than the grouping saves them. */
.tgp-admin__cat {
  display: flex;
  align-items: baseline;
  gap: 5px;
  padding: 8px 12px 3px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tgp-ink-soft);
}

.tgp-admin__catcount {
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}

/* ------------------------------------------------------------ fullscreen - */

/* A fixed overlay, never the Fullscreen API — iPhone Safari does not implement
   it (see festival-map.js). Geometry carries !important because a host theme
   sizing or positioning this container would otherwise win: these values are
   static, so forcing them costs nothing. `height` is deliberately NOT forced —
   it is written inline from JS on every visual-viewport change, and an
   !important rule here would outrank that. */
.tgp-map.is-fullscreen {
  position: fixed !important;
  /* Deliberately not !important — see the z-index note below. */
  top: 0;
  right: 0 !important;
  bottom: 0 !important;
  left: 0 !important;
  width: auto !important;
  max-width: none !important;
  min-width: 0 !important;
  /* dvh tracks the URL bar; the vh line is the fallback for browsers without
     it, and the inline pixel height from JS beats both where available. */
  height: 100vh;
  height: 100dvh;
  min-height: 0 !important;
  max-height: none !important;
  margin: 0 !important;
  border-radius: 0 !important;
  /* Above the sticky headers themes park in the 100–10000 range, and
     deliberately BELOW WordPress's #wpadminbar (position:fixed, z-index 99999).
     Fullscreen does not cover the admin bar: it starts underneath it, and JS
     writes the matching `top`/`height` inline. John's call — a logged-in editor
     wants the bar reachable while the map is open.

     History, because the number has moved twice. beta.1 shipped 99999, tied
     with the bar and lost the tie (later-in-DOM wins, and the bar is printed
     after the content), slicing the top off the overlay. beta.2 raised it to
     100000 to cover the bar; John reported the overlay was STILL underneath on
     his site. The likeliest cause is the `transform` ancestor trap documented
     in the README: a theme wrapper with a transform/filter/will-change becomes
     the containing block for our position:fixed, which also traps our z-index
     inside that ancestor's stacking context — at which point no number here can
     beat a bar that is a direct child of <body>. That is unverifiable from the
     harness (no theme), and it stops mattering under the current approach:
     sitting below the bar is now the intent, not a fight to be won.

     `top` is NOT forced with !important — JS overrides it inline (with
     !important) when an admin bar is present, and the plain `top: 0` here is the
     public-visitor case, which is the overwhelming majority. Same reasoning as
     `height` above. */
  z-index: 99990 !important;
}

.tgp-map__fs.leaflet-bar {
  border: 0;
  box-shadow: none;
}

.tgp-fs__button {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Full touch target on every form factor, not just phones — this control is
     the one thing people hunt for with a thumb while walking. */
  width: var(--tgp-tap);
  height: var(--tgp-tap);
  padding: 0;
  border: 1px solid var(--tgp-line);
  border-radius: 10px;
  background: var(--tgp-surface);
  box-shadow: var(--tgp-shadow);
  color: var(--tgp-ink);
  cursor: pointer;
}

.tgp-fs__button svg {
  width: 19px;
  height: 19px;
  display: block;
}

.tgp-fs__button:hover,
.tgp-fs__button:focus-visible {
  background: var(--tgp-surface-alt);
}

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

/* A flex row so the Editing badge can sit beside the field and size itself.
   An absolutely-positioned badge would need the field's inset hard-coded to
   match its rendered width, which is a magic number that breaks the first time
   the font metrics shift. */
.tgp-map__search {
  position: absolute;
  z-index: 1000; /* above Leaflet panes + controls */
  top: 10px;
  left: 10px;
  right: 10px;
  max-width: 420px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 6px;
}

/* Reserve the top-right corner for the full screen button. On a phone the
   search bar spans the full width, so without this it sits straight on top of
   the control. Applied at every width so the field's position doesn't jump
   across the breakpoint. */
.tgp-map--has-fs .tgp-map__search {
  right: 64px;
}

/* -------------------------------------------------------------- editor -- */

/* Lives inside .tgp-map__search as the first flex item — no inset to keep in
   sync with its own rendered width. */
.tgp-map__editing {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  height: var(--tgp-tap);
  padding: 0 11px;
  border: 1px solid #f0c36d;
  border-radius: var(--tgp-radius);
  background: #fff8e6;
  box-shadow: var(--tgp-shadow);
  color: #8a5b00;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none; /* a label, not a control */
}

.tgp-editing__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #e5a000;
}

/* Action row inside a booth popup. */
.tgp-editor__actions {
  margin-top: 9px;
  padding-top: 9px;
  border-top: 1px solid var(--tgp-line);
}

.tgp-editor__row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.tgp-editor__btn {
  flex: 0 1 auto;
  min-height: 34px;
  padding: 0 12px;
  border: 1px solid var(--tgp-line);
  border-radius: 8px;
  background: var(--tgp-surface);
  color: var(--tgp-ink);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  /* Vendor names can be long; wrap rather than blow the popup out sideways. */
  white-space: normal;
  text-align: left;
}

.tgp-editor__btn.is-primary {
  border-color: var(--tgp-focus);
  background: var(--tgp-focus);
  color: #fff;
}

.tgp-editor__btn.is-danger {
  border-color: #e0b4b1;
  background: #fff;
  color: #b3261e;
}

.tgp-editor__btn:disabled {
  opacity: 0.55;
  cursor: default;
}

.tgp-editor__btn:not(:disabled):hover,
.tgp-editor__btn:not(:disabled):focus-visible {
  filter: brightness(0.96);
}

.tgp-editor__confirm {
  margin: 0 0 8px;
  font-size: 13px;
  line-height: 1.45;
}

.tgp-editor__error {
  margin: 8px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: #b3261e;
  overflow-wrap: anywhere;
}

/* Stands in for the assign/unassign button while reposition mode is on. Muted
   and italic so it reads as an explanation of an absence rather than a control
   that has stopped working. */
.tgp-editor__paused {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.45;
  font-style: italic;
  color: var(--tgp-ink-soft);
}

/* ---- reposition mode (editor only) ---- */

/* A third full-width row of .tgp-map__search, exactly like the results
   dropdown — no inset to keep in sync with a bar whose height changes the
   moment the Editing badge wraps. Same warm palette as that badge, because
   both say the same thing: this view can write. */
.tgp-map__reposnote {
  flex: 1 0 100%;
  padding: 7px 11px;
  border: 1px solid #f0c36d;
  border-radius: var(--tgp-radius);
  background: #fff8e6;
  box-shadow: var(--tgp-shadow);
  color: #8a5b00;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  pointer-events: none; /* a label, not a control */
}

/* The drag target. 44px because a booth circle is 20px across at best, and
   this widget holds every other tap target to 44 — see the JS. Deliberately
   faint: 80 of these are on screen at once and a solid disc each would bury
   the map they are meant to help you read. */
.tgp-repos__handle {
  border: 1.5px dashed rgba(30, 136, 229, 0.55);
  border-radius: 50%;
  background: rgba(30, 136, 229, 0.09);
  cursor: grab;
  cursor: -webkit-grab;
  /* The browser must not claim the gesture for a scroll before Leaflet's
     Draggable sees it. Without this a touch-drag on a phone pans the page. */
  touch-action: none;
}

.tgp-repos__handle:hover {
  border-color: rgba(30, 136, 229, 0.9);
  background: rgba(30, 136, 229, 0.18);
}

/* The facility handle. Same size, same faintness, a different hue — in this
   mode a facility pin and the booth it happens to sit near are both grabbable,
   and picking up the first aid tent when you meant the booth under it is a
   mistake the UI should show before the drop rather than after. Teal because it
   is the one family not already spoken for here: blue is the booth handle and
   the focus ring, grey is a gap space. */
.tgp-repos__handle--facility {
  border-color: rgba(0, 121, 107, 0.6);
  background: rgba(0, 121, 107, 0.1);
}

.tgp-repos__handle--facility:hover {
  border-color: rgba(0, 121, 107, 0.95);
  background: rgba(0, 121, 107, 0.2);
}

/* Leaflet puts this class on the icon it is dragging. */
.tgp-repos__handle.leaflet-drag-target,
.tgp-map.is-dragging-booth .tgp-repos__handle {
  cursor: grabbing;
  cursor: -webkit-grabbing;
}

/* The confirm chip. Anchored to the dropped pin from JS (left/top in container
   pixels); the translate is what makes `left` mean "centre", so the maths in
   positionMoveChip only has to clamp one number. */
.tgp-map__movechip {
  position: absolute;
  /* Above the Leaflet panes and the popup pane (700), below the search bar
     (1000) — the chip is transient, the search bar is permanent furniture. */
  z-index: 900;
  transform: translateX(-50%);
  min-width: 168px;
  max-width: 260px;
  padding: 9px 11px;
  border: 1px solid var(--tgp-line);
  border-radius: var(--tgp-radius);
  background: var(--tgp-surface);
  box-shadow: var(--tgp-shadow);
  color: var(--tgp-ink);
}

.tgp-map__movechip[hidden] {
  display: none;
}

.tgp-map__movechip.is-error {
  border-color: #e0b4b1;
}

.tgp-map__movechip.is-busy {
  opacity: 0.7;
}

.tgp-movechip__text {
  margin: 0 0 8px;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.tgp-movechip__row {
  display: flex;
  gap: 6px;
}

.tgp-movechip__btn {
  flex: 1 1 auto;
  min-height: 34px;
  padding: 0 12px;
  border: 1px solid var(--tgp-line);
  border-radius: 8px;
  background: var(--tgp-surface);
  color: var(--tgp-ink);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.tgp-movechip__btn.is-primary {
  border-color: var(--tgp-focus);
  background: var(--tgp-focus);
  color: #fff;
}

.tgp-movechip__btn[hidden] {
  display: none;
}

.tgp-movechip__btn:disabled {
  opacity: 0.55;
  cursor: default;
}

.tgp-movechip__btn:not(:disabled):hover,
.tgp-movechip__btn:not(:disabled):focus-visible {
  filter: brightness(0.96);
}

.tgp-movechip__error {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.4;
  color: #b3261e;
  overflow-wrap: anywhere;
}

.tgp-movechip__error[hidden] {
  display: none;
}

/* The control's on-state, matching the pressed look the rest of the bar has
   nothing equivalent for — full screen swaps its icon instead. */
.tgp-fs__repos[aria-pressed='true'],
.tgp-fs__add[aria-pressed='true'] {
  border-color: #f0c36d;
  background: #fff8e6;
  color: #8a5b00;
}

/* ---- duplicate booth numbers (editor only) ---- */

/* A fourth full-width row of .tgp-map__search, mounted exactly like the
   reposition banner. Red rather than that banner's amber, and deliberately so:
   the amber one reports a MODE the editor turned on, this one reports data that
   is wrong. Unlike the reposition banner it is interactive, so it must NOT
   inherit that rule's pointer-events:none. */
.tgp-map__dupwarn {
  flex: 1 0 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 7px 11px;
  border: 1px solid #e0b4b1;
  border-radius: var(--tgp-radius);
  background: #fdf4f3;
  box-shadow: var(--tgp-shadow);
  color: #8a2622;
}

.tgp-map__dupwarn[hidden] {
  display: none;
}

.tgp-dupwarn__text {
  flex: 1 1 100%;
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.tgp-dupwarn__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.tgp-dupwarn__num {
  min-height: 28px;
  padding: 0 10px;
  border: 1px solid #d8a29e;
  border-radius: 7px;
  background: #fff;
  color: #8a2622;
  font-size: 12px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
}

.tgp-dupwarn__num:hover,
.tgp-dupwarn__num:focus-visible {
  background: #fbe9e7;
}

.tgp-dupwarn__more {
  font-size: 11.5px;
  font-weight: 600;
  opacity: 0.8;
}

.tgp-dupwarn__dismiss {
  min-height: 28px;
  margin-left: auto;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: transparent;
  color: #8a2622;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.tgp-dupwarn__dismiss:hover,
.tgp-dupwarn__dismiss:focus-visible {
  border-color: #d8a29e;
  background: #fff;
}

/* ---- add-booth mode ---- */

/* The cursor is the mode's only always-visible signal once the banner has been
   read, so it goes on the canvas rather than on any particular layer. */
.tgp-map--adding .leaflet-container,
.tgp-map--adding .leaflet-interactive {
  cursor: crosshair;
}

/* In add mode a tap means "put a booth here" — everywhere, without exception.

   Left alone, a tap that landed on a booth circle would open its assign popup,
   and one that landed on a zone polygon would open the zone's; both would
   swallow the tap and neither would place anything. Making the drawing panes
   transparent to the pointer means every tap reaches the map itself and fires
   the one `click` the mode listens for — deterministic, rather than relying on
   Leaflet re-firing a layer click as a map click.

   The popup pane and the controls are deliberately NOT listed: an already-open
   popup must stay closable, and the button that turns this mode off has to keep
   working. */
.tgp-map--adding .leaflet-overlay-pane,
.tgp-map--adding .leaflet-marker-pane,
.tgp-map--adding .leaflet-tgp-zones-pane,
.tgp-map--adding .leaflet-tgp-facilities-pane,
.tgp-map--adding .leaflet-tgp-needs-pane {
  pointer-events: none;
}

/* ---- the edit chip ---- */

/* Same anchoring machinery and the same visual family as the move chip — it is
   the same kind of object (a small card pinned to the thing it is about) and
   the two are never on screen together. */
.tgp-map__editchip {
  position: absolute;
  /* Above the Leaflet panes and the popup pane (700), below the search bar
     (1000), exactly like the move chip. */
  z-index: 900;
  transform: translateX(-50%);
  min-width: 208px;
  max-width: 272px;
  padding: 10px 11px;
  border: 1px solid var(--tgp-line);
  border-radius: var(--tgp-radius);
  background: var(--tgp-surface);
  box-shadow: var(--tgp-shadow);
  color: var(--tgp-ink);
}

.tgp-map__editchip[hidden] {
  display: none;
}

.tgp-map__editchip.is-error {
  border-color: #e0b4b1;
}

.tgp-map__editchip.is-busy {
  opacity: 0.7;
}

.tgp-editchip__title {
  margin: 0 0 8px;
  font-size: 12.5px;
  font-weight: 700;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.tgp-editchip__fields {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.tgp-editchip__field {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.tgp-editchip__field[hidden] {
  display: none;
}

.tgp-editchip__label {
  font-size: 11px;
  font-weight: 600;
  color: var(--tgp-ink-soft, #5a5a5a);
}

.tgp-editchip__input {
  width: 100%;
  /* Border-box, or the 100% width above plus the padding would overflow the
     card on every browser. */
  box-sizing: border-box;
  min-height: 34px;
  padding: 0 8px;
  border: 1px solid var(--tgp-line);
  border-radius: 8px;
  background: var(--tgp-surface);
  color: var(--tgp-ink);
  /* 16px: anything smaller makes iOS Safari zoom the whole page on focus, which
     throws the map's framing away mid-edit. */
  font-size: 16px;
  font-family: inherit;
}

.tgp-editchip__input:disabled {
  opacity: 0.55;
}

/* The zone-drift warning. Advisory colouring — the same warm palette the
   Editing badge and the mode banner use — and deliberately NOT the error red:
   nothing here is wrong yet, and a red box would read as a blocker. */
.tgp-editchip__drift {
  margin-top: 8px;
  padding: 7px 8px;
  border: 1px solid #f0c36d;
  border-radius: 8px;
  background: #fff8e6;
  color: #8a5b00;
}

.tgp-editchip__drift[hidden] {
  display: none;
}

.tgp-editchip__drifttext {
  margin: 0 0 6px;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

.tgp-editchip__driftrow {
  display: flex;
  gap: 6px;
}

.tgp-editchip__driftbtn {
  flex: 1 1 auto;
  min-height: 30px;
  padding: 0 8px;
  border: 1px solid #e0b96a;
  border-radius: 7px;
  background: #fffdf7;
  color: #8a5b00;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  overflow-wrap: anywhere;
}

.tgp-editchip__driftbtn.is-ghost {
  flex: 0 0 auto;
  background: transparent;
}

.tgp-editchip__driftbtn:not(:disabled):hover,
.tgp-editchip__driftbtn:not(:disabled):focus-visible {
  filter: brightness(0.97);
}

.tgp-editchip__row {
  display: flex;
  gap: 6px;
  margin-top: 9px;
}

.tgp-editchip__btn {
  flex: 1 1 auto;
  min-height: 34px;
  padding: 0 12px;
  border: 1px solid var(--tgp-line);
  border-radius: 8px;
  background: var(--tgp-surface);
  color: var(--tgp-ink);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.tgp-editchip__btn.is-primary {
  border-color: var(--tgp-focus);
  background: var(--tgp-focus);
  color: #fff;
}

.tgp-editchip__btn:disabled {
  opacity: 0.55;
  cursor: default;
}

.tgp-editchip__btn:not(:disabled):hover,
.tgp-editchip__btn:not(:disabled):focus-visible {
  filter: brightness(0.96);
}

.tgp-editchip__error {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.4;
  color: #b3261e;
  overflow-wrap: anywhere;
}

.tgp-editchip__error[hidden] {
  display: none;
}

/* The inline pencil on a popup heading. Sized to the 28px the popup's own type
   scale allows rather than the 44px the map controls use: it sits inside a line
   of text, and a 44px target there would push the heading apart. */
.tgp-popup__edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin-left: 6px;
  padding: 0;
  border: 1px solid var(--tgp-line);
  border-radius: 7px;
  background: var(--tgp-surface);
  color: var(--tgp-ink);
  vertical-align: middle;
  cursor: pointer;
}

.tgp-popup__edit svg {
  width: 15px;
  height: 15px;
  display: block;
}

.tgp-popup__edit:disabled {
  opacity: 0.5;
  cursor: default;
}

.tgp-popup__edit:not(:disabled):hover,
.tgp-popup__edit:not(:disabled):focus-visible {
  background: var(--tgp-surface-alt);
}

/* ---- vendor picker ---- */

.tgp-map__picker {
  position: absolute;
  inset: 0;
  /* Above the booth list (1200) and the loading overlay (1100): it is modal. */
  z-index: 1400;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(28, 28, 30, 0.45);
}

.tgp-map__picker[hidden] {
  display: none;
}

.tgp-picker__panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 380px;
  max-height: 100%;
  min-height: 0;
  background: var(--tgp-surface);
  border-radius: var(--tgp-radius);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
  overflow: hidden;
}

.tgp-picker__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 8px 8px 14px;
  border-bottom: 1px solid var(--tgp-line);
}

.tgp-picker__title {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tgp-picker__close {
  flex: 0 0 auto;
  width: var(--tgp-tap);
  height: var(--tgp-tap);
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--tgp-ink-soft);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.tgp-picker__close:hover,
.tgp-picker__close:focus-visible {
  background: var(--tgp-surface-alt);
  color: var(--tgp-ink);
}

.tgp-picker__field {
  padding: 8px 10px;
  border-bottom: 1px solid var(--tgp-line);
}

.tgp-picker__input {
  width: 100%;
  height: var(--tgp-tap);
  padding: 0 10px;
  border: 1px solid var(--tgp-line);
  border-radius: 10px;
  background: var(--tgp-surface);
  color: var(--tgp-ink);
  /* 16px: anything smaller and iOS zooms the page on focus. */
  font-size: 16px;
  -webkit-appearance: none;
  appearance: none;
}

.tgp-picker__input:focus {
  outline: 0;
  border-color: var(--tgp-focus);
  box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.25);
}

.tgp-picker__note {
  margin: 0;
  padding: 10px 14px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--tgp-ink-soft);
  overflow-wrap: anywhere;
}

.tgp-picker__note[hidden] {
  display: none;
}

.tgp-picker__note.is-error {
  color: #b3261e;
}

.tgp-picker__list {
  flex: 1 1 auto;
  min-height: 0;
  margin: 0;
  /* No top padding: a sticky header pins to the content edge, and 4px of
     padding above it would let rows scroll through the gap. */
  padding: 0 0 4px;
  list-style: none;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

.tgp-picker__list[hidden] {
  display: none;
}

/* The section wrapper is load-bearing, not cosmetic: it is the containing block
   that clamps the sticky header inside it, so each header un-pins as its own
   rows end instead of two headers pinning at top:0 together. Same arrangement
   as .tgp-panel__group in the sidebar. */
.tgp-picker__section {
  display: block;
}

.tgp-picker__rows {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Sticky, not merely labelled: with a few hundred assigned vendors in the
   scroll you lose track of which half you are in, and the header is the only
   thing that says. Matches the sidebar group-header idiom (sticky, opaque,
   top:0) minus the collapse affordance — these sections never collapse (that
   would hide matches mid-search), so this is a plain div rather than a button
   and costs keyboard users no extra focus stop. */
.tgp-picker__sechead {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: 5px;
  padding: 7px 14px 5px;
  /* Opaque: rows pass behind this. */
  background: var(--tgp-surface);
  border-bottom: 1px solid var(--tgp-line);
}

.tgp-picker__seclabel {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tgp-ink-soft);
}

.tgp-picker__seccount {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--tgp-ink-soft);
}

.tgp-picker__row {
  display: block;
  width: 100%;
  min-height: var(--tgp-tap);
  padding: 7px 14px;
  border: 0;
  background: transparent;
  text-align: left;
  cursor: pointer;
}

.tgp-picker__row:hover,
.tgp-picker__row:focus-visible {
  background: var(--tgp-surface-alt);
}

.tgp-picker__name {
  display: block;
  font-size: 14px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.tgp-picker__sub {
  display: block;
  margin-top: 1px;
  font-size: 12.5px;
  color: var(--tgp-ink-soft);
}

/* An application whose status disqualifies it, which reaches this list ONLY by
   already holding a booth. Visible so the picker and the map agree about what
   is assigned; locked so it cannot be handed anything new.

   Dimmed rather than struck through or hidden: the row still has a job, which
   is to let somebody tell two identically-named applications apart. The hover
   and focus highlights are cancelled so nothing about it invites a click. */
.tgp-picker__row.is-excluded {
  cursor: default;
  opacity: 0.7;
}

.tgp-picker__row.is-excluded:hover,
.tgp-picker__row.is-excluded:focus-visible {
  background: transparent;
}

/* The status, verbatim from Airtable. Warning-toned rather than neutral,
   because "Duplicate Signup" is the answer to a question somebody is actively
   confused by — it should catch the eye that is scanning for it. */
.tgp-picker__status {
  display: inline-block;
  margin-top: 3px;
  padding: 1px 7px;
  border: 1px solid #e0b96a;
  border-radius: 999px;
  background: #fff8e6;
  color: #8a5b00;
  font-size: 11px;
  font-weight: 700;
  line-height: 1.5;
  /* Airtable option names are organiser-authored and can be long. */
  overflow-wrap: anywhere;
}

/* The same tag on a row that IS clickable — a priority-waitlist vendor being
   offered for assignment. It must not borrow the excluded row's dead look, so
   it is the map's own accent rather than the warning amber, and it sits on a
   row with full opacity and a live hover. Amber says "careful, this one is
   not available"; this says "note what this is" while staying pressable. */
.tgp-picker__status.is-tagged {
  border-color: var(--tgp-focus);
  background: #eef3fd;
  color: var(--tgp-focus);
}

.tgp-picker__empty {
  padding: 14px;
  font-size: 13px;
  color: var(--tgp-ink-soft);
}

.tgp-picker__confirm {
  padding: 14px;
}

.tgp-picker__confirm[hidden] {
  display: none;
}

/* Nothing in the picker is clickable while a write is in flight. The JS
   guards as well — this is the part the editor can see. */
.tgp-map__picker.is-busy .tgp-picker__list,
.tgp-map__picker.is-busy .tgp-picker__confirm,
.tgp-map__picker.is-busy .tgp-picker__close {
  pointer-events: none;
  opacity: 0.55;
}

.tgp-search__field {
  position: relative;
  /* basis 0 so the field never forces a wrap on its own content width; the
     min-width is the point at which it gives up and takes its own row rather
     than shrinking into something you cannot type in. */
  flex: 1 1 0;
  min-width: 140px;
  display: flex;
  align-items: center;
  background: var(--tgp-surface);
  border: 1px solid var(--tgp-line);
  border-radius: var(--tgp-radius);
  box-shadow: var(--tgp-shadow);
}

.tgp-search__icon {
  flex: 0 0 auto;
  width: 38px;
  height: var(--tgp-tap);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--tgp-ink-soft);
  pointer-events: none;
}

.tgp-search__icon svg {
  width: 17px;
  height: 17px;
  display: block;
}

.tgp-search__input {
  flex: 1 1 auto;
  min-width: 0;
  height: var(--tgp-tap);
  padding: 0 4px 0 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--tgp-ink);
  /* 16px minimum: anything smaller makes iOS Safari zoom the page on focus,
     which throws the map viewport off and hides the field behind the keyboard. */
  font-size: 16px;
  line-height: var(--tgp-tap);
  border-radius: var(--tgp-radius);
  -webkit-appearance: none;
  appearance: none;
}

.tgp-search__input::placeholder {
  color: var(--tgp-ink-soft);
  opacity: 1;
}

.tgp-search__input::-webkit-search-cancel-button {
  display: none;
}

.tgp-search__field:focus-within {
  border-color: var(--tgp-focus);
  box-shadow: var(--tgp-shadow), 0 0 0 3px rgba(30, 136, 229, 0.25);
}

.tgp-search__clear {
  flex: 0 0 auto;
  width: var(--tgp-tap);
  height: var(--tgp-tap);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--tgp-ink-soft);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  border-radius: var(--tgp-radius);
}

.tgp-search__clear:hover,
.tgp-search__clear:focus-visible {
  color: var(--tgp-ink);
}

.tgp-map__search.is-dirty .tgp-search__clear {
  display: flex;
}

.tgp-search__results {
  display: none;
  /* Always its own row under the field + badge. */
  flex: 1 0 100%;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--tgp-surface);
  border: 1px solid var(--tgp-line);
  border-radius: var(--tgp-radius);
  box-shadow: var(--tgp-shadow);
  /* Keeps the list inside the map on short phone viewports; the field itself
     stays pinned at the top so the on-screen keyboard never covers it. */
  max-height: min(46vh, 320px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

.tgp-map__search.is-open .tgp-search__results {
  display: block;
}

.tgp-search__result {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: var(--tgp-tap);
  padding: 6px 10px;
  border-radius: 8px;
  cursor: pointer;
}

.tgp-search__result[aria-selected="true"],
.tgp-search__result:hover {
  background: var(--tgp-surface-alt);
}

.tgp-search__dot {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid currentColor;
  background: currentColor;
}

.tgp-search__dot.is-open {
  background: var(--tgp-surface);
}

.tgp-search__text {
  min-width: 0;
  flex: 1 1 auto;
}

.tgp-search__num {
  font-weight: 700;
  font-size: 14px;
}

.tgp-search__vendor {
  display: block;
  font-size: 13px;
  color: var(--tgp-ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tgp-search__empty {
  padding: 12px 10px;
  font-size: 14px;
  color: var(--tgp-ink-soft);
}

/* ---------------------------------------------------------------- legend - */

.tgp-map__legend {
  position: absolute;
  z-index: 900;
  left: 10px;
  bottom: 10px;
  max-width: calc(100% - 20px);
  background: var(--tgp-surface);
  border: 1px solid var(--tgp-line);
  border-radius: var(--tgp-radius);
  box-shadow: var(--tgp-shadow);
  overflow: hidden;
}

.tgp-legend__toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 38px;
  padding: 8px 12px;
  border: 0;
  background: transparent;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
}

.tgp-legend__caret {
  margin-left: auto;
  font-size: 10px;
  color: var(--tgp-ink-soft);
  transition: transform 0.15s ease;
}

.tgp-map__legend.is-collapsed .tgp-legend__caret {
  transform: rotate(-90deg);
}

.tgp-legend__body {
  padding: 0 12px 10px;
}

.tgp-map__legend.is-collapsed .tgp-legend__body {
  display: none;
}

.tgp-legend__group {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin: 0 0 8px;
  padding: 0;
  list-style: none;
}

.tgp-legend__group:last-child {
  margin-bottom: 0;
}

.tgp-legend__label {
  margin: 0 0 4px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--tgp-ink-soft);
}

.tgp-legend__item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  line-height: 1.4;
}

/* Booth-needs toggles (editor only). They sit directly under the Zones, Spaces
   and Facilities keys, so they share those rows' rhythm exactly — same row gap,
   same natural row height, same icon size. A key whose last section is drawn a
   size down reads as a different, lesser list rather than one more entry in the
   same key, which is what John saw. The control-ness is carried by the
   checkbox, not by extra height. */
.tgp-legend__group--toggles {
  gap: 4px 12px;
}

.tgp-legend__toggleitem {
  display: flex;
  align-items: center;
  gap: 6px;
  /* The whole label is the hit area, so the row responds edge to edge, not just
     the 15px box — on desktop that is plenty at pointer precision. The phone
     floor lives in the <=767px block, which is the only place a 44px target is
     worth spending three legend rows on. */
  cursor: pointer;
  user-select: none;
}

.tgp-legend__checkbox {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  margin: 0;
  accent-color: var(--tgp-focus);
  cursor: pointer;
}

/* The same disc the sidebar rows carry, so the switch is visibly attached to
   the thing it switches — and the same SIZE as the facility disc two rows up
   (.tgp-legend__glyph, 17px with a 68% glyph), so the four sections read as one
   key rather than three keys and a footnote.

   TWO traps are defused here, both of which this rule previously fell into.

   1. `.tgp-need` is declared LATER in this file at equal specificity, so a
      single-class `.tgp-legend__needdisc` selector lost every property it
      shared with it — width, height, border and shadow all reverted to the
      on-map mini-disc. What actually rendered was a 14px disc wearing a 1.5px
      WHITE ring and a drop shadow: 11px of visible colour beside a 17px
      facility disc. The doubled `.tgp-need.tgp-legend__needdisc` selector wins
      on specificity and is immune to where in the file either rule sits.

   2. Percentages resolve against the CONTENT box, which a border shrinks first
      (see the sidebar-disc note in README). `border: 0` — not merely a
      transparent colour — is what makes 68% here mean the same 11.56px it means
      on the facility disc. Leave the 1.5px ring in and the same 68% draws a
      10.2px glyph instead. */
.tgp-need.tgp-legend__needdisc {
  width: 17px;
  height: 17px;
  border: 0;
  box-shadow: none;
}

.tgp-need.tgp-legend__needdisc svg {
  width: 68%;
  height: 68%;
}

/* An unchecked need reads as "off" at a glance rather than only by the tick. */
.tgp-legend__toggleitem:has(.tgp-legend__checkbox:not(:checked)) .tgp-legend__needdisc,
.tgp-legend__toggleitem:has(.tgp-legend__checkbox:not(:checked)) .tgp-legend__togglelabel {
  opacity: 0.45;
}

.tgp-legend__checkbox:focus-visible {
  outline: 2px solid var(--tgp-focus);
  outline-offset: 2px;
}

.tgp-legend__swatch {
  flex: 0 0 auto;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid currentColor;
  background: currentColor;
}

.tgp-legend__swatch.is-open {
  background: var(--tgp-surface);
}

.tgp-legend__swatch.is-zone {
  border-color: rgba(0, 0, 0, 0.18);
}

/* The legend's "Planning" checkbox lived here. The control moved into the booth
   list as its own group — see .tgp-panel__group--admin below. */

/* ---------------------------------------------------------- facilities -- */

/* Glyph discs. One shared shape at four sizes: the map marker, the legend key,
   the popup and the admin sidebar row. Monochrome glyph on a solid disc — the
   disc colour is set inline per type, the glyph is always white, so a new type
   needs one entry in FACILITY_TYPES and nothing here. A class that wants a disc
   must be listed below; leave one out and it renders as a dark glyph on a
   coloured SQUARE, which is exactly how the admin row first shipped. */
.tgp-glyph-disc,
.tgp-facility__disc,
.tgp-legend__glyph,
.tgp-popup__glyph,
.tgp-admin__glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border-radius: 50%;
  color: #ffffff;
}

.tgp-glyph {
  display: block;
  width: 68%;
  height: 68%;
  fill: currentColor;
}

/* The marker itself. divIcon's own box is transparent — Leaflet gives it no
   background, and the theme-proofing above stops a WP theme adding one. */
.tgp-map .tgp-facility {
  background: none;
  border: 0;
}

.tgp-facility__disc {
  width: 28px;
  height: 28px;
  border: 2px solid #ffffff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}

.tgp-legend__glyph {
  width: 17px;
  height: 17px;
}

/* A hidden facility: the same glyph, faded, inside a dashed ring. Two signals
   rather than one, because opacity alone is easy to miss on a busy basemap and
   the ring survives colour-blindness. */
.tgp-map .tgp-facility--admin {
  opacity: 0.55;
}

.tgp-facility--admin .tgp-facility__disc {
  border-style: dashed;
  box-shadow: 0 0 0 2px rgba(28, 28, 30, 0.35);
}

/* ---- booth needs badges (editor mode only) ---- */

/* The pane is what guarantees a badge can never steal a booth's tap. JS sets
   pointer-events:none on it when it creates the pane; this is the same rule
   written where a reader looking at the badge will find it, and it also covers
   the icon element Leaflet gives its own box. Z-order is tap order here, and
   the whole feature is decoration on top of the one interaction that matters. */
.tgp-map .leaflet-tgp-needs-pane,
.tgp-map .tgp-needs {
  pointer-events: none;
  background: none;
  border: 0;
}

.tgp-map .tgp-needs {
  display: flex;
  align-items: center;
  gap: 2px;
  white-space: nowrap;
}

/* ---- needs pill (on-map, editor only) ---- */

/* ONE capsule per booth holding one to three icons, not a stack of discs.
   Overlapping mini-discs beside a 6px marker were unreadable and ambiguous
   about which booth owned them; a bordered white capsule reads as a single
   label attached to a single booth.

   White ground with a thin dark border is also the one combination that
   survives a mono laser unchanged, which is why print needs no separate
   treatment beyond keeping the border. Sized inline from JS (padding, gap and
   glyph box scale with zoom); everything constant lives here. */
.tgp-needpill {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  background: #ffffff;
  border: 1px solid #24242a;
  border-radius: 999px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  line-height: 0;
  white-space: nowrap;
}

.tgp-needpill__glyph {
  display: inline-block;
  flex: 0 0 auto;
}

/* The same capsule, sitting in a legend row instead of on the map. Only ever
   built for a forced sheet (the fire map) — see renderNeedsLegend. */
.tgp-legend__needpill {
  padding: 3px 5px;
  margin-right: 2px;
  vertical-align: middle;
}

/* `currentColor` is set per glyph from NEED_BADGES.pillColor — coloured icon on
   white, the inverse of the disc treatment the popup and sidebar keep. */
.tgp-needpill__glyph svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: currentColor;
}

/* The pane already carries pointer-events: none, but a pill must never take a
   tap meant for its booth even if that pane rule is ever relaxed. */
.tgp-needs,
.tgp-needpill {
  pointer-events: none;
}

/* Mini disc, half the facility marker's size: legible beside a 10px booth
   circle without competing with it. Still the popup chip and the sidebar row
   icon — only the on-map badge became a pill. */
.tgp-need {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 1.5px solid #ffffff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  color: #ffffff;
}

.tgp-need svg {
  display: block;
  width: 66%;
  height: 66%;
  fill: currentColor;
}

/* Hidden areas: the dash pattern is set on the path from JS (dashArray, so it
   scales with the stroke); this only softens the fill edge. */
.tgp-map .tgp-area--admin {
  opacity: 0.9;
}

.tgp-popup__glyph {
  width: 16px;
  height: 16px;
}

/* Named zone polygons answer taps and say so; unnamed ones are inert and get
   no cursor, because there is nothing behind the click. */
.tgp-map .leaflet-interactive.tgp-zone.is-named {
  cursor: pointer;
}

/* ------------------------------------------------------- loading / error - */

.tgp-map__status {
  position: absolute;
  inset: 0;
  z-index: 1100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  text-align: center;
  background: rgba(244, 244, 246, 0.94);
}

.tgp-map__status[hidden] {
  display: none;
}

.tgp-status__inner {
  max-width: 330px;
}

.tgp-status__spinner {
  width: 26px;
  height: 26px;
  margin: 0 auto 12px;
  border: 3px solid var(--tgp-line);
  border-top-color: var(--tgp-focus);
  border-radius: 50%;
  animation: tgp-spin 0.8s linear infinite;
}

.tgp-map__status.is-error .tgp-status__spinner {
  display: none;
}

.tgp-status__title {
  margin: 0 0 6px;
  font-size: 15px;
  font-weight: 700;
}

.tgp-status__detail {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--tgp-ink-soft);
  overflow-wrap: anywhere;
}

.tgp-status__retry {
  display: none;
  min-height: var(--tgp-tap);
  margin: 14px auto 0;
  padding: 0 20px;
  border: 0;
  border-radius: 10px;
  background: var(--tgp-focus);
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.tgp-map__status.is-error .tgp-status__retry {
  display: block;
}

@keyframes tgp-spin {
  to {
    transform: rotate(360deg);
  }
}

/* -------------------------------------------------------------- markers - */

.tgp-map__marker {
  /* circleMarker paths; transition keeps zoom-driven radius changes smooth */
  transition: stroke-width 0.12s ease;
}

.tgp-pulse {
  /* divIcon wrapper for the search "found it" highlight; iconSize is 0 so the
     anchor sits exactly on the marker and the ring is centred on it. */
  width: 0 !important;
  height: 0 !important;
  pointer-events: none;
}

.tgp-pulse span {
  position: absolute;
  top: 0;
  left: 0;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  border: 3px solid var(--tgp-focus);
  opacity: 0;
}

.tgp-pulse.is-on span {
  animation: tgp-pulse 1.1s ease-out 3;
}

@keyframes tgp-pulse {
  0% {
    transform: scale(0.5);
    opacity: 0.95;
  }
  70% {
    opacity: 0.35;
  }
  100% {
    transform: scale(2.6);
    opacity: 0;
  }
}

/* -------------------------------------------------------------- popups -- */

.tgp-map .leaflet-popup-content-wrapper {
  border-radius: 10px;
}

.tgp-map .leaflet-popup-content {
  margin: 10px 14px;
  font-size: 13.5px;
  line-height: 1.45;
}

.tgp-popup__num {
  display: block;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.25;
}

.tgp-popup__vendor {
  display: block;
  margin-top: 2px;
}

.tgp-popup__vendor.is-open {
  color: var(--tgp-ink-soft);
  font-style: italic;
}

.tgp-popup__meta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 12.5px;
  color: var(--tgp-ink-soft);
}

.tgp-popup__swatch {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.2);
}

/* ---- area popups: notes + setup schedule ---- */

/* `overflow-wrap: anywhere` because notes are free text an organizer typed —
   one unbroken 60-character string must not push the popup off the map. */
.tgp-popup__notes {
  margin: 8px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--tgp-ink);
  overflow-wrap: anywhere;
}

/* Vendor links. A ROW, not a stack: this popup opens over the map on a phone,
   and three labelled lines would push the booth number off the top of it. */
/* Vendor links: a row of circular icon buttons. Left-aligned to match every
   other line in this popup — the booth number, the vendor name and the meta
   line all start at the same edge, and a centred row would be the only thing
   that did not. */
.tgp-popup__links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 9px 0 0;
}

/* Same white-disc / thin-border / soft-shadow idiom as the map's own controls,
   so a link in a popup reads as a thing you press.

   Scoped `.tgp-map .tgp-popup__link` rather than the bare class because
   Leaflet's own `.leaflet-container a { color: #0078A8 }` is (0,1,1) and would
   otherwise outrank a (0,1,0) class no matter which stylesheet loads second —
   which turned every glyph Leaflet blue. The descendant selector buys the one
   point of specificity that decides it, without an `!important` that a host
   theme would then have to fight. */
.tgp-map .tgp-popup__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--tgp-line);
  border-radius: 50%;
  background: var(--tgp-surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.16);
  color: var(--tgp-ink);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

.tgp-map .tgp-popup__link svg {
  display: block;
  width: 18px;
  height: 18px;
}

/* A visible ring rather than a colour change alone: these are icon-only, so
   "which one am I on" has to survive being unable to read a label. */
.tgp-map .tgp-popup__link:hover,
.tgp-map .tgp-popup__link:focus-visible {
  border-color: var(--tgp-focus);
  color: var(--tgp-focus);
  box-shadow: 0 0 0 3px rgba(30, 136, 229, 0.3);
}

/* "View menu": the SITE'S button (`button button--primary button--slim`,
   GenerateBlocks global styles + GeneratePress tokens). Nothing here restyles
   that button. Inside a Leaflet popup, three rules outrank it and are undone,
   with the same values the theme itself uses (the tgp-map-offline.css pattern):

     leaflet.css   .leaflet-container a     { color: #0078A8 }        (0,1,1)
     leaflet.css   .leaflet-popup-content   { font-size: 13px; line-height: 1.3 }
     leaflet.css   .leaflet-container       { font-family: ... }

   so the colour, the site's base type (GeneratePress `body, button
   { font-family: Roboto, sans-serif; font-size: 1.13rem }`) and GenerateBlocks'
   `.button { line-height: 1em }` are restated. Padding, border, radius and
   background come from the theme untouched. The 44px floor is the map's
   tap-target rule for its slim buttons (John, 2026-09-18); full width so it is
   one clear target on a phone. */
.tgp-map .tgp-popup__menu {
  margin: 10px 0 0;
}

.tgp-map .tgp-popup__menulink {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 100%;
  min-height: var(--tgp-tap);
  color: var(--base-900);
  font-family: Roboto, sans-serif;
  font-size: 1.13rem;
  line-height: 1em;
  text-decoration: none;
}

.tgp-map .tgp-popup__menulink:hover,
.tgp-map .tgp-popup__menulink:focus {
  color: var(--base-900);
}

.tgp-popup__menunote {
  font-size: 12.5px;
  color: var(--tgp-ink-soft);
}

/* ---- editor-only planning block ---- */

/* Needs, audience, access notes and the vendor's blurb. Never built in public
   mode — `audience` alone carries content-rating tags. */
.tgp-popup__planning {
  margin-top: 8px;
  padding-top: 7px;
  border-top: 1px solid var(--tgp-line);
}

.tgp-popup__needs,
.tgp-popup__audience,
.tgp-popup__plannote {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 6px;
  margin: 0 0 5px;
  font-size: 12.5px;
  line-height: 1.4;
}

.tgp-popup__planlabel {
  font-weight: 700;
  color: var(--tgp-ink-soft);
  white-space: nowrap;
}

.tgp-popup__need {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.tgp-popup__needname {
  font-size: 12px;
}

/* Muted chips: an audience tag is a fact about the vendor, not a warning, and
   colouring them would turn a planning aid into a judgement. */
.tgp-popup__tag {
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--tgp-surface-alt);
  color: var(--tgp-ink-soft);
  font-size: 11.5px;
  line-height: 1.55;
  overflow-wrap: anywhere;
}

.tgp-popup__plannote {
  display: block;
}

.tgp-popup__plannote .tgp-popup__planlabel {
  margin-right: 5px;
}

.tgp-popup__promo {
  margin: 5px 0 0;
  font-size: 12.5px;
  line-height: 1.45;
  font-style: italic;
  color: var(--tgp-ink);
  overflow-wrap: anywhere;
}

/* ---- area activities (public data; hidden ones editor-only) ---- */

.tgp-popup__activities {
  margin-top: 8px;
  padding-top: 7px;
  border-top: 1px solid var(--tgp-line);
}

.tgp-popup__actheading {
  margin: 0 0 4px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--tgp-ink-soft);
}

.tgp-popup__activity {
  margin-bottom: 7px;
}

.tgp-popup__activity:last-child {
  margin-bottom: 0;
}

.tgp-popup__actname {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.35;
  overflow-wrap: anywhere;
}

/* Same pill as the popup's own admin tag, inline after the name. */
.tgp-popup__actadmin {
  margin-left: 5px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--tgp-surface-alt);
  color: var(--tgp-ink-soft);
  font-size: 10.5px;
  font-weight: 700;
  white-space: nowrap;
}

.tgp-popup__actmeta {
  margin: 1px 0 0;
  font-size: 12px;
  color: var(--tgp-ink-soft);
  overflow-wrap: anywhere;
}

.tgp-popup__actdesc {
  margin: 2px 0 0;
  font-size: 12.5px;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.tgp-popup__setup {
  margin-top: 8px;
  padding-top: 7px;
  border-top: 1px solid var(--tgp-line);
}

.tgp-popup__setuprow {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  margin: 0 0 3px;
  font-size: 12.5px;
  line-height: 1.4;
}

.tgp-popup__setuprow:last-child {
  margin-bottom: 0;
}

.tgp-popup__setuplabel {
  font-weight: 700;
  color: var(--tgp-ink-soft);
  white-space: nowrap;
}

.tgp-popup__setupvalue {
  color: var(--tgp-ink);
  overflow-wrap: anywhere;
}

/* "Admin-only — not on the public map". Only ever rendered in editor mode. */
.tgp-popup__admintag {
  display: inline-block;
  margin: 8px 0 0;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--tgp-surface-alt);
  color: var(--tgp-ink-soft);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.01em;
}

/* ------------------------------------------------------------- controls - */

.tgp-map .leaflet-control-zoom a {
  width: 34px;
  height: 34px;
  line-height: 34px;
}

.tgp-map .leaflet-control-attribution {
  font-size: 10px;
  background: rgba(255, 255, 255, 0.82);
}

/* ------------------------------------------------------------ responsive - */

/* Booth list as a bottom sheet. Note this breakpoint is 767px, not the 600px
   the rest of the chrome uses: a 700px-wide tablet has the height for a sheet
   but not the width to give 310px away to a docked column. */
@media (max-width: 767px) {
  /* Sheet mode is thumb territory: the all-toggle becomes a full tap target
     rather than the compact desktop chip. */
  .tgp-panel__allbtn {
    min-height: var(--tgp-tap);
    padding-right: 12px;
    padding-left: 12px;
    font-size: 13px;
  }

  .tgp-map__panel {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1000;
    flex: none;
    width: auto;
    height: var(--tgp-panel-peek);
    max-height: 85%;
    border-right: 0;
    border-top: 1px solid var(--tgp-line);
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.18);
    transition: height 0.22s ease;
    overflow: hidden;
  }

  .tgp-map__panel.is-half {
    height: 50%;
  }

  .tgp-map__panel.is-full {
    height: 85%;
  }

  /* While a finger is on the handle the height is written every pointermove;
     a transition would make it lag behind the finger. */
  .tgp-map__panel.is-dragging {
    transition: none;
  }

  /* Defensive: the edge tab is desktop-only, and a stale is-collapsed class
     must never be able to zero out the sheet. */
  .tgp-panel__tab {
    display: none;
  }

  .tgp-map__panel.is-collapsed {
    flex: none;
    width: auto;
  }

  .tgp-map__panel.is-collapsed .tgp-panel__head {
    display: flex;
  }

  .tgp-map__panel.is-collapsed .tgp-panel__body {
    display: block;
  }

  .tgp-panel__head {
    padding-top: 14px;
  }

  /* The grab handle. Bigger and darker than it was: at 36x4 in the same grey
     as every hairline border on the page it read as decoration, which is
     precisely the complaint. */
  .tgp-panel__grip {
    display: block;
    position: absolute;
    top: 7px;
    left: 50%;
    width: 44px;
    height: 5px;
    margin-left: -22px;
    border-radius: 3px;
    background: var(--tgp-ink-soft);
    opacity: 0.55;
  }

  /* The words. Shown ONLY while the sheet is down — once it is up, the thing
     it was pointing at has happened and the label would be stale advice.
     Pushed to the right so it sits opposite the title rather than competing
     with it; the count gives up its auto-margin to make room. */
  .tgp-map__panel.is-peek .tgp-panel__hint {
    display: inline;
    margin-left: auto;
    padding-left: 8px;
    color: var(--tgp-ink-soft);
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
  }

  /* With the hint present the count is no longer the last item, so it stops
     claiming the free space. */
  .tgp-map__panel.is-peek .tgp-panel__count {
    margin-left: 0;
  }

  /* The head is the drag surface AND the tap target: 48px via
     --tgp-panel-peek, comfortably past the 44px floor this widget holds
     itself to everywhere else. Stated here so a future padding change cannot
     quietly shrink it. */
  .tgp-panel__head {
    min-height: var(--tgp-panel-peek);
  }

  /* Finger targets. */
  .tgp-panel__row {
    min-height: 48px;
  }

  .tgp-panel__grouphead,
  .tgp-admin__headbtn {
    min-height: var(--tgp-tap);
  }

  .tgp-admin__row {
    min-height: 48px;
  }

  /* Every editor control is a finger target on a phone. */
  .tgp-editor__btn {
    min-height: var(--tgp-tap);
  }

  .tgp-picker__row {
    min-height: 48px;
  }

  /* Icon-only links are a finger target like everything else here. The disc
     itself grows rather than being padded, because a 32px disc inside a 44px
     hit area looks like a mistake next to the 44px controls in the same view. */
  .tgp-map .tgp-popup__link {
    width: var(--tgp-tap);
    height: var(--tgp-tap);
  }

  .tgp-map .tgp-popup__link svg {
    width: 22px;
    height: 22px;
  }

  .tgp-picker__panel {
    max-width: none;
  }

  /* The booth-needs switches are the only thing in the legend a finger has to
     hit, so they get the same floor as every other control here. The row grows,
     the ICONS do not: the disc stays 17px, matched to the facility key above it,
     and the extra height is empty space around a centred row. Three rows of
     44px is the whole cost, editor-only, and it buys a switch that can actually
     be tapped — a 17px row cannot. */
  .tgp-legend__toggleitem {
    min-height: var(--tgp-tap);
  }

  /* Without this the group's own 4px row-gap stacks on top of three already
     tall rows for no benefit — the rows are unmistakably separate at 44px. */
  .tgp-legend__group--toggles {
    gap: 0 12px;
  }

  /* Lift the map's own bottom furniture clear of the peek bar so the
     attribution and the legend stay readable. */
  .tgp-map--has-panel .tgp-map__legend {
    bottom: calc(var(--tgp-panel-peek) + 10px);
  }

  .tgp-map--has-panel .tgp-map__stage .leaflet-bottom {
    bottom: var(--tgp-panel-peek);
  }
}

@media (max-width: 600px) {
  .tgp-map {
    height: 78vh;
    border-radius: 0;
  }

  .tgp-map__search {
    max-width: none;
  }

  /* Bigger finger targets in the results list on phones. */
  .tgp-search__result {
    min-height: 48px;
  }

  .tgp-map__legend {
    font-size: 12px;
  }

  .tgp-legend__toggle {
    min-height: var(--tgp-tap);
  }

  .tgp-map .leaflet-control-zoom {
    display: none; /* pinch-zoom is the phone gesture; reclaim the space */
  }
}

/* ONE small rise-and-settle, the first time the sheet is on screen, to say
   "this moves". No loop, no bounce, no arrow. Gated on a stated preference for
   motion rather than merely the absence of a preference against it, so the
   default for anyone who has not expressed one is still to animate — matching
   how the rest of this file treats motion. */
@media (prefers-reduced-motion: no-preference) {
  .tgp-map__panel.is-hinting {
    animation: tgp-sheet-hint 1100ms ease-in-out 1;
  }

  @keyframes tgp-sheet-hint {
    0% {
      transform: translateY(0);
    }
    35% {
      transform: translateY(-12px);
    }
    70% {
      transform: translateY(-4px);
    }
    100% {
      transform: translateY(0);
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  .tgp-map__panel,
  .tgp-panel__chevron,
  .tgp-panel__caret,
  .tgp-panel__allcaret {
    transition: none;
  }

  .tgp-map__status .tgp-status__spinner,
  .tgp-pulse.is-on span {
    animation-duration: 0.001ms;
    animation-iteration-count: 1;
  }

  .tgp-pulse.is-on span {
    opacity: 0.9;
    transform: scale(1.6);
  }
}

/* ----------------------------------------------------------------- print - */

/* EDITOR ONLY in practice — nothing adds `tgp-map--printing` in a public build
   — but written to be harmless anywhere, because a host theme's own print
   stylesheet is not something this widget gets to audit.

   The rules apply to `@media print` regardless of the class: Ctrl-P with no
   preparation should still produce a usable page, not a screenshot of a web
   app. The class is what carries the parts JS had to arrange (admin layers on,
   legend open, size recalculated). */
@media print {
  /* LANDSCAPE, because the park is a ~3.5:1 east-west strip and a portrait
     sheet spends half its ink on trees. 8mm margins: enough for a printer's
     unprintable edge, not so much that the strip has to shrink to fit.

     Printable area after those margins:

       Letter landscape  ->  263.4 x 199.9mm
       A4     landscape  ->  281.0 x 194.0mm

     A4 is the shorter sheet and Letter the narrower one, so ONE PAGE on both
     means sizing to the smaller of each. The height below (190mm) is under A4's
     194mm with slack for mm->px rounding and for the header/footer line
     browsers draw in the margin when that option is left on.

     190mm is mirrored as PRINT_PAGE_H_MM in festival-map.js, because Leaflet
     has to be laid out at the paper's shape BEFORE it chooses what to show.
     One decision in two files; change them together. */
  @page {
    size: landscape;
    margin: 8mm;
  }

  /* PAGE ISOLATION. John's first real print came out with the theme header, the
     site logo, the footer and the WordPress admin bar on it. A print stylesheet
     scoped to `.tgp-map` cannot reach any of that — it is the host page, not
     the widget — so the only thing that works from inside is the classic
     hide-everything-then-unhide-us pattern.

     `visibility` rather than `display`, because `display: none` on a wrapper
     would take the map down with it; visibility is inherited and can be turned
     back on further in. Everything hidden this way still occupies flow, which
     is exactly why `.tgp-map` is lifted to `position: absolute` at the page
     origin below — otherwise a tall hidden header would push the map onto
     page 2 as a sheet of white.

     KNOWN LIMITATION, the same one full screen has: an ancestor with
     `position`, `transform`, `filter` or `will-change` becomes the containing
     block, and the map anchors to that instead of the page. Nothing the widget
     can do from the inside; move the container out of the transformed wrapper. */
  body * {
    visibility: hidden !important;
  }

  .tgp-map,
  .tgp-map * {
    visibility: visible !important;
  }

  /* COLOUR SURVIVAL. Browsers drop backgrounds when printing unless told
     otherwise, and on this map the backgrounds ARE the information — zone
     fills, badge discs, glyph discs, legend chips, the booth circles' own
     fills. Declared across the whole subtree rather than a list of classes,
     because the list was already six entries and every new badge added a
     seventh. A mono printer greyscales it regardless; that is what the darker
     strokes below are for. */
  .tgp-map,
  .tgp-map * {
    -webkit-print-color-adjust: exact !important;
    print-color-adjust: exact !important;
  }

  /* Nothing you can press survives onto paper. */
  .tgp-map__search,
  .tgp-map__panel,
  .tgp-map__fs,
  .tgp-map__editing,
  .tgp-map__picker,
  /* Out of the flow, not merely invisible: the stage is a flex column in print,
     so anything left in it becomes a row of that column. */
  .tgp-map__status,
  .tgp-map__reposnote,
  .tgp-map__dupwarn,
  .tgp-map__movechip,
  .tgp-map__editchip,
  .tgp-popup__edit,
  /* Belt and braces on both: enterPrintMode() closes the menu and turns the
     mode off, so by the time a page is composed there is neither to hide. */
  .tgp-map__printmenu,
  .tgp-repos__handle,
  .tgp-map .leaflet-control-zoom,
  .tgp-map .leaflet-control-attribution,
  .tgp-legend__toggle,
  /* The needs show/hide switches. They are controls, and paper has none — the
     fire sheet swaps this heading for a static key instead (renderNeedsLegend),
     so nothing is lost there. */
  .tgp-legend__label--toggles,
  .tgp-legend__group--toggles,
  .tgp-panel__toolbar {
    display: none !important;
  }

  /* White ground and no shadows: a page-wide grey box is a cartridge of ink
     for nothing, and shadows print as smudges. */
  .tgp-map,
  .tgp-map__stage,
  .tgp-map__canvas {
    background: #ffffff !important;
    box-shadow: none !important;
  }

  /* The map takes the page, from the page origin. `absolute` at 0,0 does two
     jobs at once: it undoes the fullscreen fixed box (which would print one
     viewport and drop the rest) AND it lifts the widget out of the flow that
     the hidden page furniture above still occupies. */
  .tgp-map {
    position: absolute !important;
    top: 0 !important;
    left: 0 !important;
    right: auto !important;
    bottom: auto !important;
    width: 100% !important;
    /* Clamped, not `auto`. This is the hard stop that makes "one page" a
       guarantee rather than an arithmetic hope: whatever the content does, the
       widget cannot occupy more than one sheet's printable height. */
    height: 190mm !important;
    max-height: 190mm !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    overflow: hidden !important;
  }

  /* The host page's own scroll offset must not travel onto the sheet, and
     nothing after the map may generate a second page. */
  html,
  body {
    background: #ffffff !important;
    height: auto !important;
    overflow: visible !important;
  }

  /* Nothing in the widget may be split across a page boundary. With the clamp
     above there should be no boundary to split at — this is what makes a
     mistake in that arithmetic show up as a clipped legend rather than as a
     second sheet with a strip on it. */
  .tgp-map,
  .tgp-map__stage,
  .tgp-map__canvas,
  .tgp-map__legend,
  .tgp-legend__section {
    break-inside: avoid !important;
    page-break-inside: avoid !important;
    break-after: avoid !important;
    page-break-after: avoid !important;
  }

  /* THE SPLIT IS COMPUTED, NOT CONSTANT. The stage is a flex column of the full
     page height; the legend takes the height it needs and the canvas takes what
     is left. The previous build fixed the map at 150mm and let the legend fall
     under it, which at production density is 150 + 6 + 48 = 204mm against A4's
     194 — that is the strip John got on page 2.

     `applyPrintBox()` measures the canvas out of this same layout before it
     frames, so the on-screen box and the sheet agree. The inline width JS
     writes is overridden here because the page is the authority on its own
     width; the height is not, because it is the clamp. */
  .tgp-map__stage {
    position: relative !important;
    display: flex !important;
    flex-direction: column !important;
    width: 100% !important;
    height: 190mm !important;
    max-height: 190mm !important;
    flex: 0 0 auto !important;
    overflow: hidden !important;
  }

  .tgp-map__canvas {
    /* `relative`, NOT the `absolute` it wears on screen. An absolutely
       positioned child is out of the flex flow entirely: the canvas kept
       filling the whole stage and the legend, now static, stacked at the TOP of
       the column — printing the key straight over the map. Putting the canvas
       back in flow is what makes the column a column. */
    position: relative !important;
    flex: 1 1 auto !important;
    /* Without this a flex item refuses to shrink below its content height, and
       Leaflet's canvas would push the legend off the sheet again. */
    min-height: 0 !important;
    height: auto !important;
    inset: auto !important;
  }

  /* The legend is the key to the badges and the hidden pins, so it prints —
     but as a block under the map rather than a floating card over it, and at
     its natural height so the canvas is what absorbs any change. */
  .tgp-map__legend {
    position: static !important;
    flex: 0 0 auto !important;
    max-width: none !important;
    margin-top: 4mm;
    box-shadow: none !important;
    border: 1px solid #999999 !important;
  }

  .tgp-map__legend.is-collapsed .tgp-legend__body {
    display: block !important;
  }

  .tgp-legend__body {
    max-height: none !important;
    overflow: visible !important;
  }

  /* MONO-PRINTER LEGIBILITY.

     Most of what John prints goes through an office laser, which turns the zone
     palette into five nearly identical greys. Colour still carries on a PDF or
     a colour printer (see the print-color-adjust block above); this is the
     other half — shape and edge, which survive greyscaling.

     Booth circles get a near-black outline whatever their zone is. On screen an
     assigned booth wears a WHITE ring (it sits on a coloured basemap and needs
     to pop off it); on paper that ring is invisible against the sheet, which is
     what turned a run of assigned booths into a grey smudge. Open booths keep
     their hollow white fill — the assigned/open distinction is fill, and it has
     to stay readable — but gain the same strong edge.

     Set on the class the circleMarkers carry rather than on `path`, so zone
     polygons, admin shapes and Leaflet's own furniture are untouched. */
  .tgp-map .tgp-map__marker {
    stroke: #141414 !important;
    stroke-width: 1.5 !important;
    stroke-opacity: 1 !important;
  }

  /* Gaps keep their dashes on paper. The rule above deliberately takes the
     stroke COLOUR off every booth (grey prints as a smudge), which would have
     left a gap indistinguishable from a bookable space on the one sheet where
     the difference decides where a truck parks. The dash pattern is the half of
     the signal that greyscales, so it is restated here rather than relied on:
     Leaflet writes `stroke-dasharray` as a presentation attribute, and any
     stylesheet rule at all — including a host theme's — would beat it.
     Slightly wider than the screen pattern because the print stroke is
     narrower than the screen one and short dashes fill in on a laser. */
  .tgp-map .tgp-map__marker--gap {
    stroke-dasharray: 3.5 2.5 !important;
  }

  /* Zone polygons: a little more edge so a fill that greyscales to nearly the
     basemap still reads as a bounded region. Deliberately lighter than the
     booth stroke — a zone is the ground these sit on, not a competitor. */
  .tgp-map .leaflet-tgp-zones-pane path {
    stroke-width: 2 !important;
    stroke-opacity: 0.9 !important;
  }

  /* The needs pill needs almost nothing here, which was the point of a white
     capsule with a dark edge: white prints as paper and the border prints as
     ink whatever the printer does with colour. The shadow goes (it prints as a
     smudge) and the border firms up to survive greyscaling next to the basemap;
     the glyphs keep their colours through the print-color-adjust rule above and
     greyscale to distinguishable densities without it. */
  .tgp-needpill {
    background: #ffffff !important;
    border-color: #111111 !important;
    box-shadow: none !important;
  }

  .tgp-map .leaflet-popup,
  .tgp-map .leaflet-tooltip {
    display: none !important;
  }

  /* Belt and braces on the link buttons: the whole popup is already hidden
     above, but a URL you cannot click is clutter on paper wherever it turns up,
     and a host stylesheet that resurrects popups for print should not resurrect
     these with them. */
  .tgp-popup__links,
  .tgp-popup__menu {
    display: none !important;
  }
}

/* On screen, print mode looks like fullscreen minus the furniture, so John can
   see what he is about to send to the printer before the dialog opens. */
.tgp-map--printing .tgp-map__search,
.tgp-map--printing .tgp-map__editing,
.tgp-map--printing .tgp-map__movechip,
.tgp-map--printing .tgp-map__editchip {
  visibility: hidden;
}

/* The booth list goes out of the flow, not just out of sight: on paper it is
   `display: none`, so a preview that still reserved 310px would frame the map
   into a box narrower than the page and print a different extent than the one
   on screen. Same reason the stage is sized in page millimetres. */
.tgp-map--printing .tgp-map__panel {
  display: none !important;
}

/* The stage carries an inline page-shaped box while print mode is on (see
   applyPrintBox). It can be wider than the container on a laptop, and that is
   fine — the preview is allowed to overflow, because the pixel box is what
   makes the framing honest. */
.tgp-map--printing {
  overflow: visible;
}

/* THE SAME FLEX SPLIT AS THE SHEET, on screen. This is not decoration: JS
   measures the canvas out of this layout to decide what to frame, so if the
   preview stacked the legend differently the framing would be computed for a
   box the printer never uses — which is the class of bug this whole pass
   exists to close. Height comes from the inline style applyPrintBox writes. */
.tgp-map--printing .tgp-map__stage {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.tgp-map--printing .tgp-map__canvas {
  /* Same reason as the print block: absolute children do not flex. */
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  height: auto;
  inset: auto;
}

.tgp-map--printing .tgp-map__status {
  display: none;
}

.tgp-map--printing .tgp-map__legend {
  position: static;
  flex: 0 0 auto;
  max-width: none;
  margin-top: 4mm;
  box-shadow: none;
}

/* ------------------------------------------------------ print scope menu - */

/* Anchors the menu to the print button rather than to the bar, so it stays put
   if the bar ever gains or loses a control. */
.tgp-fs__printwrap {
  position: relative;
  display: block;
}

/* Opens to the LEFT: the bar lives against the right edge of the map, so there
   is nowhere else for it to go. `bottom: auto` + `top: 0` keeps it aligned with
   its own button rather than with the bar. */
.tgp-map__printmenu {
  position: absolute;
  top: 0;
  right: calc(100% + 8px);
  z-index: 1200;
  min-width: 216px;
  padding: 6px;
  border-radius: 10px;
  border: 1px solid var(--tgp-line);
  background: var(--tgp-surface);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  text-align: left;
}

.tgp-map__printmenu[hidden] {
  display: none;
}

.tgp-printmenu__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  /* The same finger target every other control here holds itself to, at every
     width — this one is pressed standing up in a park as often as at a desk. */
  min-height: var(--tgp-tap);
  padding: 8px 10px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  text-align: left;
  cursor: pointer;
  color: var(--tgp-ink);
}

.tgp-printmenu__item:hover,
.tgp-printmenu__item:focus-visible {
  background: var(--tgp-surface-alt);
}

.tgp-printmenu__icon {
  flex: 0 0 auto;
  display: inline-flex;
  width: 20px;
  height: 20px;
  color: var(--tgp-ink-soft);
}

.tgp-printmenu__icon svg {
  width: 20px;
  height: 20px;
}

.tgp-printmenu__text {
  min-width: 0;
  display: block;
}

.tgp-printmenu__label {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
}

/* The line that makes the choice answerable without printing one to find out. */
.tgp-printmenu__hint {
  display: block;
  font-size: 11.5px;
  color: var(--tgp-ink-soft);
}

/* On a phone the map is narrow and the bar is hard against the right edge;
   pinning the menu to the viewport edge instead of to the button keeps it from
   running off. */
@media (max-width: 600px) {
  .tgp-map__printmenu {
    right: calc(100% + 6px);
    min-width: 200px;
    max-width: calc(100vw - 80px);
  }

  .tgp-printmenu__item {
    min-height: 48px;
  }
}
