/* C17 click-to-load Google Landbank map. Geometry and states use shared tokens.

   THE COMPONENT IS A PAIR: a map and a list of the same records. The list is
   not a caption for the map - it carries every field of every location, it
   reads with no JavaScript and before Google is activated, and selecting in
   one view selects in the other.

   THE BAR AND THE LIST FLOAT ON THE MAP (site owner, 12 Aug 2026, reversing
   the same day's earlier bar-above-map decision recorded in the note on
   landbank_map in scripts/components.py - read it before touching this
   again). That earlier layout was replaced because overlaid filters covered
   the pins they described and a floating detail sheet needed its camera
   padding measured out by hand to avoid hiding the plot it had just
   selected. Both are solved differently this time instead of reverting
   again: the filter bar is capped to leave the list's own column clear
   (never drawn under it), and page-landbank.js now measures the actual
   rendered rect of both floating panels on every camera move and adds that
   as directional fitBounds padding, so the padding tracks whatever the
   panels really occupy - open or closed, wrapped or not - rather than a
   fixed guess.

   NO box-shadow. Both floating panels use the same translucent-glass recipe
   the header pill already uses ( rgba(255,255,255,.82) + blur(18px)
   saturate(140%) + a hairline --line border) rather than a second glass
   treatment - separation from the map is that blur plus the border, never a
   shadow. The markers keep a white ring instead, because a ring is a border
   rather than a shadow and a pin still has to read on a photograph. */

/* ===== SHELL ===== */

/* minmax(0, 1fr), not the implicit auto column: the map's own DOM has a
   min-content width larger than a phone, and a grid item at auto width takes
   it and pushes the map past the viewport edge.

   position: relative because .land-explorer-bar is this element's child, not
   .land-explorer-body's - .land-explorer-body only wraps the map and the
   list panel. Once the bar is taken out of flow (it floats over the map now)
   this is the single remaining grid item, so .land-explorer's own box still
   exactly matches the map's, and the bar positions against the right thing. */
/* THE TWO FLOATING PANELS ARE ONE HEIGHT, IN BOTH STATES (site owner, 14 Aug
   2026). They dock to the same top edge and read as a pair, but each used to
   compute its own resting height from a different starting point, so all
   three of these disagreed: the bar with filters only (51px, the facade
   state), the bar once the map is live and its toolbar appears (61px - it
   visibly grew when you activated the map), and the closed list, built from a
   caption line inside a 16px box (57px).

   One number now settles all three. --land-map-panel-block is the tallest row
   either panel can hold - a toolbar control - plus the padding and hairline
   that wrap it, and both panels take it as a floor. Nothing new is invented:
   every term already existed in this file, as a token or as a literal typed
   into one rule. Change the control target and both panels follow. */
.land-explorer {
  --land-map-toolbar-pad: 4px;
  --land-map-panel-line: 1px;
  --land-map-panel-block: calc(
    var(--land-map-control-target) + 2 * var(--land-map-toolbar-pad)
    + 2 * var(--land-map-panel-line)
  );
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--land-map-gap);
  margin-block-start: var(--sec-head-gap);
}

.land-explorer-bar,
.land-explorer-body { min-inline-size: 0; }

/* THE BAR IS A POSITION, NOT A SURFACE (site owner, 14 Aug 2026). It used to
   carry the same white glass its two groups already carry, so every control
   sat on two stacked whites - and because the bar is stretched to the list's
   column edge, the outer one also drew a wide empty white band across the map
   beside them. The ground belongs to the things that need one: the filter
   strip and the toolbar each have theirs, and this element now only places
   them.

   inset-inline-end is still capped short of the list panel's own column (its
   width plus the gap either side of it) so the bar's contents can never be
   drawn under the list - the two never need a shared z-index fight because
   they simply don't occupy the same pixels. --land-map-panel-layer, not
   --land-map-overlay-layer: the facade also uses the overlay layer and comes
   after this element in the DOM, so at an equal z-index it would win the
   stack and hide the controls completely. */
.land-explorer-bar {
  position: absolute;
  z-index: var(--land-map-panel-layer);
  inset-block-start: var(--land-map-inset);
  inset-inline-start: var(--land-map-inset);
  inset-inline-end: calc(var(--land-map-list-inline) + 2 * var(--land-map-inset));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--land-map-gap);
}

/* ONE FLOATING GROUND, WRITTEN ONCE. Everything that floats on the map is
   drawn the same way - the translucent glass the header pill already uses
   (rgba white + blur + saturate) over a hairline --line border, never a
   shadow, which the token system bans. The filter strip takes it in place of
   the flat --surface-raised .news-tabs carries elsewhere, because here it
   sits on a photograph rather than on a page surface.

   The three are also one height: --land-map-panel-block is the tallest row
   any of them holds - a toolbar control - plus its padding and the hairline.
   The list panel's own summary takes the same floor further down. */
.land-map-filterbar,
.land-map-toolbar,
.land-map-list {
  border: var(--land-map-panel-line) solid var(--line);
  border-radius: var(--r);
  background: rgba(255, 255, 255, .82);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
}

.land-map-filterbar,
.land-map-toolbar {
  align-items: center;
  min-block-size: var(--land-map-panel-block);
}

/* The stacking context for the map plus its two floating panels. Its own
   block-size is what the map fills (.land-map is inset: 0 inside it), so the
   two live at the same rule instead of drifting apart. */
.land-explorer-body {
  position: relative;
  block-size: var(--land-map-block);
}

/* ===== THE CONTROL BAR ===== */

/* The filter pill, compact variant. Same component as .news-tab on the news
   index - same weight, same colour roles, same one radius, accent fill on the
   selected one - at the control height this bar needs (site owner: the tabs
   were too large for what they say). The count badge is what makes a short
   label carry its own weight: "صناعي 4" says more than "صناعي" in less room. */
.land-map-filterbar {
  --control-h: var(--land-map-filter-height);
  flex: 0 1 auto;
}

.land-map-filterbar .news-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding-inline: 14px;
  font-size: var(--type-meta);
}

.land-map-filterbar .news-tab[aria-pressed="true"] {
  color: var(--brand-white);
  background: var(--accent-fill);
}

/* The count carries itself with weight and tone, not a chip (site owner, 12
   Aug 2026): a filled badge inside a pill that is itself filled when active
   was two nested surfaces saying one thing. The number is --ink against the
   pill's lighter --brand-jais label, so it reads as the heavier of the two
   without a second background behind it. */
.news-tab-count {
  color: var(--ink);
  font-size: var(--type-meta);
  font-weight: 700;
}

.news-tab[aria-pressed="true"] .news-tab-count {
  color: var(--brand-white);
}

.land-map-toolbar {
  display: flex;
  flex: 0 0 auto;
  gap: 4px;
  padding: var(--land-map-toolbar-pad);
}

.land-map-control.btn {
  inline-size: var(--land-map-control-target);
  min-inline-size: var(--land-map-control-target);
  block-size: var(--land-map-control-target);
  min-block-size: var(--land-map-control-target);
  padding: 0;
  background: transparent;
  color: var(--brand-jais);
}

.land-map-control.btn:is(:hover, :focus-visible) {
  background: var(--accent-fill);
  color: var(--brand-white);
}

/* The map controls do nothing until the map is live, and a control that does
   nothing must not be reachable. */
.land-explorer:not(.is-live) .land-map-toolbar {
  display: none;
}

/* NOTHING BUT THE PICTURE UNTIL THE VISITOR ASKS FOR THE MAP (site owner,
   14 Aug 2026). The filter strip and the locations panel float over a map
   that is not running yet, so before the click they are two controls for
   something the visitor has not started - a distraction in front of a single
   invitation.

   KEYED ON .is-scripted, WHICH page-landbank.js ADDS, AND THAT MATTERS. The
   markup ships with .is-facade, so a rule keyed on that alone would also hide
   them where nothing can ever bring them back: with JavaScript off, the map
   never activates and the list IS the inventory - every location, in full, in
   the markup. Hiding it there would delete the page's content for that
   visitor rather than defer it. */
.land-explorer.is-scripted:not(.is-live) :is(.land-explorer-bar, .land-map-list) {
  display: none;
}

/* ===== THE MAP ===== */

.land-map {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: var(--r);
  background: var(--surface-sunken);
  color: var(--ink);
}

.land-map-canvas,
.land-map-facade {
  position: absolute;
  inset: 0;
}

.land-map-canvas {
  z-index: 1;
  background: var(--surface-sunken);
}

.land-map-canvas:focus-visible,
.land-map-facade:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* The invitation sits at the block end, not dead centre: centred, its card
   covered the very cluster of markers the poster exists to show. */
.land-map-facade {
  z-index: var(--land-map-overlay-layer);
  display: grid;
  place-items: end center;
  inline-size: 100%;
  block-size: 100%;
  padding: var(--land-map-inset);
  overflow: hidden;
  border: 0;
  background: var(--surface-raised);
  color: var(--brand-white);
  cursor: pointer;
  font: inherit;
  text-align: center;
}

/* cover, not contain: the poster is the live map's own overview view (site
   owner, 14 Aug 2026), so it has to fill the same box the canvas fills. A
   contained map would sit letterboxed inside the frame the canvas fills edge
   to edge, and activation would visibly resize the picture as well as
   replacing it. */
.land-map-facade img {
  position: absolute;
  inset: 0;
  z-index: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform var(--dur-base) var(--ease-out);
}

/* EVEN, AND DARK ENOUGH THAT THE MAP IS A TEXTURE (site owner, 14 Aug 2026,
   revising the same day's gradient). The poster is not there to be read
   before the click - it is there so the visitor knows a map is what the
   button opens - so the veil is one flat value across the whole picture
   rather than a ramp that leaves the top nearly clear and invites reading.

   Built on --image-overlay (true black, so the picture dims instead of
   greying) and flat, exactly like .menu-backdrop. Section 11's .86 floor
   governs flat brand-black layers that carry text; nothing reads off this
   one, the invitation has its own opaque ground, so the value answers to
   what it has to communicate rather than to a contrast requirement. */
.land-map-facade::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(var(--image-overlay-rgb), .70);
}

/* THE COPY CARRIES ITS OWN GROUND; THE PICTURE IS NOT VEILED (site owner, 14
   Aug 2026). A full-bleed --scrim-control over the poster is .92 opaque - the
   alpha the token system requires of a scrim that has body text on it - so it
   did its job and hid the map almost completely, which is the opposite of
   what a facade showing the live view is for. The invitation now sits on
   --surface-inverse, exactly the panel .land-map-loading uses in the same
   place a moment later, so the text keeps a contrast the system already
   documents and the poster keeps its own colour. No third treatment is
   invented: this is the two roles the component already had, applied to the
   element that actually needs each one. */
.land-map-facade-copy {
  position: relative;
  z-index: 2;
  display: grid;
  justify-items: center;
  gap: var(--land-map-gap);
  max-inline-size: min(var(--land-map-tooltip-inline), calc(100% - 2 * var(--land-map-inset)));
  padding: var(--land-map-panel-padding);
  border-radius: var(--r);
  background: var(--surface-inverse);
}

.land-map-facade-copy strong {
  font-size: var(--type-card);
  line-height: var(--leading-card);
}

.land-map-facade-copy span {
  color: var(--brand-white);
  font-size: var(--type-caption);
  line-height: var(--leading-body);
}

.land-map-facade:is(:hover, :focus-visible) img { transform: scale(var(--zoom)); }

.land-map-loading,
.land-map-fallback,
.land-map-noscript {
  position: absolute;
  z-index: var(--land-map-overlay-layer);
  inset-inline-start: 50%;
  inline-size: min(var(--land-map-tooltip-inline), calc(100% - 2 * var(--land-map-inset)));
  margin: 0;
  padding: var(--land-map-inset);
  border-radius: var(--r);
  background: var(--surface-inverse);
  color: var(--brand-white);
  font-size: var(--type-caption);
  line-height: var(--leading-body);
  text-align: center;
  transform: translateX(50%);
}

.land-map-loading {
  inset-block-start: 50%;
  transform: translate(50%, -50%);
}

.land-map-loading .ph { animation: land-map-spin var(--dur-ambient) linear infinite; }

.land-map-fallback,
.land-map-noscript { inset-block-end: var(--land-map-inset); }

[dir="ltr"] :is(.land-map-loading, .land-map-fallback, .land-map-noscript) {
  transform: translateX(-50%);
}

[dir="ltr"] .land-map-loading { transform: translate(-50%, -50%); }

.land-explorer.is-live :is(.land-map-facade, .land-map-loading, .land-map-fallback) {
  display: none;
}

.land-explorer.is-loading .land-map-facade { cursor: wait; }

/* ===== THE LIST ===== */

/* Floats over the map's top-end corner, on the same glass recipe as
   .land-explorer-bar. Fixed to --land-map-list-inline (the same token the
   old side-by-side column used) so .land-explorer-bar's own inset-inline-end
   can be capped against one shared number instead of two rules drifting
   apart. max-block-size stops the open panel from growing past the map's own
   bottom edge - it is a floating card, not a second surface the map sits
   inside. */
.land-map-list {
  position: absolute;
  z-index: var(--land-map-panel-layer);
  inset-block-start: var(--land-map-inset);
  inset-inline-end: var(--land-map-inset);
  inline-size: var(--land-map-list-inline);
  max-inline-size: calc(100% - 2 * var(--land-map-inset));
  display: flex;
  flex-direction: column;
  block-size: auto;
  /* The same floor the bar takes, on the panel itself and not only on its
     summary: a border renders as a fraction of a CSS pixel at a fractional
     device-pixel ratio, so a panel whose height is only implied by its
     contents lands ~0.6px short of a bar whose height is stated. */
  min-block-size: var(--land-map-panel-block);
  max-block-size: calc(100% - 2 * var(--land-map-inset));
  overflow: hidden;
}

/* The panel's own <summary>: the same disclosure contract every row inside
   it already uses (.land-plot-summary), one level up. No default marker, a
   caret that rotates open like every other one on the site. */
/* Its resting height is the filter bar's, by construction: the control row
   the bar wraps, plus the same padding the bar puts around it. The inline
   padding stays --land-map-panel-padding so the label lines up with the rows
   disclosed underneath it. */
.land-map-list-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex: 0 0 auto;
  margin: 0;
  min-block-size: calc(var(--land-map-panel-block) - 2 * var(--land-map-panel-line));
  padding-block: 0;
  padding-inline: var(--land-map-panel-padding);
  color: var(--label);
  font-size: var(--type-caption);
  font-weight: 700;
  line-height: var(--leading-body);
  cursor: pointer;
  list-style: none;
}

.land-map-list-head::-webkit-details-marker { display: none; }

.land-map-list:focus-within > .land-map-list-head,
.land-map-list-head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: var(--r);
}

.land-map-list-caret {
  flex: 0 0 auto;
  color: var(--brand-jais);
  transition: transform var(--dur-fast) var(--ease-out);
}

.land-map-list[open] > .land-map-list-head .land-map-list-caret { transform: rotate(180deg); }

/* THE DISCLOSURE GLIDES, IT DOES NOT CUT (site owner, 14 Aug 2026). Both the
   panel and the rows inside it used to appear and vanish between two frames,
   which reads as a glitch next to a map that now moves smoothly. The heights
   animate at --dur-base on the shared easing - the same motion vocabulary the
   caret rotation beside them already uses - and nothing new is invented for
   it.

   Wrapped in @supports because ::details-content and the `auto` keyword being
   animatable are recent: where either is missing the whole block is inert and
   the panel snaps open exactly as it always did. The flex declarations are
   not decoration - ::details-content becomes the real flex item between the
   panel and its list, so without them the scroller inside loses its height
   and the list stops scrolling. */
@supports (interpolate-size: allow-keywords) {
  .land-explorer { interpolate-size: allow-keywords; }

  .land-map-list::details-content,
  .land-plot::details-content {
    block-size: 0;
    overflow: clip;
    opacity: 0;
    transition: block-size var(--dur-base) var(--ease-out),
      opacity var(--dur-base) var(--ease-out),
      content-visibility var(--dur-base) allow-discrete;
  }

  .land-map-list[open]::details-content,
  .land-plot[open]::details-content {
    block-size: auto;
    opacity: 1;
  }

  .land-map-list::details-content {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-block-size: 0;
  }
}

/* A hairline scroller, not a system-width one: the same `thin` + 4px webkit
   treatment .sector-grid already uses, on --line so the thumb is the tonal
   step the panel is already drawn with rather than a new colour. */
.land-map-list-items {
  flex: 1 1 auto;
  min-block-size: 0;
  margin: 0;
  padding: 0 var(--land-map-panel-padding) var(--land-map-panel-padding);
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

.land-map-list-items::-webkit-scrollbar { width: 4px; }

.land-map-list-items::-webkit-scrollbar-track { background: transparent; }

.land-map-list-items::-webkit-scrollbar-thumb {
  border-radius: var(--r);
  background: var(--line);
}

.land-plot-item + .land-plot-item { margin-block-start: 4px; }

.land-plot {
  border-radius: var(--r);
  background: none;
}

/* Selected in either view. The map paints the parcel in the accent; the row
   states the same thing with a tonal step and the accent on its own index,
   because a filled row would compete with the selected filter pill. */
.land-plot.is-selected { background: var(--surface-sunken); }

.land-plot-summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  cursor: pointer;
  list-style: none;
}

.land-plot-summary::-webkit-details-marker { display: none; }

.land-plot-summary:hover .land-plot-title { color: var(--brand-black); }

.land-plot-summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: var(--r);
}

/* The row's own ordering, renumbered on every filter change. It used to be a
   key into the map as well - the marker carried the same number - and since
   14 Aug 2026 the marker carries the area's name instead, which identifies it
   without a lookup. The number stays because a list of nine rows reads better
   numbered, not because anything else depends on it. */
.land-plot-index {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  inline-size: var(--land-map-index-target);
  block-size: var(--land-map-index-target);
  border-radius: var(--land-map-pin-radius);
  background: var(--brand-liwa);
  color: var(--brand-white);
  font-size: var(--type-caption);
  font-weight: 700;
}

.land-plot.is-selected .land-plot-index { background: var(--accent-fill); }

.land-plot-head {
  display: grid;
  flex: 1 1 auto;
  gap: 2px;
  min-inline-size: 0;
}

.land-plot-title {
  color: var(--ink);
  font-size: var(--type-meta);
  font-weight: 700;
  line-height: var(--leading-card);
  transition: color var(--dur-fast) var(--ease-out);
}

.land-plot-meta {
  color: var(--muted);
  font-size: var(--type-caption);
  line-height: var(--leading-body);
}

.land-plot-caret {
  flex: 0 0 auto;
  color: var(--brand-jais);
  transition: transform var(--dur-fast) var(--ease-out);
}

.land-plot[open] .land-plot-caret { transform: rotate(180deg); }

.land-plot-detail {
  padding: 0 12px 12px calc(12px + var(--land-map-index-target) + 10px);
}

[dir="ltr"] .land-plot-detail {
  padding: 0 calc(12px + var(--land-map-index-target) + 10px) 12px 12px;
}

.land-plot-detail > p {
  margin: 0 0 12px;
  color: var(--muted);
  font-size: var(--type-caption);
  line-height: var(--leading-body);
}

.land-plot-detail dl {
  display: grid;
  gap: 6px;
  margin: 0 0 12px;
}

.land-plot-detail dl > div {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: 10px;
}

.land-plot-detail :is(dt, dd) {
  margin: 0;
  font-size: var(--type-caption);
  line-height: var(--leading-body);
}

.land-plot-detail dt {
  color: var(--label);
  font-weight: 700;
}

.land-plot-detail dd { color: var(--ink); }

.land-plot-status {
  color: var(--muted);
}

.land-plot-detail .action-link {
  color: var(--accent-strong);
  font-size: var(--type-caption);
}

/* SCOPED VARIANT, THIS BUTTON ONLY - does not touch the shared .action-link
   used elsewhere on the site (company/project cards etc.), which stays the
   documented no-background underline link (design-system/buttons.html:
   "colour is resolved by the ancestor, never by a modifier on the link
   itself"). Site owner, 31 Aug 2026, on this specific control inside the
   map's location list: reversed rest/active so the INACTIVE state carries
   no fill (text only) and the ACTIVE (hover/focus) state gets the filled
   background for real distinction - the same rest/hover contract every
   filled .btn on the site already uses, applied here as a card-scoped
   class rather than widening .action-link's own contract site-wide. */
.land-plot-detail .land-plot-action {
  display: inline-flex;
  align-items: center;
  gap: .48rem;
  padding: 8px 16px;
  border-radius: var(--r);
  color: var(--accent-strong);
  font-size: var(--type-caption);
  font-weight: 700;
  transition: background var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.land-plot-detail .land-plot-action::after {
  content: none;
}

.land-plot-detail .land-plot-action:is(:hover, :focus-visible) {
  background: var(--accent-fill);
  color: var(--on-action);
}

.land-plot-detail .land-plot-action > i.ph {
  transition: transform var(--dur-base) var(--ease-out);
}

/* ===== MARKERS ===== */

/* ===== MARKERS =====
   TWO KINDS, TWO SHAPES, ONE COLOUR EACH FOR GOOD (site owner, 14 Aug 2026).
   A group of plots is a circle carrying how many it holds, in --accent-fill.
   A single plot is a plate carrying its own area name, in Liwa. Before this
   both were a number in a circle - a row number and a count - so the glyph
   said nothing about which, and the two colours were the only clue.

   The colours no longer move. Hover, focus and selection are the ring and the
   scale, never a fill swap: a selected plot used to turn --accent-fill, the
   cluster colour, so orange meant "several plots" and "this one" at once. */

.land-map-marker-wrap,
.land-map-cluster-wrap {
  display: grid;
  place-items: center;
  min-inline-size: var(--land-map-pin-target);
  block-size: var(--land-map-pin-target);
  padding: 0;
  border: 0;
  cursor: pointer;
  font: inherit;
}

/* Sized by its name, not by a fixed box - but never below the touch target,
   which is what min-inline-size above is for. */
.land-map-marker-wrap { background: transparent; }

.land-map-cluster-wrap {
  inline-size: var(--land-map-pin-target);
  border: var(--land-map-pin-ring) solid var(--brand-white);
  border-radius: var(--land-map-pin-radius);
  background: var(--accent-fill);
  color: var(--brand-white);
  font-size: var(--type-card);
  font-weight: 700;
  line-height: var(--leading-card);
  transition: transform var(--dur-fast) var(--ease-out);
}

/* THE NAME PLATE. One line, capped, and an ellipsis past the cap - which is
   not the banned clamp: that rule is about copy a reader can reach nowhere
   else, and every name here is also on the marker's title, in its hover
   tooltip and in the list row it selects. The cap is the tooltip's own width,
   so the plate can never be wider than the panel that explains it; today the
   longest name renders well inside it and no ellipsis appears at all.

   One radius, var(--r): --land-map-pin-radius is 50%, which is a circle's
   rule and would draw this as an ellipse. */
.land-map-marker {
  display: block;
  max-inline-size: var(--land-map-tooltip-inline);
  block-size: var(--land-map-pin-mark);
  padding-inline: var(--land-map-gap);
  border: var(--land-map-pin-ring) solid var(--brand-white);
  border-radius: var(--r);
  background: var(--brand-liwa);
  color: var(--brand-white);
  font-size: var(--type-caption);
  font-weight: 700;
  line-height: calc(var(--land-map-pin-mark) - 2 * var(--land-map-pin-ring));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: transform var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

/* The off-screen twin page-landbank.js measures a name against before it
   decides whether two plates would collide. Same class, so same font, same
   padding and same cap - a measurement of the rule rather than of a guess. */
.land-map-marker--ruler {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  visibility: hidden;
  pointer-events: none;
}

.land-map-marker-wrap:is(:hover, .is-selected) .land-map-marker,
.land-map gmp-advanced-marker:focus-visible .land-map-marker {
  border-color: var(--accent-fill);
  transform: scale(var(--zoom));
}

.land-map-cluster-wrap:hover,
.land-map gmp-advanced-marker:focus-visible .land-map-cluster-wrap {
  transform: scale(var(--zoom));
}

/* The focus ring follows the shape it is drawn around: a circle for a cluster,
   the one radius for a name plate. */
.land-map gmp-advanced-marker:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r);
}

.land-map gmp-advanced-marker:focus-visible:has(.land-map-cluster-wrap) {
  border-radius: var(--land-map-pin-radius);
}

.land-map-tooltip-content {
  color: var(--ink);
  font-family: inherit;
  font-size: var(--type-meta);
  line-height: var(--leading-body);
  text-align: start;
}

.land-map-tooltip-content strong { font-size: var(--type-card); }

.land-map :is(.gm-style, .gm-style button, .gm-style div) { font-family: inherit; }

/* ===== FULLSCREEN =====
   Fullscreen takes the whole component, map and list together: a full-screen
   map without its list would drop the one view that stays legible. */

.land-explorer:fullscreen,
.land-explorer.is-fullscreen {
  inline-size: 100vw;
  block-size: 100svh;
  padding: var(--land-map-inset);
  background: var(--surface-page);
}

body.has-land-map-fullscreen { overflow: hidden; }

.land-explorer.is-fullscreen {
  position: fixed;
  inset: 0;
  z-index: var(--land-map-fullscreen-layer);
}

/* .land-explorer-bar no longer occupies a grid row of its own - it floats
   out of flow over the map, in fullscreen exactly as everywhere else - so
   .land-explorer-body is the only in-flow child left and simply fills the
   fullscreen container. .land-map already fills .land-explorer-body via
   inset: 0, and .land-map-list keeps hugging its own content instead of
   being forced to 100%: a floating card that always filled the full height
   even closed would cover the map behind it for no reason. */
.land-explorer:is(:fullscreen, .is-fullscreen) .land-explorer-body {
  block-size: 100%;
}

@keyframes land-map-spin { to { transform: rotate(1turn); } }

/* ===== RESPONSIVE =====
   980px is --land-map-collapse. Below it there is no side column left for the
   bar to dodge, so the two floating panels re-dock to opposite full-width
   edges instead of sharing the top corner - the bar stays at the top, the
   list moves to the bottom, and neither has to shrink to make room for the
   other on a narrow screen. */

@media (max-width: 980px) {
  .land-explorer-body { block-size: var(--land-map-block-mobile); }

  /* ONE CONTROL ROW, NOT TWO. With the list gone from the top corner the bar
     spans the full width, and the toolbar still did not fit beside the
     filters, so it wrapped to a second row - 111px of the map's own 420px
     spent on controls at the width that can least afford it. The filter strip
     is a horizontal scroller by construction (.news-tabs: nowrap, overflow-x
     auto), so it can hand the toolbar the width it needs and keep every pill
     reachable by swipe instead of by line break. */
  .land-explorer-bar {
    inset-inline-end: var(--land-map-inset);
    flex-wrap: nowrap;
  }

  .land-map-filterbar { min-inline-size: 0; }

  /* Pinch is the zoom affordance on a touch screen - gestureHandling is
     'cooperative', so two fingers already zoom, and the map keeps its own
     keyboard shortcuts. The two buttons are 88px of a row that also has to
     hold the filters, and at 390px they left the filter strip 84px: one pill
     of five. The filters are what this component is steered with; the zoom
     buttons are a convenience that the screen itself provides. */
  .land-map-control[data-land-control="zoom-in"],
  .land-map-control[data-land-control="zoom-out"] { display: none; }

  /* The list re-docks to the map's bottom edge at this width - the exact
     place the facade's invitation sits - so the invitation is lifted by that
     collapsed panel's own height (its inset plus one control-sized row)
     instead of being half-hidden behind it. */
  .land-map-facade {
    padding-block-end: calc(2 * var(--land-map-inset) + var(--land-map-control-target));
  }

  .land-map-list {
    inset-block-start: auto;
    inset-block-end: var(--land-map-inset);
    inset-inline-start: var(--land-map-inset);
    inline-size: auto;
    max-inline-size: calc(100% - 2 * var(--land-map-inset));
    max-block-size: var(--land-map-list-block-mobile);
  }
}

@media (max-width: 560px) {
  .land-map-filterbar { max-inline-size: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .land-map-facade img,
  .land-map-marker,
  .land-plot-caret,
  .land-plot-title { transition: none; }

  /* The camera's own flight has the same escape, in page-landbank.js. */
  .land-map-list::details-content,
  .land-plot::details-content { transition: none; }

  .land-map-loading .ph { animation: none; }
}
