/* Same TTF the map's own labels render from (via MapLibre's PBF glyph
   tiles at /fonts/glyphs — see KID_MAP_FONT_STACK in app.js), loaded here
   too so the HOME marker's plain-DOM label (.home-marker-label below) can
   use the identical font, not just an approximation of its look. */
@font-face {
  font-family: "Patrick Hand Regular";
  src: url("fonts/PatrickHand-Regular.ttf") format("truetype");
  font-display: swap;
}

/* Cute/chonky display font used only for the "My First Map" title
   link (.home-link) below. */
@font-face {
  font-family: "Tomatoku";
  src: url("fonts/Tomatoku-6Rl0A.ttf") format("truetype");
  font-display: swap;
}

html, body {
  height: 100%;
}

body {
  font-family: "Comic Sans MS", "Marker Felt", system-ui, sans-serif;
  margin: 0;
  /* Warm off-white, lighter than .page-box's own background (#f2ede1) —
     per explicit feedback that an earlier, closer-matching tone (#f6f3ec)
     made the sidebar and the map page blend into each other rather than
     reading as two distinct areas. kidwalktransparent.gif (used in
     .panel-hero-gif) has a transparent background, so it isn't tied to
     matching any particular page color the way the old opaque-background
     gif was. */
  background: #fbf9f3;
  color: #2b2b2b;
  overflow: hidden;
}

.app-layout {
  display: flex;
  width: 100%;
  height: 100%;
}

.side-panel {
  flex: 0 0 338px;
  width: 338px;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 18px 24px 18px 32px;
  overflow-y: auto;
  font-family: "Patrick Hand Regular", "Comic Sans MS", "Marker Felt", system-ui, sans-serif;
  /* Patrick Hand only ships one weight, and reads a little thin at
     this size — a slight text-stroke thickens the letterforms without
     relying on the browser's synthetic-bold fallback (which can look
     blurry/uneven on a cursive font). */
  -webkit-text-stroke: 0.4px currentColor;
}

.panel-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 18px;
}

.panel-field-label {
  font-weight: normal;
}

/* Puts the label and its content side by side instead of .panel-field's
   default stacked layout — used for the page shape toggle, title input,
   and title style picker, to save vertical space since each one's own
   content is short/compact enough to sit next to its label on one line
   rather than needing a full row of its own underneath it. Content sits
   directly next to the label (gap), not pushed to the row's far end. */
.panel-field-row {
  flex-direction: row;
  align-items: center;
  gap: 12px;
}

/* Label keeps its natural width; the input/picker next to it claims the
   rest of the row instead of sizing to its own content (a bare text
   input has no intrinsic width, and the style picker's dropdown menu is
   positioned relative to its own trigger, so both need to actually fill
   the remaining row width rather than staying tiny). */
.panel-field-row #map-title-input,
.panel-field-row .map-title-style-picker,
.panel-field-row .water-style-picker {
  flex: 1;
  min-width: 0;
}

.panel-divider {
  width: 100%;
  border: none;
  margin: 4px 0;
}

/* "Map Settings" / "Stickers" groups — always expanded (no collapse/
   toggle) so every control is visible at once; per explicit feedback the
   collapsible version made it harder to see all settings. Rendered as a
   card (rounded corners, no border) rather than a plain heading-plus-
   underline — per explicit feedback the blue underline version (tried
   first, see .panel-section-title below) didn't look right; two such
   cards stacked in the sidebar read better. */
.panel-section {
  display: flex;
  flex-direction: column;
  /* Same color as the sidebar background (#fbf9f3), not a lighter/white
     fill — a white fill (tried first) was explicitly reverted in favor
     of letting the shadow alone carry the separation, so the card doesn't
     read as a visibly different-colored block, just a raised one. Needs
     a noticeably stronger/tighter shadow than the earlier white-fill
     version to still read as "raised" with no color contrast to help. */
  background: #fbf9f3;
  border-radius: 16px;
  padding: 12px 14px;
  box-shadow: 0 3px 10px rgba(43, 43, 43, 0.22);
}

/* Section title: bold on its own wasn't enough to read as a distinct
   heading from the regular .panel-field-label rows below it, so this
   bumps size and adds letter-spacing to make each section's title clearly
   stand out as a heading. The underline this used to have (blue,
   matching the water-blue #1183c2 used elsewhere in this panel, e.g.
   button hovers — itself a replacement for an earlier yellow underline)
   was dropped once the section itself became a card: the card's own
   edge already separates it from the rest of the panel, so an underline
   under just the title was redundant/looked wrong per explicit feedback. */
.panel-section-title {
  margin: 0;
  font-family: inherit;
  font-size: 20px;
  font-weight: bold;
  letter-spacing: 0.02em;
  -webkit-text-stroke: inherit;
  color: #000;
}

.panel-section-title-hint {
  font-size: 14px;
  font-weight: normal;
  letter-spacing: normal;
  color: #555;
}

/* No longer indented (per-title underline removal above made the indent
   look like a leftover rather than an intentional hierarchy cue) — the
   card's own padding now provides the section's visual boundary. */
.panel-section-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 10px;
}

/* Pins the export field to the bottom of the side panel. margin-top: auto
   alone (used previously) only pushes it to the bottom when the panel's
   content is shorter than the panel itself — once the settings cards make
   the panel tall enough to actually scroll, an auto-margin block scrolls
   away with the rest of the content instead of staying put. position:
   sticky (the panel itself, .side-panel, is the scroll container via its
   own overflow-y: auto) keeps it visible at the bottom of the viewport
   through a scroll instead. Needs its own opaque background behind the
   button so scrolled-past content doesn't show through underneath it.
   The former divider <hr> right above the button was removed — with the
   settings/stickers groups now their own cards, a second separator line
   directly above the sticky button was redundant clutter. */
.panel-field-export-pin {
  position: sticky;
  bottom: 0;
  margin-top: auto;
  margin-left: -32px;
  margin-right: -24px;
  padding: 14px 24px 0 32px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  background: #fbf9f3;
}

/* Soft fade above the sticky button instead of a hard opaque edge —
   found live: a flat background boundary sitting right on top of
   .panel-section's own box-shadow made that shadow look like it was
   being cut off in a straight line rather than fading out naturally.
   The gradient repeats the sticky block's own background color from
   transparent to opaque so a card's shadow (or its scrolled-past bottom
   edge) fades away before reaching the button instead of stopping short. */
.panel-field-export-pin::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -14px;
  height: 14px;
  background: linear-gradient(to bottom, rgba(251, 249, 243, 0), #fbf9f3);
  pointer-events: none;
}

/* Sidebar's own landing content (title/search/tagline/gif), shown instead
   of the normal sidebar (below) until the user has a location — see
   app.js's revealMapControls(), which swaps [hidden] between this and
   .panel-field-post-search the moment a search succeeds (or a bookmarked
   URL already carries a location on load). Lives in the sidebar rather
   than as an overlay on top of the map, per explicit request — the map
   itself stays uncluttered even before a location is chosen. */
.panel-pre-search {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  gap: 14px;
}

.panel-pre-search[hidden] {
  display: none;
}

.panel-hero-title {
  font-family: "Tomatoku", "Comic Sans MS", "Marker Felt", system-ui, sans-serif;
  font-size: 37.2px;
  font-weight: 400;
  line-height: 1.2;
  margin: 0;
  color: #000;
  -webkit-text-stroke: 0.5px #2b2b2b;
}

.panel-hero-tagline {
  font-size: 24px;
  line-height: 1.35;
  font-weight: 400;
  color: #2b2b2b;
  margin: 0;
}

.panel-hero-gif {
  width: 100%;
  max-width: 138px;
  height: auto;
  border-radius: 10px;
  align-self: center;
  /* Pins the gif to the bottom of the sidebar's pre-search block (same
     margin-top: auto trick .panel-field-export-pin uses to pin the export
     button to the bottom of the post-search block), per explicit request
     to move it down rather than leaving it directly under the tagline. */
  margin-top: auto;
}

/* Wraps every sidebar control that only makes sense once a location has
   been chosen (the normal home-link/search row, plus page shape toggle,
   stickers, export) — hidden via the [hidden] attribute on page load,
   revealed by app.js once the map has an initial location (search or a
   bookmarked lat/lon URL). Needs to be a flex column itself, matching
   .side-panel's own flex-direction, so the nested .panel-field-export-pin's
   margin-top: auto still pins the export button to the bottom of the panel
   rather than just the bottom of this wrapper. */
.panel-field-post-search {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  gap: 14px;
}

.panel-field-post-search[hidden] {
  display: none;
}

/* App name/logo link, styled like a bold kid's-toy title rather than a
   plain nav link: Tomatoku (local @font-face above, user-supplied
   font file), a cute/chonky display font distinct from Patrick Hand
   Regular (which the map's own labels/stickers use). Solid black per
   explicit request (previously tinted per-word from the app's warm
   palette). Kept upright (no rotate) per explicit request, unlike the
   sticker-tray icons elsewhere in this file which do tilt. */
.home-link {
  font-family: "Tomatoku", "Comic Sans MS", "Marker Felt", system-ui, sans-serif;
  font-size: 37.2px;
  font-weight: 400;
  line-height: 1.2;
  text-decoration: none;
  display: inline-block;
  align-self: flex-start;
  color: #000;
  -webkit-text-stroke: 0.5px #2b2b2b;
}

.home-link:hover {
  transform: scale(1.1);
}

.side-panel select {
  font-family: inherit;
  font-size: 18px;
  padding: 4px 8px;
}

.side-panel input[type="text"] {
  font-family: inherit;
  border: 2px solid #2b2b2b;
  border-radius: 10px 12px 10px 12px;
  padding: 5px 10px;
  font-size: 18px;
  min-width: 0;
}

.side-panel button {
  font-family: inherit;
  font-size: 18px;
  padding: 6px 16px;
  border-radius: 12px 14px 12px 16px / 16px 12px 18px 12px;
  border: 2.5px solid #2b2b2b;
  background: #ffd166;
  cursor: pointer;
}

.side-panel button:hover {
  background: #ffbe3d;
}

/* Panel's final CTA — same font/size as every other side-panel button
   (.side-panel button), just wider padding since it spans the full
   panel width. white-space: nowrap keeps the label on one line rather
   than wrapping inside the button. No border, using the same soft
   shadow as the .panel-section cards/.page-box instead, per explicit
   feedback. */
#export-map-btn {
  width: 100%;
  padding: 10px 6px;
  border: none;
  border-radius: 12px 14px 12px 16px / 16px 12px 18px 12px;
  box-shadow: 0 3px 10px rgba(43, 43, 43, 0.22);
  white-space: nowrap;
}

#export-status {
  text-align: center;
}

.export-btn-title {
  font-weight: normal;
}

.search-row {
  display: flex;
  gap: 8px;
}

.search-row input[type="text"] {
  flex: 1;
}

.search-row button {
  flex: 0 0 auto;
  padding: 6px 12px;
}

/* Side-panel sticker tray (see STICKER_CONFIGS/setupStickerDrop in
   app.js) — one draggable thumbnail per sticker, wrapping into as many
   rows as needed. Similar stickers (animals, flowers, etc.) are kept
   adjacent by DOM order in map.html, not by row-grouping markup, so
   wrapping is free to reflow at any panel width without breaking that
   grouping. */
.sticker-tray {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.sticker-tray-row {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 6px;
  align-items: flex-start;
}

.sticker-tray-item {
  flex: 0 0 auto;
  width: 44.8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-width: 0;
}

.sticker-drag-icon {
  /* 20% smaller than the original 56px, per explicit feedback.
     --sticker-scale (set inline per-sticker in app.js's
     STICKER_CONFIGS/setupStickerDrop, e.g. heart/star/sparkle/flower2)
     shrinks individual stickers that read too big at this shared base
     size; defaults to 1 for every sticker without an override. */
  --sticker-scale: 1;
  width: calc(44.8px * var(--sticker-scale));
  height: calc(44.8px * var(--sticker-scale));
  object-fit: contain;
  cursor: grab;
  transform: rotate(-4deg);
  filter: drop-shadow(2px 2px 0 rgba(43, 43, 43, 0.15));
}

.sticker-drag-icon:active {
  cursor: grabbing;
}

.sticker-drag-icon.dragging {
  opacity: 0.4;
}

/* Marker for a user-dropped sticker (see setupStickerDrop in app.js).
   Plain DOM marker (icon + optional label stacked), no MapLibre popup
   box — matches the flat, no-chrome look the other custom icons (trees,
   critters) use. */
.sticker-marker {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  cursor: grab;
}

.sticker-marker:active {
  cursor: grabbing;
}

/* Wraps just the icon (see setupStickerDrop in app.js) so the resize
   handle below can be positioned via position:absolute relative to the
   icon's own box, independent of whether a label sits below it. */
.sticker-marker-icon-wrap {
  position: relative;
  display: inline-flex;
}

.sticker-marker-icon {
  /* 20% smaller than the original 48px, per explicit feedback.
     --sticker-scale: see .sticker-drag-icon's own comment above — same
     per-sticker override, set inline on this element too.
     --sticker-rotation: set inline by the rotate-handle drag handler in
     setupStickerDrop (app.js). Rotating only the icon (not the label,
     for home) matches how scale only visually resizes the icon shape —
     text has no natural "rotated" reading, so the label stays upright. */
  --sticker-scale: 1;
  --sticker-rotation: 0deg;
  width: calc(38.4px * var(--sticker-scale));
  height: calc(38.4px * var(--sticker-scale));
  /* Real bug, found live: home.png/star.png aren't square (122x99 and
     78x66 respectively), and a plain <img>'s default object-fit ("fill")
     stretches a non-square source to fill a square box — the on-map
     marker was visibly squatter/wider than the correctly-proportioned
     sidebar thumbnail (.sticker-drag-icon, which already had this),
     until this was added to match. */
  object-fit: contain;
  transform: rotate(var(--sticker-rotation));
  filter: drop-shadow(2px 2px 0 rgba(43, 43, 43, 0.25));
}

/* Resize/rotate grip (see setupStickerDrop's pointerdown handler in
   app.js) — one small round dot pinned to the icon's bottom-right corner,
   doing double duty for both actions (see .sticker-grab-zone below for
   how the two are distinguished). Hidden until the sticker is clicked
   (setupStickerDrop toggles .sticker-resize-active on
   .sticker-marker-icon-wrap, the same click that opens the "Remove"
   popup) rather than always-visible, per explicit feedback that a
   permanent handle on every dropped sticker was too much visual clutter.
   Never drawn into the exported PNG: compositeExportCanvas only ever
   draws .sticker-marker-icon/.sticker-marker-label onto the export
   canvas, this element is never referenced there. */
.sticker-resize-handle {
  display: none;
  position: absolute;
  right: -3px;
  bottom: -3px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #fff;
  border: 1.5px solid #666;
  /* Purely decorative — grabZone (positioned on top, see below) is what
     actually receives pointer events and sets the live cursor per
     position, so this static cursor is only ever seen if grabZone's own
     pointermove handler hasn't run yet (effectively never in practice). */
  cursor: nwse-resize;
  touch-action: none;
  pointer-events: none;
}

.sticker-marker-icon-wrap.sticker-resize-active .sticker-resize-handle {
  display: block;
}

/* Invisible, larger ring centered on the same corner as the dot above —
   the actual pointer target for both resize and rotate. Sized well past
   the dot's own radius so there's a comfortable rotate band surrounding
   it (see HANDLE_RADIUS_PX/distanceToHandleCenter in app.js, which decide
   resize-vs-rotate purely from distance to the dot's center, not from
   which DOM element the event landed on). Same selected-state visibility
   and export-exclusion as the dot. Cursor is set inline per-pointermove
   in app.js (nwse-resize over the dot, the curved rotate arrow anywhere
   else in this zone) rather than a fixed value here. */
.sticker-grab-zone {
  display: none;
  position: absolute;
  /* Centered on the exact same point as .sticker-resize-handle's own
     center (right: -3px + 12px/2 = 3px inward) — width/2 here must equal
     that same 3px offset from `right`, i.e. right = 3 - width/2. */
  right: -10.5px;
  bottom: -10.5px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: transparent;
  touch-action: none;
}

.sticker-marker-icon-wrap.sticker-resize-active .sticker-grab-zone {
  display: block;
}

/* Matches poi_r1/poi_r7/poi_r20's actual default label paint (confirmed
   live against the fetched liberty style: text-color #666, white halo,
   1px, 0.5 blur) and the same Patrick Hand Regular font every other
   on-map label uses (see KID_MAP_FONT_STACK in app.js and the @font-face
   above) — not just a visual approximation, the same font file and the
   same color/halo values real landmark labels render with. Font-size 15
   mirrors scaleTextSize's own POI-label bump in app.js (12px base scaled
   up ~1.25x for Patrick Hand's smaller letterforms, confirmed live). Only
   the home sticker uses this — heart/star are unlabeled per explicit
   request. */
.sticker-marker-label {
  font-family: "Patrick Hand Regular", "Comic Sans MS", "Marker Felt", system-ui, sans-serif;
  /* --sticker-scale: set inline alongside .sticker-marker-icon's own copy
     (see setupStickerDrop's resize-handle drag handler in app.js) so the
     "HOME" label grows/shrinks along with the icon instead of staying a
     fixed size while the icon above it resizes. */
  --sticker-scale: 1;
  font-size: calc(15px * var(--sticker-scale));
  font-weight: normal;
  color: #666;
  text-shadow:
    0 0 1px #fff,
    0 0 1px #fff,
    0 0 1px #fff,
    0 0 1px #fff;
  white-space: nowrap;
  pointer-events: none;
}

.page-shape-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Self-explanatory shape instead of a text label: each button is just a
   small rectangle (or square) already drawn in the page shape it picks
   (tall for vertical, wide for horizontal, even for square), so the icon
   doubles as the affordance. Sized down and given a lighter border (1.5px
   vs. .side-panel button's default 2.5px) per explicit feedback that the
   original 22x40/40x22 pair, at the same border weight as full-size
   buttons like "Go"/export, read as too big/heavy for what's just a small
   toggle. */
/* .side-panel button (a class+element compound selector, specificity
   0-1-1) otherwise beats a bare .page-shape-btn (0-1-0) regardless of
   source order, silently reintroducing that rule's padding/width — so
   this has to match the same compound-selector shape to actually win. */
.side-panel button.page-shape-btn {
  box-sizing: border-box;
  flex: 0 0 auto;
  padding: 0;
  width: 16px;
  height: 28px;
  border-width: 1.5px;
  /* .side-panel button's default radius (12-18px corners) is nearly half
     this button's own width and rounds it into a blob/pill that reads as
     "square" rather than a rectangle — use a much smaller radius here so
     the tall/wide/even shape itself stays legible. */
  border-radius: 4px;
}

.side-panel button.page-shape-btn-horizontal {
  width: 28px;
  height: 16px;
}

.side-panel button.page-shape-btn-square {
  width: 20px;
  height: 20px;
}

.page-shape-btn.active {
  background: #ffbe3d;
  box-shadow: inset 0 0 0 1.5px #2b2b2b;
}

.zoom-controls {
  display: flex;
  align-items: center;
  gap: 6px;
}

.zoom-controls button {
  padding: 6px 12px;
}

#zoom-in-btn,
#zoom-out-btn {
  font-size: 18px;
  font-weight: bold;
  line-height: 1;
  padding: 4px 12px;
}

.note {
  margin: 0;
  font-size: 16px;
  color: #666;
  min-height: 16px;
}

/* #build-status no longer ever shows a success message (removed per
   explicit feedback — the map title itself is the confirmation now, see
   searchAndFlyTo in app.js) — only a brief "Searching…"/error state, so
   its empty-state reserved space (.note's shared min-height, sized for
   every status line in this app) reads as a stray gap before the "Map
   title" field right below it. Scoped to this one id rather than
   shrinking .note generally, since other status lines (export, hero
   search) still want the full reserved height. */
#build-status:empty {
  min-height: 0;
}

.build-status-pending {
  color: #8a6d1f;
}

.build-status-success {
  color: #2f7a3a;
}

.build-status-error {
  color: #b23b3b;
}

.map-stage {
  flex: 1 1 auto;
  height: 100%;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  box-sizing: border-box;
  overflow: hidden;
}

/* US Letter aspect ratio (8.5in x 11in) for vertical/horizontal, or a
   plain 1x1 square. Scales to fill the available stage space while
   keeping that exact ratio, like a print preview —
   width/height are capped independently so it never overflows either
   axis, and the aspect-ratio box keeps the box "letter-shaped" at
   whichever size fits. */
.page-box {
  position: relative;
  background: #f2ede1;
  border-radius: 6px;
  /* Same soft shadow as the sidebar's .panel-section cards (Map Settings/
     Stickers), no border — per explicit feedback, so the map reads as a
     raised card like those two rather than hand-drawn-outlined. */
  box-shadow: 0 3px 10px rgba(43, 43, 43, 0.22);
  overflow: hidden;
}

.page-box.page-shape-vertical {
  aspect-ratio: 8.5 / 11;
  width: min(100%, calc((100vh - 48px) * 8.5 / 11));
  height: min(100%, calc((100vw - 338px - 48px) * 11 / 8.5));
}

.page-box.page-shape-horizontal {
  aspect-ratio: 11 / 8.5;
  width: min(100%, calc((100vh - 48px) * 11 / 8.5));
  height: min(100%, calc((100vw - 338px - 48px) * 8.5 / 11));
}

.page-box.page-shape-square {
  aspect-ratio: 1 / 1;
  width: min(100%, calc(100vh - 48px));
  height: min(100%, calc(100vw - 338px - 48px));
}

#map {
  width: 100%;
  height: 100%;
}

/* Shown while a geocode search is queued behind other in-flight searches
   (see app.js's showGeocodeBufferingOverlay/hideGeocodeBufferingOverlay) —
   a frosted blur over the map itself (backdrop-filter, so it reads as
   "the map is still there, temporarily obscured" rather than a plain
   loading screen) with a hopping-dots animation and wavy status text
   centered on top. Sits above #map and the title overlay but below the
   sticker-remove/POI-remove popups, which aren't relevant during a
   search anyway.

   A hand-drawn CSS animation rather than an <img> gif: this app already
   shows kidwalksmall.gif in the pre-search sidebar, and reusing the same
   (or a second, different) gif here read as a visibly repeated asset on
   screen at once — CSS avoids that without needing a second gif file. */
.geocode-buffering-overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background: rgba(242, 237, 225, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* The plain class rule's own "display: flex" above always wins over the
   HTML "hidden" attribute's UA-stylesheet default (display: none) at
   equal specificity — found live as the overlay/blur showing on initial
   page load with no search in progress at all. This attribute-selector
   rule outranks the class rule so toggling the "hidden" property in JS
   (showGeocodeBufferingOverlay/hideGeocodeBufferingOverlay) actually
   hides it again. */
.geocode-buffering-overlay[hidden] {
  display: none;
}

.geocode-buffering-dots {
  display: flex;
  gap: 6px;
}

.geocode-buffering-dots span {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #2b2b2b;
  opacity: 0.35;
  animation: geocode-dot-hop 1.8s ease-in-out infinite;
}

.geocode-buffering-dots span:nth-child(2) {
  animation-delay: 0.3s;
}

.geocode-buffering-dots span:nth-child(3) {
  animation-delay: 0.6s;
}

@keyframes geocode-dot-hop {
  0%, 100% { transform: translateY(0); opacity: 0.35; }
  50% { transform: translateY(-5px); opacity: 0.9; }
}

.geocode-buffering-text {
  font-family: inherit;
  font-size: 17px;
  font-weight: bold;
  color: #2b2b2b;
  text-shadow: 0 0 6px #fdfbf5, 0 0 6px #fdfbf5, 0 0 6px #fdfbf5;
  max-width: calc(100% - 48px);
  text-align: center;
}

/* Each letter is its own span (built in app.js's
   showGeocodeBufferingOverlay, since CSS alone can't stagger individual
   characters of one text node) with a per-letter animation-delay, so the
   line reads as one continuous wave rather than the whole string bobbing
   as a single rigid block — same staggering technique as the dots above,
   just applied letter-by-letter. */
.geocode-buffering-text-letter {
  display: inline-block;
  white-space: pre;
  animation: geocode-dot-hop 1.8s ease-in-out infinite;
}


.map-label {
  font-size: 13px;
  font-weight: bold;
  font-family: inherit;
  fill: #2b2b2b;
  paint-order: stroke;
  stroke: #fdfbf5;
  stroke-width: 3px;
  stroke-linejoin: round;
}

.road-label {
  font-size: 10px;
  font-family: inherit;
  fill: #4a4536;
  letter-spacing: 0.5px;
  paint-order: stroke;
  stroke: #fdfbf5;
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* Movable/resizable map title (editing/style now live in the sidebar —
   see #map-title-input/#map-title-style-picker in index.html and
   setupMapTitle in app.js). Absolutely positioned over #page-box (not a
   MapLibre marker) so it stays put on the printed page rather than
   moving with the map on pan — defaults to top-left, draggable
   anywhere within the page box afterward by grabbing the title box
   itself, same "drag the thing directly" convention setupStickerDrop's
   markers use. Positioned via left/top (not transform:translate) so
   compositeExportCanvas/drag math can read getBoundingClientRect()
   directly. */
.map-title-overlay {
  position: absolute;
  top: 16px;
  left: 16px;
  z-index: 5;
  max-width: calc(100% - 32px);
}

/* Text itself: a semi-opaque parchment plate behind the text (rather than
   just a text-shadow halo like on-map POI labels use) — per explicit
   feedback that the title needs to stay readable sitting on top of any
   map tile color underneath, and a title-sized string is too big for a
   thin halo to reliably outline. Plain (non-editable) text now — clicking
   it selects it (showing the resize handle only) and dragging it moves
   it; editing the text itself happens in the sidebar's #map-title-input.
   font-family matches the sidebar's own "My First Map"/home-link title
   (.panel-hero-title/.home-link, both Tomatoku) per explicit feedback
   that all title text should share one font — every preset below either
   inherits this or repeats the same Tomatoku-first stack; only
   "Attribution Pill" deliberately overrides it (see that preset's own
   comment for why). */
.map-title-text {
  --map-title-scale: 1;
  font-family: "Tomatoku", "Patrick Hand Regular", "Comic Sans MS", system-ui, sans-serif;
  font-size: calc(28px * var(--map-title-scale));
  font-weight: bold;
  color: #2b2b2b;
  background: rgba(253, 251, 245, 0.82);
  border: 2px solid rgba(43, 43, 43, 0.35);
  border-radius: 10px;
  padding: 4px 14px;
  cursor: grab;
  white-space: pre-wrap;
  word-break: break-word;
  outline: none;
  min-width: 40px;
  text-align: center;
  touch-action: none;
  user-select: none;
}

.map-title-text:active {
  cursor: grabbing;
}

/* Preset style options — applied by swapping this class (see
   MAP_TITLE_STYLE_PRESETS in app.js). These 5 are the finalists chosen
   from a 13-option visual gallery (title-styles-gallery.html, built and
   screenshotted live for user comparison before picking) — each one below
   ports that gallery entry's CSS as closely as possible, adapted in two
   ways every preset needs: (1) font-size uses calc(<gallery px> *
   var(--map-title-scale)) instead of a flat px value, so the resize
   handle still works under every preset, not just classic; (2) Tomatoku
   (the gallery's display font for these) is layered as a fallback after
   Patrick Hand Regular rather than replacing the font stack outright, so
   a preset still renders reasonably before Tomatoku's own @font-face
   finishes loading. "classic" (the default) intentionally has no
   override here since the base rule already is the classic look. */

/* Blob Badge — rounded amoeba-shaped color badge, thick soft-outlined
   white type. Riffed on an orange "KNOWLEDGE" reference image. */
.map-title-style-blob {
  font-family: "Tomatoku", "Patrick Hand Regular", "Comic Sans MS", system-ui, sans-serif;
  font-size: calc(30px * var(--map-title-scale));
  font-weight: 400;
  color: #fff;
  -webkit-text-stroke: 1px rgba(0, 0, 0, 0.15);
  background: #f0a63e;
  border: none;
  padding: 14px 34px;
  border-radius: 50% 50% 45% 55% / 60% 55% 45% 40%;
  box-shadow: 2px 4px 10px rgba(0, 0, 0, 0.2);
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
}

/* Halftone Bubble — thick-outline bubble letters filled with a subtle
   dotted halftone texture. Riffed on a blue "BANNER" reference image's
   dot-pattern letter fill. */
.map-title-style-halftone {
  font-family: "Tomatoku", "Patrick Hand Regular", "Comic Sans MS", system-ui, sans-serif;
  font-size: calc(30px * var(--map-title-scale));
  font-weight: 400;
  color: #6bb6e8;
  -webkit-text-stroke: 2px #1f5c9e;
  background:
    radial-gradient(circle, rgba(31, 92, 158, 0.25) 1px, transparent 1.2px) 0 0/6px 6px,
    #fdfbf5;
  border: 2.5px solid #1f5c9e;
  padding: 8px 24px;
  border-radius: 10px;
  box-shadow: 3px 3px 0 rgba(31, 92, 158, 0.4);
}

/* Minimal Halo Text — no box/background/border at all, just a soft white
   halo behind plain bold text. Quietest option in the set — strips every
   plate-related property .map-title-text's base rule sets. */
.map-title-style-minimalhalo {
  font-size: calc(32px * var(--map-title-scale));
  font-weight: bold;
  color: #2b2b2b;
  background: none;
  border: none;
  padding: 6px 10px;
  box-shadow: none;
  text-shadow:
    0 0 6px #fff, 0 0 6px #fff, 0 0 6px #fff, 0 0 6px #fff,
    0 0 6px #fff, 0 0 6px #fff, 0 0 6px #fff, 0 0 6px #fff;
}

/* Attribution Pill — based on MapLibre's own expanded attribution control
   (confirmed live against maplibre-gl.css: background #fff, border-radius
   12px, text rgba(0,0,0,.75), font 12px/20px Helvetica Neue/Arial/
   Helvetica/sans-serif) rather than approximating it, sized 30% bigger
   per explicit feedback that the real control's own tiny size read too
   small once this became a selectable title style rather than an
   incidental match. font-family stays a plain override (not stacked
   after Patrick Hand/Tomatoku like the other presets) since matching the
   real control's own font is still the point of this preset, just at a
   more legible size. */
.map-title-style-attribution {
  font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
  font-size: calc(15.6px * var(--map-title-scale));
  line-height: 26px;
  font-weight: normal;
  color: rgba(0, 0, 0, 0.75);
  background: #fff;
  border: none;
  border-radius: 12px;
  padding: 2px 8px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

/* Resize handle: shown only once the title is selected (a click on the
   title box, same "controls only show once selected" rule
   setupStickerDrop's own resize handle follows) — dragging it scales
   --map-title-scale via setupMapTitle's pointerdown handler. */
.map-title-resize-handle {
  display: none;
  position: absolute;
  right: -6px;
  bottom: -6px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  border: 1.5px solid #666;
  cursor: nwse-resize;
  touch-action: none;
}

.map-title-overlay.map-title-selected .map-title-resize-handle {
  display: block;
}

/* Sidebar style picker (#map-title-style-picker, index.html) — a
   dropdown, not an always-expanded stacked list: #map-title-style-trigger
   shows a live preview of the CURRENTLY SELECTED preset (collapsed by
   default), and clicking it opens #map-title-style-menu, listing every
   MAP_TITLE_STYLE_PRESETS entry as its own live preview swatch (built in
   setupMapTitle/renderMapTitleStylePicker). Each swatch renders the
   *actual current title text* in that preset's real styling (reusing
   .map-title-text/.map-title-style-* directly, not a hand-drawn
   approximation) so a user can compare presets side by side without
   clicking through them one at a time on the map itself. */
.map-title-style-picker {
  position: relative;
}

/* Both the trigger and each swatch are real <button> elements, which
   .side-panel button (a class+element compound selector, specificity
   0-1-1) otherwise restyles with its own yellow background/bold
   border/pill radius regardless of source order here — found live via
   screenshot, the exact same specificity trap .page-shape-btn's own
   comment already documents elsewhere in this file. Matching that same
   compound-selector shape is what actually wins. The trigger itself now
   matches .water-style-trigger/.grass-style-trigger's own bordered pill
   box (border/background/padding) so all three style pickers in the
   sidebar look like the same kind of control — an earlier plain/
   invisible-wrapper version (no border/background of its own) was
   reported as looking inconsistent with the other two pickers. The
   *swatch* buttons inside the open menu stay borderless/plain, since a
   swatch choosing its own background/border on top of the preview's own
   styling was the original "button around the style" look that was
   reported as looking wrong. */
.side-panel button.map-title-style-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  box-sizing: border-box;
  padding: 4px;
  border: 2px solid #2b2b2b;
  border-radius: 10px;
  background: #fdfbf5;
  cursor: pointer;
}

/* Wraps the live preset preview (see the comment above this rule block)
   — needs to flex-grow so the preview stretches to the trigger's own
   full width (matching the swatch box it opens below), pushing the
   arrow to the right edge instead of sitting right after the preview's
   own content width. */
#map-title-style-trigger-preview {
  flex: 1;
  min-width: 0;
  height: 28px;
  display: flex;
  align-items: center;
  overflow: hidden;
}

.map-title-style-trigger-arrow {
  flex: 0 0 auto;
  font-size: 19px;
  color: #666;
}

.map-title-style-menu {
  display: flex;
  flex-direction: column;
  gap: 6px;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: #fdfbf5;
  border: 2px solid #2b2b2b;
  border-radius: 10px;
  padding: 6px;
  box-shadow: 3px 3px 0 rgba(43, 43, 43, 0.15);
  z-index: 6;
  max-height: 320px;
  overflow-y: auto;
}

/* A plain `display: flex` above always beats the [hidden] attribute's own
   UA-stylesheet `display: none` (same specificity family, source order
   wins) — confirmed live in an earlier version of this dropdown (the
   on-map style menu this sidebar version replaced had the identical bug)
   — without this override the menu renders open even while `hidden` is
   set. */
.map-title-style-menu[hidden] {
  display: none;
}

.side-panel button.map-title-style-swatch {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 28px;
  box-sizing: border-box;
  padding: 4px;
  border: 2px solid transparent;
  border-radius: 10px;
  background: none;
  overflow: hidden;
  cursor: pointer;
}

.side-panel button.map-title-style-swatch.map-title-style-swatch-active {
  border-color: #2b2b2b;
  background: rgba(43, 43, 43, 0.06);
}

.side-panel button.map-title-style-swatch:hover {
  background: rgba(43, 43, 43, 0.06);
}

/* The preview copy of .map-title-text inside a trigger/swatch must never
   pick up the on-map overlay's own resize scale (--map-title-scale) or
   respond to drag/click-to-select — it's a static preview, not the real
   draggable element. Fixed font-size (ignoring the custom property) and
   default cursor/no touch-action override do that without needing a
   second near-duplicate set of preset classes just for previews. */
.map-title-style-trigger .map-title-text,
.map-title-style-swatch .map-title-text {
  font-size: 18px !important;
  line-height: 20px !important;
  padding: 0 8px !important;
  border-width: 0 !important;
  box-shadow: none !important;
  white-space: nowrap !important;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: default;
  touch-action: auto;
  user-select: auto;
}

/* Sidebar water style picker (#water-style-picker, index.html) — same
   dropdown pattern as #map-title-style-picker above (collapsed trigger
   showing the current pick, opens a menu of every WATER_STYLE_PRESETS
   entry as its own swatch), just with a tiled-texture preview swatch
   (.water-style-swatch-preview, a plain div with a repeating
   background-image) instead of styled preview text — a water style is a
   fill pattern, not something expressible as text. */
.water-style-picker {
  position: relative;
}

.side-panel button.water-style-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  box-sizing: border-box;
  padding: 4px;
  border: 2px solid #2b2b2b;
  border-radius: 10px;
  background: #fdfbf5;
  cursor: pointer;
}

#water-style-trigger-preview,
#grass-style-trigger-preview {
  flex: 1;
  min-width: 0;
  display: flex;
}

.water-style-trigger-arrow {
  flex: 0 0 auto;
  font-size: 19px;
  color: #666;
}

.water-style-menu {
  display: flex;
  flex-direction: column;
  gap: 6px;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: #fdfbf5;
  border: 2px solid #2b2b2b;
  border-radius: 10px;
  padding: 6px;
  box-shadow: 3px 3px 0 rgba(43, 43, 43, 0.15);
  z-index: 6;
  max-height: 320px;
  overflow-y: auto;
}

.water-style-menu[hidden] {
  display: none;
}

.side-panel button.water-style-swatch {
  display: flex;
  align-items: center;
  width: 100%;
  box-sizing: border-box;
  padding: 4px;
  border: 2px solid transparent;
  border-radius: 10px;
  background: none;
  cursor: pointer;
}

.side-panel button.water-style-swatch.water-style-swatch-active {
  border-color: #2b2b2b;
  background: rgba(43, 43, 43, 0.06);
}

.side-panel button.water-style-swatch:hover {
  background: rgba(43, 43, 43, 0.06);
}

.water-style-swatch-preview {
  width: 100%;
  height: 28px;
  border-radius: 6px;
  background-repeat: repeat;
  background-size: 48px auto;
  flex: 1;
  min-width: 0;
}

@media print {
  .side-panel {
    display: none;
  }

  .map-stage {
    padding: 0;
  }

  .page-box {
    border: none;
    box-shadow: none;
  }

  body {
    background: #fff;
  }
}

/* "Remove landmark" popup shown on clicking a POI (see setupPoiRemoval
   in app.js). The popup's own content box is stripped bare (no
   background/border/shadow/padding) so the button below is the only
   visible shape — otherwise MapLibre's default white content box shows
   through as a second outline around the button. */
.maplibregl-popup.poi-remove-popup-container .maplibregl-popup-content {
  padding: 0;
  background: transparent;
  box-shadow: none;
}

.poi-remove-btn {
  font-family: inherit;
  font-size: 13px;
  padding: 5px 10px;
  border-radius: 12px 14px 12px 16px / 16px 12px 18px 12px;
  border: 2px solid #2b2b2b;
  background: #ffd166;
  cursor: pointer;
  white-space: nowrap;
}

.poi-remove-btn:hover {
  background: #ffbe3d;
}

